2025, Sep 30 13:34
CherryPy में _cp_dispatch से कस्टम REST URL रूटिंग: हैंडलर सीधे लौटाएँ
CherryPy के _cp_dispatch से कस्टम REST URL रूटिंग सही करें: vpath न बदलें, लक्षित हैंडलर सीधे लौटाएँ, पैरामीटर साफ सेट करें और गलत पथों पर 404 पाएं। कोड उदाहरण सहित.
CherryPy का _cp_dispatch आपको URL रूटिंग को अपनी ज़रूरत के हिसाब से मोड़ने के लिए एक लो-लेवल हुक देता है। जब आपको साफ-सुथरे, REST-शैली के पथ चाहिए जो मेथड नामों से एक-से-एक न मिलते हों, तब यह बहुत उपयोगी है। लेकिन एक पेच है: अगर आप पाथ सेगमेंट्स को बिना सोचे-समझे बदलते रहें और self ही लौटाते रहें, तो CherryPy इसे ऐसे ट्रैवर्स कर सकता है मानो सब-ऑब्जेक्ट्स मौजूद हों, और आपका हैंडलर कभी कॉल ही न हो। नीचे इस समस्या और उसके हल का संक्षिप्त मार्गदर्शन है।
समस्या सेटअप
लक्ष्य सीधा है: /batch/ को एक खास मेथड से जोड़ना, और /batch/{id}/product/ या /batch/{id}/product/{sku} को दूसरे से। बाकी सभी पथों पर 404 मिलना चाहिए। शुरुआती कोशिश vpath को फिर से लिखती है और self लौटाती है, यह उम्मीद करते हुए कि CherryPy बदले हुए पथ पर दोबारा रेज़ोल्यूशन शुरू करेगा।
import cherrypy
class GoodsApi(object):
    def __init__(self, data_store):
        self.data_store = data_store
    def _cp_dispatch(self, vpath):
        # /batch/
        if len(vpath) == 1 and vpath[0] == "batch":
            vpath[0] = "batch_entry"
            return self
        # /batch/{id}/product/
        if len(vpath) == 3 and vpath[0] == "batch":
            vpath.pop(0)  # "batch" हटाएँ
            cherrypy.request.params["batch_id"] = vpath.pop(0)
            return self
        # /batch/{id}/product/{sku}
        if len(vpath) == 4 and vpath[0] == "batch" and vpath[2] == "product":
            vpath.pop(0)  # "batch" हटाएँ
            cherrypy.request.params["batch_id"] = vpath.pop(0)
            vpath.pop(0)  # "product" हटाएँ
            cherrypy.request.params["sku"] = vpath[0]
            vpath[0] = "item"
            return self
        # डिफ़ॉल्ट ट्रैवर्सल
        return self
    @cherrypy.expose
    @cherrypy.tools.json_out()
    def batch_entry(self):
        # इसके लिए बनाया गया: /batch/
        pass
    @cherrypy.expose
    @cherrypy.tools.json_out()
    def item(self, batch_id, sku=None):
        # इसके लिए बनाया गया: /batch/{id}/product[/ {sku}]
        pass
असल में क्या हो रहा है
CherryPy पहले पाथ से मेल खाते एक्सपोज़्ड हैंडलर ढूंढता है। यदि ऐसा कोई नहीं मिलता, तो वह शेष सेगमेंट्स के साथ _cp_dispatch(self, vpath) को बुलाता है। इस हुक के भीतर आपके पास दो वैध रणनीतियाँ हैं: vpath को बदलें और traversal जारी रखने के लिए self लौटाएँ, या फिर सीधे किसी बाउंड मेथड को लौटाएँ ताकि रेज़ोल्यूशन तुरंत समाप्त हो जाए। अगर हुक None लौटाता है, तो CherryPy 404 देता है।
vpath[0] = "batch_entry" जैसा कोई सेगमेंट बदलना CherryPy को मेथड सीधे कॉल नहीं करवाता; वह इसे एक और ट्रैवर्सल स्टेप की तरह ही मानता है। इसी वजह से शुरुआती कोशिश में /batch/ वाला हैंडलर कभी ट्रिगर नहीं होता। एक और बारीकी यह है कि vpath में बचे हुए सेगमेंट तर्क-रेज़ोल्यूशन में घुस सकते हैं, जिससे “batch_id के लिए multiple values” जैसी दिक्कतें आती हैं। _cp_dispatch के भीतर पैरामीटर निकालना और यह सुनिश्चित करना कि कोई बचे-खुचे सेगमेंट न रहें, इस भ्रम से बचाता है।
समाधान: हैंडलर को सीधे लौटाएँ
बेहतर तरीका यह है कि पाथ को फिर से लिखना बंद करें और _cp_dispatch से सीधे लक्षित हैंडलर लौटाएँ। जरूरत के मुताबिक cherrypy.request.params भरें, जिन पाथ हिस्सों का इस्तेमाल किया है उन्हें खपा दें, और CherryPy को वही मेथड कॉल करने दें जो आपने लौटाया है। जो पथ मेल न खाएँ, उनके लिए None लौटाएँ ताकि 404 मिले।
import cherrypy
class GoodsApi(object):
    def __init__(self, data_store):
        self.data_store = data_store
    def _cp_dispatch(self, vpath):
        # /batch/
        if len(vpath) == 1 and vpath[0] == "batch":
            return getattr(self, "batch_entry")
        # /batch/{id}/product/
        if len(vpath) == 3 and vpath[0] == "batch":
            vpath.pop(0)  # "batch" हटाएँ
            cherrypy.request.params["batch_id"] = vpath.pop(0)
            return getattr(self, "item")
        # /batch/{id}/product/{sku}
        if len(vpath) == 4 and vpath[0] == "batch" and vpath[2] == "product":
            vpath.pop(0)  # "batch" हटाएँ
            cherrypy.request.params["batch_id"] = vpath.pop(0)
            vpath.pop(0)  # "product" हटाएँ
            cherrypy.request.params["sku"] = vpath.pop(0)
            return getattr(self, "item")
        # बाकी सब → 404
        return None
    @cherrypy.expose
    @cherrypy.tools.json_out()
    def batch_entry(self):
        return {"msg": "batch list or create batch here"}
    @cherrypy.expose
    @cherrypy.tools.json_out()
    def item(self, batch_id, sku=None):
        return {"batch": batch_id, "sku": sku}
if __name__ == "__main__":
    cherrypy.quickstart(GoodsApi({}), "/rest-products")
यह वायरिंग इच्छित व्यवहार देती है। /rest-products/batch/ पर आने वाले अनुरोधों में GET और POST दोनों के लिए batch_entry() कॉल होता है। /rest-products/batch/123/product/ पर item(batch_id=123) चलता है। /rest-products/batch/123/product/ABC123 पर item(batch_id=123, sku="ABC123") चलता है। बाकी किसी भी पथ पर 404 मिलता है।
यह क्यों मायने रखता है
कस्टम REST रूटिंग अक्सर URL के स्वरूप और हैंडलर सिग्नेचर के बीच की धुंधली जगह में आती है। यह समझना कि _cp_dispatch या तो बदले हुए vpath के साथ traversal जारी रखता है, या हैंडलर लौटाकर शॉर्ट-सर्किट कर देता है, अस्पष्ट रेज़ोल्यूशन से बचाता है। यह सूक्ष्म पैरामीटर टकरावों को भी रोकता है, जैसे एक ही आर्ग्युमेंट के लिए कई मान तब मिलना जब vpath के बचे हिस्से और request.params दोनों उसे प्रदान करने की कोशिश करें।
मुख्य बातें
URL से बस वही हिस्से लेने के लिए _cp_dispatch का उपयोग करें जिनकी जरूरत है, उन्हें cherrypy.request.params में रखें, और या तो हैंडलर लौटाएँ या None। पाथ सेगमेंट्स का नाम बदलकर मेथड नामों का आभास देने और self लौटाने से बचें, क्योंकि CherryPy उन्हें सीधे मेथड लक्ष्य के बजाय सामान्य ट्रैवर्सल स्टेप मानता है। जब लक्ष्य स्पष्टता और मेल न खाने वाले पथों पर सख्त 404 हो, तो हैंडलर को स्पष्ट रूप से लौटाना रूटिंग को निर्धारक और समझने में आसान बनाए रखता है।
यह लेख StackOverflow पर एक प्रश्न, जिसे Bart Friederichs ने पूछा था, और Madhav Singh Rana के उत्तर पर आधारित है।