[{"data":1,"prerenderedAt":319},["ShallowReactive",2],{"project-45604013":3},{"id":4,"name":5,"line":6,"line_source":7,"title":8,"url":9,"domain":10,"maker":11,"posted_at":12,"year":13,"points":14,"comments":15,"rank_in_year":16,"shown_in_year":17,"top":18,"tier":19,"category":20,"subcategory":21,"format":22,"keywords":23,"reception":29,"sentiment":30,"stance_mix":31,"scored_comments":35,"aspects":36,"strip":43,"post_text":44,"what":45,"said":76,"maker_in_thread":205,"shape":210,"grid":213,"attempts":282,"same_week":283,"maker_shows":39,"maker_hidden":311,"reader_version":312,"card":313},"45604013","Modshim","A new alternative to monkey-patching in Python","title","Show HN: Modshim – A new alternative to monkey-patching in Python","https:\u002F\u002Fgithub.com\u002Fjoouha\u002Fmodshim","github.com","joouha",1760613476,2025,109,30,680,3837,18,"notable","languages","python import hooks","library",[24,25,26,27,28],"python","monkey patching","import hook","module overlay","ast transformation","debated",-0.108,{"positive":32,"neutral":32,"mixed":33,"critical":34},5,1,8,19,{"docs":37,"idea":38,"design":40},{"positive":33,"critical":33},{"positive":39,"critical":32},3,{"positive":41,"critical":42},0,2,[33,39,41,41,32,33,39,33,41,33,41,33,39,41,41,32,39,41,39,33,32,32,39,41,33,41,41,33,32,41],"I've invented a new alternative to forking \u002F vendoring \u002F monkey-patching packages in Python.\n\nIt's a bit like OverlayFS for Python modules - it allows you write modifications for a target module (lower) in a new module (upper), and have these combined in a new virtual module (mount).\n\nIt works by rewriting imports using AST transformations, then running both the lower and upper module's code in the new Python module.\n\nThis prevents polluting the global namespace when monkey-patching, and means if you want to make changes to a third-party package, you don't have to take on the maintenance burde",{"problem":46,"audience":55,"pricing":63,"motivation":64,"time_to_build":63,"stack":68,"openness":74,"stage":75,"team_size":75},{"value":47,"source":48,"evidence":49},"Modifying third-party Python packages without forking, vendoring or global monkey-patching","maker",[50,52],{"id":51},"post",{"id":53,"by":11,"text":54},"45673083","Let me explain what inspired me to create modshim: I've written a Jupyter client for the terminal (euporie), for which I've had to employ monkey-patching of various third-party…",{"value":56,"source":48,"evidence":57},"Python developers who need to change third-party package internals",[58,59],{"id":53,"by":11,"text":54},{"id":60,"by":61,"text":62},"45666523","OJFord","I didn't really get why I'd want to actually use it (vs. just a cool demo) either, until: > means if you want to make changes to a third-party package, you don't have to take on…",null,{"value":65,"source":48,"evidence":66},"Maintainer needed to patch upstream libraries (prompt-toolkit, aiohttp) for euporie without maintaining forks or exposing patches to users",[67],{"id":53,"by":11,"text":54},[69],{"value":70,"source":71,"evidence":72},"Python","inferred",[73],{"id":51},"open_source","unknown",{"praise":77,"objections":104,"requests":140,"questions":159,"alternatives":174},[78,86,94],{"value":79,"source":80,"evidence":81},"Clean, original solution to a long-standing problem","commenter",[82],{"id":83,"by":84,"text":85},"45664919","BiteCode_dev","The is really awesome and an original and clean solution to a dirty old problem. Kudos.",{"value":87,"source":80,"evidence":88},"Useful for distributing changes to third-party packages without forking",[89,90],{"id":60,"by":61,"text":62},{"id":91,"by":92,"text":93},"45668680","ramses0","Yeah, but the example of \"*.retries(...)\", in the context of \"import some_login_library.Login(...)\" is quite powerful! It basically looks like a \"super-decorator\", and I can…",{"value":95,"source":48,"evidence":96},"Python 3.9 to 3.14 support and good license choice noted",[97,100],{"id":98,"by":11,"text":99},"45675514","I've tested it works from 3.9 to 3.14",{"id":101,"by":102,"text":103},"45665661","Uptrenda","What Python versions have you tested on, OP? Good license choice, by the way.",[105,118,123,130],{"value":106,"source":80,"evidence":107},"Import hooks add supply chain and fragility risk compared to patching",[108,112,115],{"id":109,"by":110,"text":111},"45666046","nbadg","For context: one of the several projects I'm working on right now is an automated extraction system for literate-code-style documentation in python. This isn't the place nor time…",{"id":113,"by":110,"text":114},"45666761","If the goal is to actually package and distribute the changes via import hook, that makes the supply chain attack question particularly relevant. And it still doesn't explain why…",{"id":116,"by":110,"text":117},"45669873","If you only want your own code to see the patch, then why not just wrap it? I mean if you really need super strong isolation, you can always create a copy of the library object;…",{"value":119,"source":80,"evidence":120},"Unclear why it is needed versus wrapping or subclassing; use case not well explained",[121,122],{"id":116,"by":110,"text":117},{"id":109,"by":110,"text":111},{"value":124,"source":80,"evidence":125},"Patches must be maintained as upstream updates, similar to an unnamed fork",[126],{"id":127,"by":128,"text":129},"45665116","moezd","This feels too much like breaking the guarantee sticker of a vendor code, and if your vendor pushes updates weekly, or daily, you are stuck pushing updates to your shimmed code,…",{"value":131,"source":80,"evidence":132},"Does not handle external imports of the patched module under the original name",[133,137],{"id":134,"by":135,"text":136},"45665197","o11c","The import limitation seems to make this not useful for me. Usually when I am monkeypatching, it's because some code I do not control has a (possibly dynamic) import of the…",{"id":138,"by":135,"text":139},"45665275","That's talking about internal imports (and static - as much as python supports - ones at that), not external ones. If A is my application, B is buggy, and C is some other…",[141,146,153],{"value":142,"source":80,"evidence":143},"Explain the why and concrete use cases more thoroughly",[144,145],{"id":109,"by":110,"text":111},{"id":116,"by":110,"text":117},{"value":147,"source":80,"evidence":148},"Support hot reload",[149],{"id":150,"by":151,"text":152},"45607185","satya71","This is a nice system. I wonder if modshim can be used to or extended to do hot reload.",{"value":154,"source":48,"evidence":155},"Mount the virtual package over the lower module for global effect",[156],{"id":157,"by":11,"text":158},"45673192","It should in theory be possible to mount the new virtual package over the lower module - but I don't think works currently (I'll have to test this). Doing this would make…",[160,166,170],{"value":161,"source":80,"evidence":162},"Is it ready to demo?",[163],{"id":164,"by":102,"text":165},"45666315","Sounds super interesting. Is it ready to demo?",{"value":167,"source":80,"evidence":168},"Which Python versions are supported?",[169],{"id":101,"by":102,"text":103},{"value":171,"source":80,"evidence":172},"Why not just package the monkeypatch itself?",[173],{"id":113,"by":110,"text":114},[175,183,191,198],{"value":176,"source":80,"evidence":177},"Wrapping or composing functions and classes",[178,179],{"id":116,"by":110,"text":117},{"id":180,"by":181,"text":182},"45667582","afarviral","This is interesting, and I'll try to remember to give this a go next time I'm tempted to patch something from the standard library, but... The README mentions 3 scenarios that…",{"value":184,"source":80,"evidence":185},"Forking the library",[186,190],{"id":187,"by":188,"text":189},"45665664","procaryote","Of these: > * Fix bugs in third-party libraries without forking > * Modify the behavior of existing functions > * Add new features or options to existing classes > * Test…",{"id":53,"by":11,"text":54},{"value":192,"source":80,"evidence":193},"patchy library",[194],{"id":195,"by":196,"text":197},"45665540","boxed","Also check out https:\u002F\u002Fgithub.com\u002Fadamchainz\u002Fpatchy",{"value":199,"source":80,"evidence":200},"Cargo-style dependency patching",[201],{"id":202,"by":203,"text":204},"45671941","tracnar","I wish a Python package manager would support patching dependencies, like e.g. Cargo allows:…",{"comments":39,"answered":33,"key":206},[207,208,209],{"id":53,"text":54},{"id":157,"text":158},{"id":98,"text":99},{"commenters":35,"max_depth":32,"maker_replies":39,"first_comment_mins":211,"lifespan_hours":212},293,165,[214,216,218,221,225,227,229,232,233,236,239,241,242,245,246,248,250,252,256,259,261,262,264,265,267,271,274,276,278,281],[109,41,33,215,110,41],"maker\u002Fcritical\u002Fdocs",[164,41,39,217,102,41],"maker\u002Fneutral\u002Fnone",[219,41,41,220,110,41],"45666325","commenter\u002Fneutral\u002Fnone",[222,41,41,223,224,41],"45668131","other\u002Fneutral\u002Fnone","pbronez",[60,41,32,226,61,41],"project\u002Fpositive\u002Fidea",[113,41,33,228,110,41],"project\u002Fcritical\u002Fidea",[230,41,39,231,84,41],"45669215","project\u002Fmixed\u002Fidea",[116,41,33,228,110,41],[234,41,41,220,235,41],"45675636","Izkata",[237,41,33,238,110,41],"45679590","project\u002Fcritical\u002Fdesign",[53,41,41,240,11,33],"project\u002Fneutral\u002Fidea",[134,33,33,238,135,41],[243,33,39,240,244,41],"45665232","theptip",[138,33,41,220,135,41],[157,33,41,247,11,33],"project\u002Fneutral\u002Fexecution",[180,42,32,249,181,41],"project\u002Fpositive\u002Fdocs",[195,39,39,251,196,41],"project\u002Fneutral\u002Fnone",[253,254,41,223,255,41],"45664756",4,"epgui",[257,32,39,258,235,41],"45664849","project\u002Fneutral\u002Fdocs",[127,260,33,228,128,41],6,[91,260,32,226,92,41],[83,263,32,226,84,41],7,[150,34,39,240,151,41],[202,266,41,223,203,41],9,[268,269,33,228,270,41],"45667329",10,"pmarreck",[272,269,41,220,273,41],"45668234","throwaway894345",[275,269,41,223,270,41],"45672143",[187,277,33,228,188,41],11,[101,279,32,280,102,41],12,"project\u002Fpositive\u002Fnone",[98,279,41,247,11,33],[],[284,289,294,298,302,306],{"id":285,"name":286,"points":287,"category":288},"45617948","detective",318,"games",{"id":290,"name":291,"points":292,"category":293},"45633453","Duck-UI",213,"dev_tools",{"id":295,"name":296,"points":297,"category":293},"45642911","Playwright Skill",189,{"id":299,"name":300,"points":301,"category":288},"45609478","Abstract Board Games",180,{"id":303,"name":304,"points":305,"category":293},"45645120","I created a cross-platform GUI for the JJ VCS (Git compatible)",140,{"id":307,"name":308,"points":309,"category":310},"45656952","Katakate",124,"infrastructure",false,"v2",{"id":314,"name":5,"line":6,"ai":311,"cat":20,"year":13,"points":14,"comments":15,"rec":29,"s":315,"top":18,"kw":316,"maker":11,"domain":10,"tier":19,"attempts":41,"strip":317,"url":9,"mix":318,"praise":79,"objection":106,"sub":21},45604013,-0.11,[24,25,26,27,28],"130051310101300530315530100150",[32,32,33,34],1794036684228]