Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
dstansby opened this issue Sep 4, 2024 · 27 comments
Closed

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

dstansby opened this issue Sep 4, 2024 · 27 comments
Assignees
Labels
bug Something isn't working compatibility Compatibility with a specification or another tool

Comments

@dstansby
Copy link

dstansby commented Sep 4, 2024

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

@zanieb
Copy link
Member

zanieb commented Sep 4, 2024

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. 🤔

@bluss
Copy link
Contributor

bluss commented Sep 4, 2024

For the record, rye did this astral-sh/rye#233

@charliermarsh
Copy link
Member

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

@charliermarsh charliermarsh added bug Something isn't working compatibility Compatibility with a specification or another tool labels Sep 4, 2024
@mraesener-aubex
Copy link

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

@astrojuanlu
Copy link

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?

@emcek
Copy link

emcek commented Oct 16, 2024

$ 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

@astrojuanlu
Copy link

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

@giolucasd
Copy link

giolucasd commented Oct 17, 2024

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?

@zanieb
Copy link
Member

zanieb commented Oct 17, 2024

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.

@harrylaulau
Copy link

harrylaulau commented Oct 18, 2024

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?

@sonotley
Copy link

sonotley commented Oct 27, 2024

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
Copy link

sonotley commented Nov 8, 2024

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

@harrylaulau
Copy link

harrylaulau commented Nov 9, 2024

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.

@charliermarsh
Copy link
Member

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 charliermarsh self-assigned this Dec 15, 2024
@mraesener-aubex
Copy link

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?

@zanieb
Copy link
Member

zanieb commented Dec 15, 2024

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?

@charliermarsh
Copy link
Member

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

@charliermarsh
Copy link
Member

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
Copy link
Member

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

@charliermarsh
Copy link
Member

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

@mraesener-aubex
Copy link

mraesener-aubex commented Dec 15, 2024

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?

@charliermarsh
Copy link
Member

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

@domfellner
Copy link

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?

@zanieb
Copy link
Member

zanieb commented Dec 17, 2024

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
Copy link
Member

zanieb commented Dec 20, 2024

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

@astrojuanlu
Copy link

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!

@zanieb
Copy link
Member

zanieb commented Dec 20, 2024

You can uv python install --reinstall <version>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working compatibility Compatibility with a specification or another tool
Projects
None yet
Development

No branches or pull requests