I have a situation where a legacy python2 application has code that I cannot change. However it has an upgrade mechanism, where it checks if a provided file is of a newer version than itself, and replaces itself by that newer file. However, it does import said upgrade file to check if the version is newer.
Think of it like so:
# Pseudo-code "upgrade" mechanism in Python2 version:
# ...
if upgrade_file:
maybe_new_version = import_file(upgrade_file)
if maybe_new_version.__version__ > current_version:
replace_self_with(upgrade_file)
# ...
now for the upgrade file, which is python3, I’d like to do something like this:
# start python2 compatible code
import sys
__version__ = "0.3.0" # this will trigger the self-update
if sys.version_info[0] < 3:
stop_importing_file_here() # Ignore the rest of the file! Avoid Python 2 running into Python3-syntax in the rest of the file
# end python2 compatible code
... # rest of the file is the new python 3 version
Now is there some mechanism I can make the python2 import system ignore the rest of the file, i.e. the part of the code that is python3 and not python2 compatible? I.e. I’m searching for a real world thing to replace stop_importing_file_here() in the example code. And I cannot change the code in the older version or get multiple files to the system.
Calling sys.exit(0) would end the parent process doing the importing, scratch that.
Python first parses code, triggering syntax errors even in unreachable code, before executing it in a second pass. So I don’t think this is solvable at runtime within the same file.
Can you put the Python 3 code in another dependency, which is only imported if sys.version_info >= (3,):?
That won’t work, as all syntax is first parsed by Python, even if that code is unreachable at run-time, and only executed afterwards on a second pass. Even if it did work, none of the Python 3 only stuff would be importable, as it’s only within the closure of process() (the function body’s scope).
process.py
import sys
def check_version():
return sys.version_info[0] >= 3
def process():
f'other imports and logics for Python 3. {sys.version_info=}'
if check_version():
process()
c:\...\>py -2.7 process.py
File "process.py", line 7
f'other imports and logics for Python 3. {sys.version_info=}'
^
SyntaxError: invalid syntax
Oh, I thought you were picking on the syntax.
Yes, I was thinking of discussing this further.
In this module can leave only the basic logic of imports and transfer all potentially problematic code to modules that will be loaded from this.
That’s, it will again be a kind of proxy implementation to the main python3 code.
In general, can create separate logic on proxy modules, which will be full-fledged classes and objects. There is such a point in my security concept. But the implementation will be much more difficult.
And then can create the concept of lazy code loading. Although this has already been implemented in importlib through LazyLoader. By the way, can really try to apply it in this case.
You can have a python2 file and a python3 file and import that based on whether 2 or 3 is running.
import sys
__version__ = "0.3.0" # this will trigger the self-update
if sys.version_info[0] < 3:
from python2_file import *
else:
from python 3_file import *
So far the “make it a string and then eval” suggestion seems to be the only one working, though its not pretty. Thanks for that in any case, it’s probably good enough. Another solution would be to comment out the python3 code and then extract that comment and eval it conditionally if on py3.
If I could get two files on the machine, I’d probably do:
import sys
__version__ = "0.3.0"
print("I'm a shim for the py3 version")
if sys.version_info >= (3,):
import os.path
dir_ = os.path.dirname(os.path.realpath(__file__))
file_ = os.path.join(dir_, "py3version.py")
os.exeve("python3", file_, sys.argv[1:])
You are still failing to understand the constraints OP has: they can’t freely load files onto the server, they only get one python file.
@OP if you ever need more files, you can do something similar to ensurepip: base64 encoded zip archive, unpacked on demand into a temp file location and imported.
It is unclear why there is a restriction on a single file.
Yes, you’re right. The module zipimport has been in existence since version 2.3.
It seems like this is the best variant.
By the way, a question. And are there any other standard modules besides them where loaders are implemented?