简体   繁体   English

添加彼此依赖的python模块作为git子模块

[英]add python modules that depend on each other as git submodules

I have these 2 little modules in 2 separate git repositories: 我在2个独立的git存储库中有这两个小模块:

RepoA/A.py

def foo():
    return "Hello World"

RepoB/B.py

import sys
try:
    import A
except ImportError:
    print("Dependency not available!")
    sys.exit(1)

def bar():
    return A.foo() + " EXTENDED!"

You see that B currently assumes A is importable globally (either installed or in the script-execution directory). 您会看到B当前假设A可全局导入(安装或在脚本执行目录中)。

Both have an empty __init__py at their root directory to make them importable as submodules. 两者在其根目录中都有一个空的__init__py ,以使它们可以作为子模块导入。

Now I have another, bigger repository C that needs B. It has both A and B available as git submodules, so the structure virtually looks like this: 现在我有另一个更大的存储库C需要B.它有A和B两个可用作git子模块,所以结构实际上如下所示:

RepoC
|---utils/
|   |---__init__.py
|   |---RepoA/
|   |   |---__init__.py
|   |   |---A.py
|   |---RepoB/
|       |---__init__.py
|       |---B.py
|---C.py

RepoC/C.py

import utils.B

print(B.bar())

RepoC/utils/__init__.py

# remove the 1-layer indirection through the submodule
from .RepoB import B

Now this prints Dependency not available! 现在打印Dependency not available! , as expected because A isn't globally available, but rather at a location B could never guess (in this case it would need to do from ..RepoA import A ). 正如预期的那样,因为A不是全局可用的,而是在B位置永远无法猜测的位置(在这种情况下,它需要from ..RepoA import A )。 If I make A globally available again by adding it to sys.path it will work: 如果我通过将其添加到sys.path再次使A全局可用它将工作:

RepoC/utils/__init__.py

import os, sys
import inspect

def fake_install(module_path):
    # the submitted module path is relative to the caller's location
    # therefore get that script's file location first
    caller_module = inspect.getmodule(inspect.stack()[1][0])
    caller_dir = caller_module.__file__.rsplit(os.path.sep, 1)[0]
    # build an absolute file path and append it to the system paths
    path = os.path.join(os.path.abspath(caller_dir), *module_path.split("."))
    sys.path.append(path)

fake_install("RepoA")

# remove the 1-layer indirection through the submodule
from .RepoB import B

This feels like a horrendous solution though. 这感觉就像一个可怕的解决方案。

Another idea was to not use git submodules at all, but rather just collect the dependencies as git-links in a requirements.txt, write some setup.exe scripts and let pip actually install them. 另一个想法是根本不使用git子模块,而只是在requirements.txt中收集依赖关系作为git-links,编写一些setup.exe脚本并让pip 实际安装它们。

How can I elegantly overcome this problem? 我怎样才能优雅地克服这个问题? Is there any import trickery that lets me do exactly this? 是否有任何导入技巧可以让我这样做?

As you may have guessed, I think you have two options: either you let A and B be part of C, or you make B and C standalone packages. 您可能已经猜到了,我认为您有两种选择:要么让A和B成为C的一部分,要么制作B和C独立包。

It's the job of pip to "put A somewhere in sys.path", so you might as well let him do this and not do it yourself. 这是pip的工作“把一个放在sys.path中的某个地方”,所以你不妨让他这样做而不是自己动手。 You can use git links in requirements but not in setup.py, so if you have more dependencies than that (D requires B which requires C) and you can't publish those on PyPI you'll likely need a private PyPI server (devpi works well for this). 您可以在需求中使用git链接,但不能在setup.py中使用git链接,因此如果您有更多依赖项(D需要B需要C)并且您无法在PyPI上发布这些依赖项,那么您可能需要一个私有PyPI服务器(devpi)适用于此)。

(I just figured it out myself) (我自己想通了)
This can be solved by removing the extra indirection introduced by the submodule repository's folder. 这可以通过删除子模块库的文件夹引入的额外间接来解决。 You need the currently empty __init__.py files in RepoA and Repo to act as their containing module (here utils ) to give RepoC the ability to make the dependency available there. 您需要RepoARepo当前为空的__init__.py文件作为其包含模块(此处为utils ),以使RepoC能够在其中使用依赖项。

Edit your RepoB/__init__.py to look like this: 编辑您的RepoB/__init__.py ,如下所示:

from .. import *

Then also make your dependency A available in your utils by adding this to the utils' __init__.py : 然后通过将其添加到utils的__init__.py ,使您的utilA中的依赖项A可用:

from .RepoA import A

Now all you need to do is make the import in B.py local: 现在您需要做的就是在本地B.py进行导入:

from . import A

You can also make it work as a global dependency too: 您也可以将其作为全局依赖项运行:

try:
    from . import A
except ImportError:
    import A

Now all the library's user has to do is either: 现在,所有库的用户必须做的是:

  • Globally install the dependency, or 全局安装依赖项,或
  • Make the dependency available in the containing module 使包含模块中的依赖项可用

To make this approach more general I would have all the __init__.py in Submodules' root directories just act as bridges like this: 为了使这种方法更通用,我希望子模块的根目录中的所有__init__.py都像这样的桥接:

RepoA/__init__.py

from .. import *
__all__ = ["A"]

RepoB/__init__.py

from .. import *
__all__ = ["B"]

and do the same, which is removing the indirection, "from the other side". 并做同样的事情,即从另一方面去除间接。 In this case in utils/__init__.py : 在本例中,在utils/__init__.py

from .RepoA import *
from .RepoB import *

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM