{"record":{"id":"2ed3d14bee819b01","repo":"cocoindex-io/cocoindex","slug":"batching-and-runner-require-the-function-to-be-asy","errorCode":null,"errorMessage":"Batching and runner require the function to be async. Use @coco.fn.as_async instead, or rewrite the function to be async.","messagePattern":"Batching and runner require the function to be async\\. Use @coco\\.fn\\.as_async instead, or rewrite the function to be async\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/_internal/function.py","lineNumber":1850,"sourceCode":"        batching: bool = False,\n        max_batch_size: int | None = None,\n        runner: Runner | None = None,\n        version: int | None = None,\n        logic_tracking: LogicTracking = \"full\",\n        deps: Any = None,\n    ) -> None:\n        self._memo = memo\n        self._memo_key = memo_key\n        self._batching = batching\n        self._max_batch_size = max_batch_size\n        self._runner = runner\n        self._version = version\n        self._logic_tracking = logic_tracking\n        self._deps = deps\n\n    def _build_sync(self, fn: Callable[P, R_co]) -> SyncFunction[P, R_co]:\n        if self._batching or self._runner is not None:\n            raise ValueError(\n                \"Batching and runner require the function to be async. \"\n                \"Use @coco.fn.as_async instead, or rewrite the function to be async.\"\n            )\n        wrapper = SyncFunction(\n            fn,\n            memo=self._memo,\n            memo_key=self._memo_key,\n            version=self._version,\n            logic_tracking=self._logic_tracking,\n            deps=self._deps,\n        )\n        functools.update_wrapper(wrapper, fn)\n        return wrapper\n\n    def _build_async(\n        self,\n        fn: AnyCallable[P, R_co],\n    ) -> AsyncFunction[P, R_co]:","sourceCodeStart":1832,"sourceCodeEnd":1868,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/_internal/function.py#L1832-L1868","documentation":"The sync builder path (_build_sync, used by @coco.fn for plain sync functions) cannot support batching= or runner=, which require asynchronous execution. Applying such a decorator configuration to a sync function raises ValueError at decoration time.","triggerScenarios":"@coco.fn(batching=True) or @coco.fn(runner=...) applied to a def (non-async) function; _build_sync invoked via __call__ with _batching set or _runner non-None.","commonSituations":"Copying a decorator config that used batching onto a sync function; switching a function from async def to def while keeping batching/runner options; using @coco.fn instead of @coco.fn.as_async for batched processing.","solutions":["Rewrite the function as async def and use @coco.fn.as_async.","Remove batching=/runner= options if the function should stay sync.","Keep the function sync and process batches externally with mount_each/map instead."],"exampleFix":"// before\n@coco.fn(batching=True)\ndef embed(texts): ...\n// after\n@coco.fn.as_async(batching=True)\nasync def embed(texts): ...","handlingStrategy":"validation","validationCode":"import inspect\nif (batching or runner is not None) and not inspect.iscoroutinefunction(fn):\n    raise TypeError(\"use @coco.fn.as_async for batching/runner\")","typeGuard":"def is_async_fn(fn): return inspect.iscoroutinefunction(fn)","tryCatchPattern":"try:\n    deco = coco.fn(batching=True)(fn)\nexcept ValueError:\n    deco = coco.fn.as_async(batching=True)(make_async(fn))","preventionTips":["Use @coco.fn.as_async whenever batching= or runner= is set.","Match decorator variant to function kind: def -> coco.fn, async def -> as_async."],"tags":["python","async","batching","decorator"],"backgroundTag":"invalid-argument-value","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}