Я пытаюсь создать библиотеку из проекта Python, которым я не владею. Проект имеет следующую структуру каталогов:
.
├── MANIFEST.in
├── pyproject.toml
└── src
├── all.py
├── the.py
└── sources.py
В pyproject.toml
у меня есть:
[tool.setuptools]
packages = ["mypkg"]
[tool.setuptools.package-dir]
mypkg = "src"
Проблема, с которой я столкнулся, заключается в том, что когда я собираю и устанавливаю этот пакет, я не могу его использовать, потому что автор импортирует вещи без префикса mypkg
в различные исходные файлы.
F.ex. в all.py
from the import SomeThing
Поскольку у меня нет пакета, я не могу модифицировать все исходники, но я все же хочу создать из него библиотеку, просто добавив MANIFEST.in
и pyproject.toml
.
Можно ли как-то проинструктировать setuptools собрать пакет, который не будет засорять site-packages
всеми исходниками, но при этом позволит их импортировать без префикса mypkg
?
Спасибо @metatoaster! Если вы дадите ответ как ответ, я могу отметить его как принятый.
Это невозможно без добавления пользовательского хука импорта с пакетом. Хук принимает форму модуля, который поставляется с пакетом, и перед использованием его необходимо импортировать из вашего модуля (например, в src/all.py
).
src/mypkgimp.py
import sys
import importlib
class MyPkgLoader(importlib.abc.Loader):
def find_spec(self, name, path=None, target=None):
# update the list with modules that should be treated special
if name in ['sources', 'the']:
return importlib.util.spec_from_loader(name, self)
return None
def create_module(self, spec):
# Uncomment if "normal" imports should have precedence
# try:
# sys.meta_path = [x for x in sys.meta_path[:] if x is not self]
# return importlib.import_module(spec.name)
# except ImportError:
# pass
# finally:
# sys.meta_path = [self] + sys.meta_path
# Otherwise, this will unconditionally shadow normal imports
module = importlib.import_module('.' + spec.name, 'mypkg')
# Final step: inject the module to the "shortened" name
sys.modules[spec.name] = module
return module
def exec_module(self, module):
pass
if not hasattr(sys, 'frozen'):
sys.meta_path = [MyPkgLoader()] + sys.meta_path
Да, вышеприведенное использует разные методы, описанные в потоке , на который я ссылался ранее, поскольку importlib устарела в Python 3.10, обратитесь к документации за подробностями.
В любом случае, для демонстрации поместите в модули несколько фиктивных классов:
src/the.py
class SomeThing: ...
src/sources.py
class Source: ...
Теперь измените src/all.py
, чтобы иметь следующее:
import mypkg.mypkgimp
from the import SomeThing
Пример использования:
>>> from sources import Source
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'sources'
>>> from mypkg import all
>>> all.SomeThing
<class 'mypkg.the.SomeThing'>
>>> from sources import Source
>>> Source
<class 'mypkg.sources.Source'>
>>> from sources import Error
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: cannot import name 'Error' from 'mypkg.sources' (/tmp/mypkg/src/sources.py)
Обратите внимание, что изначально импорт не работал, но после того, как mypkg.all
был импортирован, импорт sources
теперь работает глобально. Следовательно, может потребоваться осторожность, чтобы не скрывать «настоящий» импорт, и я предоставил пример для импорта с использованием механизма импорта «по умолчанию» [*].
Если вы хотите, чтобы имена модулей выглядели по-другому (то есть без префикса mypkg.
), это будет отдельный вопрос, поскольку код обычно не проверяет собственное имя модуля на функциональность (и неважно, что это фактически показывает, как пространство имен неявно используется - изменение фактического имени больше похоже на перемещение модуля, да, это можно сделать, но немного сложнее, и этот ответ и так достаточно длинный).
[*] «по умолчанию», то есть не включая поведение, введенное этим пользовательским хуком импорта - другие хуки импорта могут делать свои собственные другие странные махинации.
Краткий ответ: нет, они должны быть на уровне соответствующего каталога (т.е.
site-packages
). Более длинный ответ: обойти это ограничение, внедрив хук импорта с вашим пакетом и перехватив все соответствующие импорты, чтобы они разрешались в тот, который установлен вsite-packages/mypkg
месте. Обратите внимание, что использование хуков импорта не обязательно решит проблему глобального загрязнения пространства имен, если только вы не сможете найти способ сделать этот импорт доступным только для тех модулей изmypkg
.