{"record":{"id":"e302fe6287ef1267","repo":"apache/beam","slug":"cannot-make-make-an-unkeyed-model-handler-with-pre-or","errorCode":null,"errorMessage":"Cannot make make an unkeyed model handler with pre or postprocessing functions defined into a keyed model handler. All pre/postprocessing functions must be defined on the outer modelhandler.","messagePattern":"Cannot make make an unkeyed model handler with pre or postprocessing functions defined into a keyed model handler\\. All pre/postprocessing functions must be defined on the outer modelhandler\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/ml/inference/base.py","lineNumber":764,"sourceCode":"    Args:\n      unkeyed: Either (a) an implementation of ModelHandler that does not\n        require keys or (b) a list of KeyModelMappings mapping lists of keys to\n        unkeyed ModelHandlers.\n      max_models_per_worker_hint: A hint to the runner indicating how many\n        models can be held in memory at one time per worker process. For\n        example, if your worker has 8 GB of memory provisioned and your workers\n        take up 1 GB each, you should set this to 7 to allow all models to sit\n        in memory with some buffer. For more information about memory management,\n        see `Use a keyed `ModelHandler <https://beam.apache.org/documentation/ml/about-ml/#use-a-keyed-modelhandler-object>_`.  # pylint: disable=line-too-long\n    \"\"\"\n    self._metrics_collectors: dict[str, _MetricsCollector] = {}\n    self._default_metrics_collector: _MetricsCollector = None\n    self._metrics_namespace = ''\n    self._single_model = not isinstance(unkeyed, list)\n    if self._single_model:\n      if len(unkeyed.get_preprocess_fns()) or len(\n          unkeyed.get_postprocess_fns()):\n        raise Exception(\n            'Cannot make make an unkeyed model handler with pre or '\n            'postprocessing functions defined into a keyed model handler. All '\n            'pre/postprocessing functions must be defined on the outer model'\n            'handler.')\n      self._env_vars = getattr(unkeyed, '_env_vars', {})\n      self._unkeyed = unkeyed\n      return\n\n    self._max_models_per_worker_hint = max_models_per_worker_hint\n    # To maintain an efficient representation, we will map all keys in a given\n    # KeyModelMapping to a single id (the first key in the KeyModelMapping\n    # list). We will then map that key to a ModelHandler. This will allow us to\n    # quickly look up the appropriate ModelHandler for any given key.\n    self._id_to_mh_map: dict[str, ModelHandler[ExampleT, PredictionT,\n                                               ModelT]] = {}\n    self._key_to_id_map: dict[str, str] = {}\n    for mh_tuple in unkeyed:\n      mh = mh_tuple.mh","sourceCodeStart":746,"sourceCodeEnd":782,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/ml/inference/base.py#L746-L782","documentation":"KeyedModelHandler wraps a single unkeyed handler for per-key model updates. Decorations like preprocess/postprocess functions belong on the outer (keyed) handler, not the inner one; the constructor rejects an inner handler that already carries preprocessing functions with a plain Exception.","triggerScenarios":"Calling KeyedModelHandler(unkeyed) where unkeyed is a single ModelHandler with preprocessing or postprocessing functions attached (e.g. via unkeyed.with_preprocess_fns(...)).","commonSituations":"Chaining with_preprocess_fns then wrapping in KeyedModelHandler; refactoring a decorated handler into a keyed one for model updates and forgetting to move the functions.","solutions":["Pass the bare (undecorated) handler into KeyedModelHandler.","Apply .with_preprocess_fns() / .with_postprocess_fns() to the KeyedModelHandler itself, not the inner handler.","Reorder the pipeline: construct KeyedModelHandler first, then decorate it."],"exampleFix":"# before\nmh = MyHandler().with_preprocess_fns(preprocess)\nkeyed = KeyedModelHandler(mh)\n\n# after\nkeyed = KeyedModelHandler(MyHandler()).with_preprocess_fns(preprocess)","handlingStrategy":"validation","validationCode":"if len(unkeyed.get_preprocess_fns()) or len(unkeyed.get_postprocess_fns()):\n    raise ValueError('Move pre/postprocess fns to the KeyedModelHandler, not the inner handler')","typeGuard":"def is_undecorated(mh):\n    return not mh.get_preprocess_fns() and not mh.get_postprocess_fns()","tryCatchPattern":"try:\n    keyed = KeyedModelHandler(unkeyed)\nexcept Exception as e:\n    if 'pre or postprocessing' in str(e):\n        keyed = KeyedModelHandler(type(unkeyed)(**inner_args)).with_preprocess_fns(...)\n    else:\n        raise","preventionTips":["Always construct KeyedModelHandler from bare handlers; decorate afterward.","Keep with_preprocess_fns/with_postprocess_fns calls on the outermost handler in the chain.","Add a check in pipeline-construction helpers rejecting decorated inner handlers."],"tags":["python","apache-beam","ml-inference","model-handler","api-misuse"],"backgroundTag":"conflicting-config-options","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}