{"record":{"id":"eeba1d0341e2043c","repo":"microsoft/qlib","slug":"please-implement-the-save-objs-method","errorCode":null,"errorMessage":"Please implement the `save_objs` method","messagePattern":"Please implement the `save_objs` method","errorType":"exception","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"qlib/utils/objm.py","lineNumber":34,"sourceCode":"\n        Parameters\n        ----------\n        obj : object\n            object to be saved\n        name : str\n            name of the object\n        \"\"\"\n        raise NotImplementedError(f\"Please implement `save_obj`\")\n\n    def save_objs(self, obj_name_l):\n        \"\"\"\n        save objects\n\n        Parameters\n        ----------\n        obj_name_l : list of <obj, name>\n        \"\"\"\n        raise NotImplementedError(f\"Please implement the `save_objs` method\")\n\n    def load_obj(self, name: str) -> object:\n        \"\"\"\n        load object by name\n\n        Parameters\n        ----------\n        name : str\n            the name of the object\n\n        Returns\n        -------\n        object:\n            loaded object\n        \"\"\"\n        raise NotImplementedError(f\"Please implement the `load_obj` method\")\n\n    def exists(self, name: str) -> bool:","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/microsoft/qlib/blob/79633dd9506ea689e5400dea0197717b5b3d74b7/qlib/utils/objm.py#L16-L52","documentation":"save_objs is the batch-write method of the abstract ObjManager interface (base FileManager implements it as a loop over save_obj). The base class stub raises NotImplementedError to force concrete subclasses to provide bulk persistence. Hitting it means batch-saving went to the abstract base or to a subclass that only implemented single-object saving.","triggerScenarios":"ObjManager().save_objs([(obj1, 'a'), (obj2, 'b')]) or a custom manager class overriding save_obj/load_obj but not save_objs.","commonSituations":"Custom S3/Redis-backed object managers written against an older qlib interface; code paths in qlib's task/workflow machinery that dump many artifacts at once; partial test doubles that stub only the methods a previous test used.","solutions":["Switch to FileManager (its save_objs delegates to save_obj and works out of the box).","Implement save_objs(self, obj_name_l) in your subclass — delegating to self.save_obj(obj, name) in a loop is sufficient.","As a workaround, call save_obj per item instead of save_objs."],"exampleFix":"// before\nmgr = ObjManager(); mgr.save_objs([(m, 'm1')])  # NotImplementedError\n\n// after\nclass MyManager(ObjManager):\n    def save_obj(self, obj, name):\n        backend.put(name, obj)\n    def save_objs(self, obj_name_l):\n        for obj, name in obj_name_l:\n            self.save_obj(obj, name)","handlingStrategy":"fallback","validationCode":"if not hasattr(type(mgr), 'save_objs') or type(mgr).save_objs is ObjManager.save_objs:\n    for obj, name in items:\n        mgr.save_obj(obj, name)  # fallback to per-item saves\nelse:\n    mgr.save_objs(items)","typeGuard":"def can_batch_save(mgr) -> bool:\n    return type(mgr).save_objs is not ObjManager.save_objs","tryCatchPattern":"try:\n    mgr.save_objs(items)\nexcept NotImplementedError:\n    for obj, name in items:\n        mgr.save_obj(obj, name)","preventionTips":["In custom managers, implement save_objs as a trivial loop over save_obj so the contract is always satisfied.","Prefer per-item save_obj calls in generic code unless batch semantics are known to exist."],"tags":["qlib","objm","abstract-method","notimplementederror","batch-save"],"backgroundTag":null,"analyzedSha":"79633dd9506ea689e5400dea0197717b5b3d74b7","analyzedAt":"2026-08-15T07:01:27.511Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}