Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Support two versions of a python package without clients needing to change code

I'm trying to support multiple versions of a python package without impacting client code.

Consider the following repo:

.
|-- client_code.py
`-- lib
    |-- __init__.py
    `-- foo.py

client_code.py:

from lib.foo import f
...
f()

I'd like to leave client_code.py unchanged. I first tried to do something like this:

lib
|-- __init__.py
|-- v1
|   |-- __init__.py
|   `-- foo.py
`-- v2
    |-- __init__.py
    `-- foo.py

lib/__init__.py:

import os

if os.environ.get("USE_V2", "0") == "0": # Or some other runtime check
    from .v1 import foo
else:
    from .v2 import foo

But the client code fails with the following error:

Traceback (most recent call last):
  File "client_code.py", line 1, in <module>
    from lib.foo import f
ImportError: No module named foo

I'm aware that the following options would work, but they would require clients changing code:

if os.environ.get("USE_V2", "0") == "0":
    from lib.v1.foo import f
else:
    from lib.v2.foo import f

f()
if os.environ.get("USE_V2", "0") == "0":
    import lib.v1.foo as foo
else:
    import lib.v2.foo as foo

foo.f()

Is something like this possible?

A more general version of the question is here: Support two versions of a python package without clients needing to change code

like image 567
JKD Avatar asked Oct 26 '22 12:10

JKD


1 Answers

I'm not sure it's the most elegant, but this seems to work.

├── client.py
└── lib
    ├── __init__.py
    ├── foo.py
    ├── v1
    │   └── foo.py
    └── v2
        └── foo.py

foo.py

import os
if os.environ.get("USE_V2", "0") == "0":
    from lib.v1.foo import *
else:
    from lib.v2.foo import *

v1/foo.py

def f():
    print("I'm v1.f")

v2/foo.py

def f():
    print("I'm v2.f")

client.py

from lib.foo import f

f()

Running output:

$ env | grep USE_V2
USE_V2=1
$ python client.py
I'm v2.f
$ unset USE_V2
$ python client.py
I'm v1.f

Having the actual foo.py import * looks bad, but it's just the lazy approach. Given a v1 and v2 contents that were somewhat different, you could have foo.py adapt the imports to present an uniform API in both cases. Or you could say prepare a functools.partial version of a V1 function that doesn't exist in V2 anymore.

__init__.py is empty and doesn't even need to exist under Python 3.

like image 158
JL Peyret Avatar answered Nov 01 '22 11:11

JL Peyret