2025, Oct 31 03:33
Python wheel इंस्टॉल के बाद import विफल? setuptools पैकेज डिस्कवरी और src लेआउट का सही तरीका
Python wheel इंस्टॉल हुआ, पर import पर ModuleNotFoundError? गाइड बताती है: setuptools पैकेज डिस्कवरी, src लेआउट से समस्या कैसे सुलझे, जल्दी.
जब कोई नया बनाया गया Python wheel बिना त्रुटियों के इंस्टॉल हो जाता है, लेकिन import करने पर ModuleNotFoundError मिलती है, तो समस्या अक्सर आपके रनटाइम में नहीं होती। असल कारण पैकेज का लेआउट और setuptools का पैकेज खोजने का तरीका होता है—यानी वह वितरण में क्या शामिल करे। यहां एक वास्तविक मामले का व्यावहारिक walkthrough है: पैकेज इंस्टॉल था, pip उसे देख रहा था, sys.path भी ठीक लग रहा था, फिर भी Python उसे import नहीं कर पा रहा था।
लक्षण: इंस्टॉल तो है, पर import नहीं होता
पैकेज मानक टूलिंग से बनाया गया:
python3 -m build
python3 -m pip install dist/jbpy-0.0.2.9-py3-none-any.whl
pip ने इंस्टॉलेशन की पुष्टि की, और फाइलें अपेक्षित user site-packages में गईं:
Location: /home/jbang/.local/lib/python3.12/site-packages
Python के paths भी सही दिख रहे थे; उदाहरण के लिए, sys.path में यह शामिल था:
/home/jbang/.local/lib/python3.12/site-packages
लेकिन पैकेज import करने की कोशिश असफल रही:
ModuleNotFoundError: No module named 'jbpy'
Import परीक्षण ने उसी इंटरप्रेटर का इस्तेमाल किया जो शेल का python3 था। यानी समस्या path या venv के मेल न खाने से नहीं थी।
एक न्यूनतम विफल उदाहरण
यहां एक सरल import परीक्षण है जो वही त्रुटि दोहराता है। व्यवहार वही है—बस नाम बाइंडिंग अधिक स्पष्ट कर दी गई है:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import jbpy.logging as log_pkg
if __name__ == "__main__":
    print("Modules imported")
पैकेज को pyproject.toml में इस तरह कॉन्फिगर किया गया था:
[build-system]
requires = ["setuptools >= 77.0.3"]
build-backend = "setuptools.build_meta"
[project]
name = "jbpy"
version = "0.0.2.9"
authors = [{name = "Jens Bang", email = "xxxx@xxxx.xx"}]
description = "A package with utility functions to make my Python life easier."
readme = "README.md"
requires-python = ">=3.6"
license = "BSD-3-Clause"
classifiers = [
    "Programming Language :: Python :: 3",
]
[tool.setuptools.packages.find]
where = ["src"]
include = ["argparse*", "logging*", "json*"]
exclude = ["__*"]
namespaces = false
स्रोत ट्री में Python फाइलें सीधे src के अंदर रखी गई थीं, jbpy नाम का कोई पैकेज डायरेक्टरी नहीं था:
src/
  argparse.py
  general.py
  __init__.py
  json.py
  logging.py
  jbpy.egg-info/
असल में गड़बड़ी क्या थी
setuptools वितरण में कोई भी import होने योग्य कोड जोड़ ही नहीं रहा था। सोर्स डिस्ट्रीब्यूशन के अंदर देखने पर सिर्फ मेटाडेटा और प्रोजेक्ट फाइलें दिखती थीं; Python मॉड्यूल पूरी तरह गायब थे:
$ tar tf dist/jbpy-0.0.2.9.tar.gz
jbpy-0.0.2.9/
jbpy-0.0.2.9/PKG-INFO
jbpy-0.0.2.9/README.md
jbpy-0.0.2.9/pyproject.toml
jbpy-0.0.2.9/setup.cfg
jbpy-0.0.2.9/src/
jbpy-0.0.2.9/src/jbpy.egg-info/
jbpy-0.0.2.9/src/jbpy.egg-info/PKG-INFO
jbpy-0.0.2.9/src/jbpy.egg-info/SOURCES.txt
jbpy-0.0.2.9/src/jbpy.egg-info/dependency_links.txt
jbpy-0.0.2.9/src/jbpy.egg-info/top_level.txt
कारण पैकेज डिस्कवरी सेटिंग्स में था। where = ["src"] निर्देश setuptools को src के अंदर पैकेज ढूंढने को कहता है। लेकिन src में jbpy नाम की कोई पैकेज डायरेक्टरी थी ही नहीं; वहां सिर्फ मॉड्यूल फाइलें थीं। include पैटर्न argparse, logging, json जैसे नामों को लक्षित कर रहे थे—ये मॉड्यूल का वर्णन करते हैं (और stdlib के नामों से टकराते भी हैं), न कि jbpy जैसे शीर्ष-स्तरीय पैकेज का। नतीजतन, शामिल करने लायक कोई पैकेज मैच ही नहीं हुआ।
समाधान
src के भीतर एक वास्तविक पैकेज डायरेक्टरी बनाएं और setuptools को उसे खोजने दें। सभी संबंधित मॉड्यूल्स को src/jbpy के अंदर रखें और डिस्कवरी को इस तरह समायोजित करें कि पैकेज व्यापक रूप से शामिल हों। पुनर्गठन के बाद लेआउट कुछ ऐसा दिखेगा:
src/
  jbpy/
    __init__.py
    argparse.py
    general.py
    json.py
    logging.py
फिर ऐसी पैकेज डिस्कवरी कॉन्फिगरेशन इस्तेमाल करें जो उस पैकेज को सचमुच चुन ले:
[build-system]
requires = ["setuptools >= 77.0.3"]
build-backend = "setuptools.build_meta"
[project]
name = "jbpy"
version = "0.0.2.9"
authors = [{name = "Jens Bang", email = "xxxx@xxxx.xx"}]
description = "A package with utility functions to make my Python life easier."
readme = "README.md"
requires-python = ">=3.6"
license = "BSD-3-Clause"
classifiers = [
    "Programming Language :: Python :: 3",
]
[tool.setuptools.packages.find]
where = ["src"]
include = ["*"]
exclude = []
namespaces = false
पुनः बिल्ड करें और दोबारा इंस्टॉल करें:
python3 -m build
python3 -m pip install dist/jbpy-0.0.2.9-py3-none-any.whl
अब सोर्स डिस्ट्रीब्यूशन में वास्तविक पैकेज और मॉड्यूल मौजूद हैं:
$ tar tf dist/jbpy-0.0.2.9.tar.gz
jbpy-0.0.2.9/
...
jbpy-0.0.2.9/src/jbpy/
jbpy-0.0.2.9/src/jbpy/__init__.py
jbpy-0.0.2.9/src/jbpy/argparse.py
jbpy-0.0.2.9/src/jbpy/general.py
jbpy-0.0.2.9/src/jbpy/json.py
jbpy-0.0.2.9/src/jbpy/logging.py
...
और import अपेक्षा के अनुसार काम करता है:
import jbpy.logging as log_pkg
यह क्यों मायने रखता है
pip किसी पैकेज को इंस्टॉल दिखा सकता है, भले ही wheel या sdist में व्यवहारिक रूप से सिर्फ मेटाडेटा हो। इससे उलझन पैदा होती है: वातावरण ठीक दिखता है, sys.path सही site-packages की ओर इशारा करता है, और pip show इंस्टॉल की पुष्टि करता है—फिर भी import विफल हो जाता है। डिस्ट्रीब्यूशन की सामग्री की जांच करने से असली समस्या जल्दी सामने आती है। इस मामले में, पैकेज डिस्कवरी किसी वास्तविक पैकेज से मेल ही नहीं खा रही थी, क्योंकि कोड सीधे src के नीचे मॉड्यूल के रूप में था और include/exclude पैटर्न ने जरूरी चीज़ों को छांट दिया।
निष्कर्ष
src के नीचे एक शीर्ष-स्तरीय पैकेज डायरेक्टरी वाला सही src लेआउट अपनाएं, और सुनिश्चित करें कि setuptools की पैकेज डिस्कवरी उसी पर इंगित कर रही हो। जब तक कोई ठोस कारण न हो, include पैटर्न को व्यापक रखें। सफल इंस्टॉल के बाद भी यदि ModuleNotFoundError मिले, तो बने हुए sdist या wheel को देखकर पक्का करें कि आपके Python फाइलें उसमें हैं। एक बार src के भीतर पैकेज डायरेक्टरी मौजूद हो और डिस्कवरी उसे शामिल करने के लिए कॉन्फिगर हो, तो wheel आपके कोड को साथ ले जाएगा और imports pip द्वारा दिखाए जा रहे इंस्टॉल से मेल खाएंगे।