{"record":{"id":"a3e97fa376ba7ee3","repo":"apache/beam","slug":"count-d-must-be-a-positive-integer","errorCode":null,"errorMessage":"count (%d) must be a positive integer.","messagePattern":"count \\((.+?)\\) must be a positive integer\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/transforms/trigger.py","lineNumber":686,"sourceCode":"        context) if self.early else None\n    late_proto = self.late.underlying.to_runner_api(\n        context) if self.late else None\n    return beam_runner_api_pb2.Trigger(\n        after_end_of_window=beam_runner_api_pb2.Trigger.AfterEndOfWindow(\n            early_firings=early_proto, late_firings=late_proto))\n\n  def has_ontime_pane(self):\n    return True\n\n\nclass AfterCount(TriggerFn):\n  \"\"\"Fire when there are at least count elements in this window pane.\"\"\"\n\n  COUNT_TAG = _CombiningValueStateTag('count', combiners.CountCombineFn())\n\n  def __init__(self, count):\n    if not isinstance(count, numbers.Integral) or count < 1:\n      raise ValueError(\"count (%d) must be a positive integer.\" % count)\n    self.count = count\n\n  def __repr__(self):\n    return 'AfterCount(%s)' % self.count\n\n  def __eq__(self, other):\n    return type(self) == type(other) and self.count == other.count\n\n  def __hash__(self):\n    return hash(self.count)\n\n  def on_element(self, element, window, context):\n    context.add_state(self.COUNT_TAG, 1)\n\n  def on_merge(self, to_be_merged, merge_result, context):\n    # states automatically merged\n    pass\n","sourceCodeStart":668,"sourceCodeEnd":704,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/transforms/trigger.py#L668-L704","documentation":"AfterCount.__init__ validates that its count argument is a positive integer (numbers.Integral and >= 1) and raises ValueError otherwise. The trigger fires only when a pane holds at least `count` elements, so zero, negative, or non-integer counts are meaningless.","triggerScenarios":"AfterCount(0), AfterCount(-5), or AfterCount(2.5); any call where count is a float, string, or bool-derived expression that fails the isinstance(numbers.Integral) and >= 1 checks.","commonSituations":"Building trigger pipelines where the count comes from user config (YAML/CLI) and was parsed as a float or string; computing count dynamically and getting 0 on empty datasets; translating from another framework where 0 means 'disabled'.","solutions":["Pass an int >= 1, e.g. AfterCount(1).","Coerce config input: AfterCount(int(value)) after checking it is a whole number and > 0.","If you want time-based behavior with count 0 semantics, use AfterProcessingTime or a different trigger instead."],"exampleFix":"// before\ncount = float(cfg['fire_count'])  # 2.0 -> ValueError\nt = AfterCount(count)\n// after\ncount = int(cfg['fire_count'])\nassert count >= 1\nt = AfterCount(count)","handlingStrategy":"validation","validationCode":"import numbers\nif not isinstance(count, numbers.Integral) or count < 1:\n    raise ValueError(f'count must be a positive int, got {count!r}')","typeGuard":"def is_positive_int(v):\n    return isinstance(v, numbers.Integral) and not isinstance(v, bool) and v >= 1","tryCatchPattern":"try:\n    trigger = AfterCount(count)\nexcept ValueError:\n    trigger = AfterCount(1)  # safe minimum","preventionTips":["Coerce config-driven counts with int() and validate >= 1.","Never pass floats from YAML/JSON configs directly to AfterCount.","Add unit tests covering count=0 and negative values at config parse time."],"tags":["python","apache-beam","trigger","aftercount","valueerror"],"backgroundTag":"invalid-argument-value","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-15T13:17:12.816Z"}