2025, Sep 27 15:32
VS Code और Pyright में proj.foo नेमस्पेस इम्पोर्ट कैसे चलाएँ
VS Code में Pyright को proj.foo इम्पोर्ट पहचानने का मार्गदर्शक: Python editable install और PEP 420 symlink से रनटाइम व स्टैटिक विश्लेषण दोनों को भरोसेमंद बनाएं.
जब कोई सबपैकेज एक बड़े निजी मोनोरेपो के भीतर भी रहता है और अपने अलग स्टैंडअलोन रिपॉजिटरी में भी, तब proj.foo.bar जैसे एबसोल्यूट इम्पोर्ट दो अलग माहौल में भी काम करने चाहिए। Python रनटाइम को अक्सर editable installs के जरिए मनाया जा सकता है, लेकिन VS Code में Pyright जैसे स्टैटिक एनालाइज़र तब तक शिकायत करेंगे जब तक डिस्क पर मौजूद संरचना proj/foo/... जैसी न दिखे।
समस्या की रूपरेखा
मान लीजिए डेवलपर-फेसिंग रिपॉजिटरी में कोड src/proj/foo के तहत है, लेकिन इंटर्न केवल वह स्टैंडअलोन रिपॉजिटरी क्लोन कर सकता है जिसमें सिर्फ foo सबट्री है। इस स्टैंडअलोन ट्री के भीतर भी मॉड्यूल proj.foo.bar जैसे पूरे नेमस्पेस से इम्पोर्ट करते हैं, और आप फाइलें हिला-डुला नहीं सकते, डमी स्टब्स नहीं जोड़ सकते, न ही उन इम्पोर्ट्स को बदल सकते हैं। लक्ष्य यह है कि इंटरप्रेटर और Pyright दोनों proj.foo.* को इस तरह resolve करें, मानो स्टैंडअलोन फोल्डर मूल पैरेंट पैकेज का ही हिस्सा हो।
न्यूनतम उदाहरण
स्टैंडअलोन क्लोन में, यह संरचना मान लें:
~/foo-repo/
  └── bar/
      ├── __init__.py
      ├── types.py
      └── jim.py
यह रहा वह पैकेज कोड जो proj.foo.* नेमस्पेस पर निर्भर है। नाम बदले गए हैं, पर व्यवहार समान है।
~/foo-repo/bar/__init__.py
from proj.foo.bar.types import Payload
__all__ = ["Payload"]
~/foo-repo/bar/types.py
class Payload:
    pass
~/foo-repo/bar/jim.py
import proj.foo.bar as foopkg
print(foopkg.Payload)
अगर आप एक नए venv में python bar/jim.py चलाते हैं, तो रनटाइम और Pyright दोनों proj.foo.* नहीं ढूंढ़ पाएंगे, क्योंकि स्टैंडअलोन रिपॉजिटरी proj/foo जैसी डायरेक्टरी संरचना प्रस्तुत नहीं करती।
यह क्यों विफल होता है
Setuptools package-dir और editable installs के जरिए पैकेज नामों को मनचाही सोर्स डायरेक्टरी से मैप कर सकता है। यह तरकीब इंटरप्रेटर को खुश कर देती है, क्योंकि editable install द्वारा दिया गया import hook proj.foo को आपके सोर्स तक रीडायरेक्ट कर देता है। लेकिन Pyright न तो import hooks चलाता है और न ही पैकेजिंग मेटाडेटा देखता है। वह इम्पोर्ट्स को सीधे फाइलसिस्टम लेआउट (या स्टब्स) से रिजॉल्व करता है और sys.path पर पैकेज के डॉटेड पाथ से मेल खाते असली डायरेक्टरी देखने की अपेक्षा करता है। जहां कहीं Pyright क्रॉल कर सके, अगर कोई वास्तविक proj/foo हाइअरार्की मौजूद नहीं है, तो IDE में proj.foo.* रिजॉल्व नहीं होगा, चाहे रनटाइम में कोड चल भी जाए।
दो काम करने वाले न्यूनतम रास्ते
पहला कदम यह सुनिश्चित करना है कि इंटरप्रेटर आपका पैकेज उसी जगह से चला सके। दूसरा कदम Pyright को ऐसी डायरेक्टरी दृश्यता देना है जो proj/foo/... जैसी दिखे।
नीचे दिया गया editable-install तरीका इंटरप्रेटर की जरूरतें साफ़-सुथरे ढंग से संभालता है। Pyright को संतुष्ट करने के लिए, फिर आप एक symlink के जरिए अपेक्षित नेमस्पेस आकृति को वास्तविक रूप देते हैं और उसे वर्शन कंट्रोल से बाहर रखते हैं।
विकल्प 1: स्पष्ट मैपिंग के साथ editable install (PEP 660)
इसे ~/foo-repo/pyproject.toml में रखें। यह पैकेज को proj.foo नेमस्पेस के तहत घोषित करता है और editable रहते हुए उसे आपके मौजूदा लेआउट से मैप करता है।
[project]
name = "proj.foo"
version = "0.0.0"
requires-python = ">=3.11"
[build-system]
requires = ["setuptools>=70", "wheel", "editables>=0.5"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["proj.foo", "proj.foo.bar"]
package-dir = { "proj.foo" = ".", "proj.foo.bar" = "bar" }
include-package-data = true
फिर वातावरण बनाएं और सक्रिय करें, और मॉड्यूल को -m के साथ चलाएं; इससे स्क्रिप्ट-पाथ से जुड़ी उलझनें नहीं आतीं और पैकेज इम्पोर्ट सेमांटिक्स लागू होते हैं।
uv sync
source .venv/bin/activate
python -m proj.foo.bar.jim
इस चरण पर रनटाइम काम करेगा क्योंकि editable install आवश्यक import hook उपलब्ध कराता है। लेकिन Pyright केवल इस मेटाडेटा के आधार पर proj.foo.* रिजॉल्व नहीं करेगा, क्योंकि वह उन hooks को चलाता ही नहीं।
विकल्प 2: Pyright को अपेक्षित डायरेक्टरी संरचना दें (PEP 420 + symlink)
फाइलें हटाए-ब्लॉके बिना या स्टब्स जोड़े बिना, अपने कोड की ओर इशारा करती एक वास्तविक proj/foo डायरेक्टरी प्रदान करें। कुछ डायरेक्टरी जोड़कर और एक symlink बनाकर एक implicit namespace package लेआउट तैयार करें। इस लेआउट को अनट्रैक्ड रखें ताकि मूल रिपॉजिटरी संरचना अप्रभावित रहे।
cd ~/foo-repo
mkdir -p proj/foo
ln -s ../../bar proj/foo/bar
proj/ को अपनी .gitignore में जोड़ दें ताकि ये सहायक पथ स्टैंडअलोन वर्किंग कॉपी से बाहर न जाएं। अब Pyright डिस्क पर proj/foo/bar देख सकता है और proj.foo.bar तथा उसके सबमॉड्यूल्स को रिजॉल्व कर सकता है, जबकि इंटरप्रेटर वही सोर्स लोड करता रहता है।
केवल मेटाडेटा से यह क्यों संभव नहीं
Pyright, और इसी तरह Pylance, इम्पोर्ट्स को इंटरप्रेटर के सर्च पाथ पर मौजूद वास्तविक डायरेक्टरी और फाइलों से रिजॉल्व करते हैं। वे न तो build backends चलाते हैं, न import hooks, और न ही package-dir जैसे पैकेजिंग मेटाडेटा पढ़कर सोर्स ढूंढ़ते हैं। Editable installs के लिए, path-आधारित .pth एंट्रियाँ जो वास्तविक डायरेक्टरी दिखाती हैं, वही एनालाइज़र इंडेक्स करता है। Namespace packages में भी डॉटेड नाम के हर हिस्से का एक ऐसी डायरेक्टरी के रूप में मौजूद होना जरूरी है जो sys.path से पहुँची जा सके। अगर कोई भौतिक proj/foo हाइअरार्की नहीं है, तो स्टैटिक रिजॉल्वर के पास घूमकर देखने के लिए कुछ नहीं बचेगा, इसलिए रनटाइम इम्पोर्ट सफल होने पर भी IDE के लिए proj.foo.* अनजान रहेगा।
यह क्यों मायने रखता है
स्प्लिट-रिपो या सबट्री वर्कफ़्लोज़ में विकास की गति इस बात पर टिकी होती है कि एक ही इम्पोर्ट पाथ अकेलेपन में भी और मोनोरेपो में भी काम करे। केवल पैकेजिंग तरकीबों पर भरोसा करना स्थानीय तौर पर समस्याओं को छिपा सकता है, जबकि IDE आपके मॉड्यूल्स को देख ही नहीं पाता। नेमस्पेस के लिए एक वास्तविक डायरेक्टरी दृश्य बनाकर आप सोर्स इम्पोर्ट्स को छुए बिना और फाइलें बदले बिना, सही रनटाइम व्यवहार और भरोसेमंद स्टैटिक एनालिसिस दोनों हासिल कर लेते हैं।
समापन
इंटरप्रेटर मैपिंग को सरल रखने के लिए editable install का उपयोग करें और पैकेज मॉड्यूल चलाने के लिए python -m को प्राथमिकता दें। फिर, अगर आप चाहते हैं कि Pyright और VS Code proj.foo.* को पूरी तरह रिजॉल्व करें, तो एक proj/foo डायरेक्टरी जोड़ें, उसके भीतर bar का symlink बनाएं, और इस सहायक ढाँचे को वर्शन कंट्रोल में इग्नोर करें। यदि सहायक फाइलसिस्टम वाले कदमों में से कोई भी स्वीकार्य नहीं है, तो मान लें कि Pyright उन इम्पोर्ट्स को रिजॉल्व नहीं करेगा, क्योंकि स्टैटिक एनालिसिस जानबूझकर उसी तक सीमित है जो फाइलसिस्टम उजागर करता है।