Пользовательский пакет с ошибкой инициализации зависимости: ModuleNotFoundError или ImportError

#python #python-3.x #setuptools #python-packaging

#python #python-3.x #setuptools #python-упаковка

Вопрос:

Я создаю пользовательский пакет, который имеет следующую структуру:

 test_package
    │   README.md
    │   setup.py
    │
    ├───my_package
    │       my_package.py
    │       __init__.py
    │
    └───tests
            tests.py
  

Мой пакет зависит от pygdbmi, поэтому я добавил его в список необходимых зависимостей.

Я использую __init__.py для импорта модулей

__init__.py :

 from .my_package import my_class

__version__ = '0.0.1'
__title__ = 'my_package'
  

И мой класс:

my_package.py :

 from pygdbmi.gdbcontroller import GdbController

class my_class:
    def __init__(self):
        print("my_class!!")
        self.gdbmi = GdbController()
  

Проблема в том, что при запуске я python setup.py install clean получаю ModuleNotFoundError :

 python setup.py install clean
Traceback (most recent call last):
  File "setup.py", line 2, in <module>
    import my_package
  File "c:test_packagemy_package__init__.py", line 1, in <module>
    from .my_package import my_class
  File "c:test_packagemy_packagemy_package.py", line 2, in <module>
    from pygdbmi.gdbcontroller import GdbController
ModuleNotFoundError: No module named 'pygdbmi
  

Что очевидно, поскольку __init__ при импорте my_package он прерывается, потому что у меня еще не установлен pygdbmi.

Я попытался исправить это, удалив импорт из __init__.py :

 __version__ = '0.0.1'
__title__ = 'my_package'
  

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

 python tests.py
Traceback (most recent call last):
  File "tests.py", line 3, in <module>
    from my_package import my_class
ImportError: cannot import name 'my_class' from 'my_package' (C:UserslalalalaAppDataLocalContinuumanaconda3-32libsite-packagesmy_package-0.0.1-py3.7.eggmy_package__init__.py)
  

Как мне это исправить? Я бы хотел сохранить __init__ структуру определения версии и т. Д., Поскольку это кажется питоническим способом сделать это.

Большое спасибо!!!

setup.py :

 import setuptools
import my_package

with open("README.md", "r") as fh:
    long_description = fh.read()

setuptools.setup(
    # Project information
    name=my_package.__title__,
    version=my_package.__version__,
    long_description_content_type="text/markdown",
    packages=setuptools.find_packages(),
    install_requires=["pygdbmi"],
    python_requires='>=3.7',
    # Tests
    test_suite='tests'
)
  

my_package.py :

 from pygdbmi.gdbcontroller import GdbController

class my_class:
    def __init__(self):
        print("my_class!!")
        self.gdbmi = GdbController()
  

tests.py :

 import unittest

from my_package import my_class

class some_test(unittest.TestCase):
    def test_constructor(self):
        self.assertIsNotNone(my_class())

if __name__ == '__main__':
    unittest.main()
  

Ответ №1:

Как мне это исправить? Я бы хотел сохранить __init__ структуру определения версии и т. Д., Поскольку это кажется питоническим способом сделать это.

Это не так, имя и версия являются метаданными пакета, которые не входят в исходный код. Это относится к определению вашего пакета, то setup.py есть в вашем случае.

Ваш __init__.py должен работать наоборот и получать информацию от взаимодействия со средой python и его собственной установки:

 from importlib import metadata

# this works, but usually people just write the name as a string here.
# not 100% DRY, but it's not like the package name could ever change
__title__ = __name__
# if you're stuck on python 3.7 or older, importlib-metadata is a 
# third-party package that can be used as a drop-in instead
__version__ = metadata.version(__title__)
  

Наиболее важным выводом должно быть то, что вы никогда не должны импортировать свой код в свой build-script. Это может 1) создать неприятные проблемы с куриным яйцом, когда ваш код не может быть собран, если его более старая версия уже не была установлена, и 2) превратить все ваши зависимости во время выполнения во встроенные зависимости, с чем вы и сталкиваетесь.

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

Комментарии:

1. Большое спасибо! Это очень элегантное решение. Какие метаданные обычно включаются в __init__.py ?

2. У меня лично есть только версия, потому что это единственная, которую я в конечном итоге использую в своем коде.