Workaround for uv/uvx future import bug

The uv and uvx tools have a very particular way to make virtual environments relocateable: they replace the shebang in python scripts with some polyglot magic, so that the executable is both a valid bash script and a python script at the same time. Unfortunately this breaks with a SyntaxError if the original script uses from future import ... in certain ways. The bug is known and documented in Issue #6489 but unresolved since August 2024. There is a workaround, tough.

The issue

This very simple script breaks when installed via uv/uvx:

#!/usr/bin/env python
''' Module docstring '''
from __future__ import print_function

because uv/uvx turns it into:

#!/bin/sh
'''exec' "$(dirname -- "$(realpath -- "$0")")"/'python' "$0" "$@"
' '''
''' Module docstring '''
from __future__ import print_function

which triggers SyntaxError: from __future__ imports must occur at the beginning of the file. Module strings are exempt from this parser rule, so the original script would execute without any issues. But now after uv/uvx injected additional polygot code at the top, there are now two statements in front of the future-import and only the first one counts as a module docstring.

The workaround

If you are maintaining a module or tool affected by this, there is a workaround: Move the from __future__ import ... line above your own module docstring. It must be the very first non-comment statement in the file. This way, the string injected by uv/uvx will count as the new module-docstring and not as a forbidden statements before the future-import. As long as there is only one string before the future-import, the parser won't complain.

This works:

#!/usr/bin/env python
from __future__ import print_function
''' module docstring '''

because uv/uvx turns it into:

#!/bin/sh
'''exec' "$(dirname -- "$(realpath -- "$0")")"/'python' "$0" "$@"
' '''
from __future__ import print_function
''' module docstring '''

which is ugly and may confuse help(), IDEs and documentation generators, but at least it is not a SyntaxError.

Hope this helps someone.