Условно сопоставленный модуль импорта - PullRequest
0 голосов
/ 01 июля 2018

В настоящее время я работаю над абстрактным набором модулей для Python 2.7, который я отправлю как пакет python:

 myabstractpkg
   - abstract
      - core
       - logging
       - ...
     - nodes
     - ...

Модули будут реализованы в совершенно другом наборе пакетов:

 myimppkg
   - implementation
     - core
       - logging
       - ...
     - nodes
     - ...

Однако во время выполнения я хочу всегда выполнять импорт, как это, в моих инструментах, использующих реализованные модули:

from myabstractpkg.api import nodes
from myabstractpkg.api.core.logging import Logger

Таким образом, разработчик всегда импортирует данные из «виртуального» модуля API, который затем решает, куда на самом деле указать импортера.

Я знаю, что смогу как-то взломать его, изменив dict модулей:

from myimppkg import implementation
sys.modules["myabstractpkg.api"] = implementation

или делать хитрый импорт всего в __init__.py из myabstractpackage.api, но мне это кажется немного хрупким.

Интересно, есть ли у вас, ребята, какой-нибудь вклад в то, как лучше всего это сделать. Я мог бы быть на самом деле ужасным путем со всей этой концепцией переназначения модулей, так что если у вас, ребята, есть какие-нибудь более умные, более питонские решения для моей абстракции API, реализации, подхода к использованию, я хотел бы услышать их.

1 Ответ

0 голосов
/ 02 июля 2018

Я считаю, что вам лучше всего использовать возможности entry_points из setuptools. Таким образом, в файле setup.py одной из ваших конкретных реализаций вы должны определить entry_points следующим образом:

setup(
    name="concrete_extension"
    entry_points={
        "abstract_pkg_extensions": [
            "concrete = concrete_extension"
        ]
    }
}

Тогда у вас может быть модуль расширения в вашем абстрактном пакете, который делает что-то вроде этого:

import pkg_resources
import os
import sys

from . import default

extensions = { "default": default }
extensions.update({e.name: e.load() for e in 
pkg_resources.iter_entry_points("my_pkg_extensions")})

current_implementation_name = None
current_implementation = None

def set_implementation(name):
    global current_implementation_name, current_implementation
    try:
        current_implementation = extensions[name]
        current_implementation_name = name

        # allow imports like from foo.current_implementation.bar import baz
        sys.modules["{}.current_implementation".format(__name__)] = current_implementation
        # here is where you would do any additional stuff
        # -- e.g. --
        # from . import logger
        # logger.Logger = current_implementation.Logger
    except KeyError:
        raise NotImplementedError("No implementation for: {}".format(name))

set_implementation(os.environ.get("CURRENT_IMPLEMENTATION_NAME", "default"))

Затем вы можете использовать ext.current_implementation для доступа к текущей реализации и либо установить свою реализацию в среде программы до импорта, либо вы можете явно вызвать set_implementation в своем коде перед импортом любых субмодулей, которые используют ext.current_implementation.

Подробнее о записи sys.modules и о том, как она работает, см. этот вопрос .

...