Error installing Python Plugin: ModuleNotFoundError: No module named <module>

Error installing Python Plugin: ModuleNotFoundError: No module named <module>

我正在尝试使用 Poetry 为 Saleor 构建一个 Python 插件,但在 Saleor 上安装该插件时遇到问题。我 运行 poetry add ../social_auth 在 saleor 上安装插件并且它成功了但是当我尝试 运行 Saleor 我得到这个错误:

No module named 'social_auth'
File
"/usr/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/apps/config.py",
line 224, in create
    import_module(entry)   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/apps/registry.py",
line 91, in populate
    app_config = AppConfig.create(entry)   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/__init__.py",
line 24, in setup
    apps.populate(settings.INSTALLED_APPS)   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/utils/autoreload.py",
line 64, in wrapper
    fn(*args, **kwargs)   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/core/management/__init__.py",
line 375, in execute
    autoreload.check_errors(django.setup)()   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/utils/autoreload.py",
line 87, in raise_last_exception
    raise _exception[1]   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/core/management/commands/runserver.py",
line 110, in inner_run
    autoreload.raise_last_exception()   File "/home/ton/.cache/pypoetry/virtualenvs/saleor-nXZtbKPR-py3.9/lib/python3.9/site-packages/django/utils/autoreload.py",
line 64, in wrapper
    fn(*args, **kwargs)   File "/usr/lib/python3.9/threading.py", line 910, in run
    self._target(*self._args, **self._kwargs)   File "/usr/lib/python3.9/threading.py", line 973, in _bootstrap_inner
    self.run()   File "/usr/lib/python3.9/threading.py", line 930, in _bootstrap
    self._bootstrap_inner()

但我检查了一下,模块在 settings.INSTALLED_APPSsettings.PLUGINS 中并列在 pip list...

插件结构如下:

├── __init__.py
├── poetry.lock
├── pyproject.toml
└── social_auth
    ├── __init__.py
    └── plugin.py

1 directory, 5 files

pyproject.toml 文件:

[tool.poetry]
name = "social-auth"
version = "0.1.0"
description = "Social auth for saleor"
authors = ["Wellington Zenon <wellington.zenon@gmail.com>"]
packages = [
    { include = "plugin.py", from="social_auth"}
]

[tool.poetry.dependencies]
python = "^3.9"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry.plugins."saleor.plugins"]
"social_auth" = "social_auth.plugin:SocialLoginPlugin"

plugin.py 文件:

from copy import copy, deepcopy
from dataclasses import dataclass
from decimal import Decimal
from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Optional, Tuple, Union

from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
from django_countries.fields import Country
from prices import Money, TaxedMoney
from saleor.plugins.base_plugin import BasePlugin

from ..models import PluginConfiguration

if TYPE_CHECKING:
    # flake8: noqa
    from account.models import Address, User
    from channel.models import Channel

PluginConfigurationType = List[dict]
NoneType = type(None)


class ConfigurationTypeField:
    STRING = "String"
    MULTILINE = "Multiline"
    BOOLEAN = "Boolean"
    SECRET = "Secret"
    SECRET_MULTILINE = "SecretMultiline"
    PASSWORD = "Password"
    OUTPUT = "OUTPUT"
    CHOICES = [
        (STRING, "Field is a String"),
        (MULTILINE, "Field is a Multiline"),
        (BOOLEAN, "Field is a Boolean"),
        (SECRET, "Field is a Secret"),
        (PASSWORD, "Field is a Password"),
        (SECRET_MULTILINE, "Field is a Secret multiline"),
        (OUTPUT, "Field is a read only"),
    ]


@dataclass
class ExternalAccessTokens:
    token: Optional[str] = None
    refresh_token: Optional[str] = None
    csrf_token: Optional[str] = None
    user: Optional["User"] = None


class SocialLoginPlugin(BasePlugin):
    """Abstract class for storing all methods available for any plugin.

    All methods take previous_value parameter.
    previous_value contains a value calculated by the previous plugin in the queue.
    If the plugin is first, it will use default value calculated by the manager.
    """

    PLUGIN_NAME = "Social Authentication Plugin"
    PLUGIN_ID = "plugin.socialauth"
    PLUGIN_DESCRIPTION = "A plugin for social authentication"
    CONFIG_STRUCTURE = {
        "key": {
            "type": ConfigurationTypeField.STRING,
            "help_text": "Provide the social authentication key from the authetication provider (i.e. Facebook)",
            "label": "Authentication Provider Key",
        },
        "secret": {
            "type": ConfigurationTypeField.SECRET,
            "help_text": "Provide the social authentication secret from the authetication provider (i.e. Facebook)",
            "label": "Authentication Provider Secret",
        },
    }
    CONFIGURATION_PER_CHANNEL = False
    DEFAULT_CONFIGURATION = []
    DEFAULT_ACTIVE = False

    @classmethod
    def check_plugin_id(cls, plugin_id: str) -> bool:
        """Check if given plugin_id matches with the PLUGIN_ID of this plugin."""
        return cls.PLUGIN_ID == plugin_id

    def __init__(
        self,
        *,
        configuration: PluginConfigurationType,
        active: bool,
        channel: Optional["Channel"] = None
    ):
        self.configuration = self.get_plugin_configuration(configuration)
        self.active = active
        self.channel = channel

    def __str__(self):
        return self.PLUGIN_NAME

    #  Handle authentication request.
    #
    #  Overwrite this method if the plugin handles authentication flow.
    def external_authentication_url(self, payload: dict, request: WSGIRequest) -> dict:
        print(payload)
        return {"payload": payload}

    #  Handle authentication request responsible for obtaining access tokens.
    #
    #  Overwrite this method if the plugin handles authentication flow.

    def external_obtain_access_tokens(
        self, payload: dict, request: WSGIRequest
    ) -> ExternalAccessTokens:
        pass

    #  Authenticate user which should be assigned to the request.
    #
    #  Overwrite this method if the plugin handles authentication flow.

    def authenticate_user(
        self, request: WSGIRequest, previous_value: Any
    ) -> Union["User", NoneType]:
        pass

    #  Handle logout request.
    #
    #  Overwrite this method if the plugin handles logout flow.
    external_logout: Callable[[dict], Any]

    #  Handle authentication refresh request.
    #
    #  Overwrite this method if the plugin handles authentication flow and supports
    #  refreshing the access.
    external_refresh: Callable[[dict, WSGIRequest], ExternalAccessTokens]

    #  Verify the provided authentication data.
    #
    #  Overwrite this method if the plugin should validate the authentication data.
    external_verify: Callable[[dict, WSGIRequest], Tuple[Union["User", NoneType], dict]]

    get_client_token: Callable[[Any, Any], Any]

    #  Handle received http request.
    #
    #  Overwrite this method if the plugin expects the incoming requests.
    webhook: Callable[[WSGIRequest, str, Any], HttpResponse]

我该如何解决这个问题?

问题是 pyhton 找不到插件源,因为它不在 PYTHONPATH 中,我不得不使用此命令将它添加到 PYTHONPATH 环境变量中:

PYTHONPATH=path/to/plugin