{"record":{"id":"1003311d9206e773","repo":"apache/beam","slug":"packages-must-be-an-iterable-of-strings-got-r-packages","errorCode":null,"errorMessage":"\"Packages must be an iterable of strings, got %r\" % packages","messagePattern":"\"Packages must be an iterable of strings, got %r\" % packages","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/yaml/yaml_provider.py","lineNumber":1345,"sourceCode":"\n\nclass PypiExpansionService:\n  \"\"\"Expands transforms by fully qualified name in a virtual environment\n  with the given dependencies.\n  \"\"\"\n  if 'TOX_WORK_DIR' in os.environ:\n    VENV_CACHE = tempfile.mkdtemp(\n        prefix='test-venv-cache-', dir=os.environ['TOX_WORK_DIR'])\n  elif 'RUNNER_WORKDIR' in os.environ:\n    VENV_CACHE = tempfile.mkdtemp(\n        prefix='test-venv-cache-', dir=os.environ['RUNNER_WORKDIR'])\n  else:\n    VENV_CACHE = os.path.expanduser(\"~/.apache_beam/cache/venvs\")\n\n  def __init__(\n      self, packages: Iterable[str], base_python: str = sys.executable):\n    if not isinstance(packages, Iterable) or isinstance(packages, str):\n      raise TypeError(\n          \"Packages must be an iterable of strings, got %r\" % packages)\n    self._packages = list(packages)\n    self._base_python = base_python\n\n  @classmethod\n  def _key(cls, base_python: str, packages: list[str]) -> str:\n    def normalize_package(package):\n      if os.path.exists(package):\n        # Ignore the exact path by which this package was referenced,\n        # but do create a new environment if it changed.\n        with open(package, 'rb') as fin:\n          return os.path.basename(package) + '-' + _file_digest(\n              fin, 'sha256').hexdigest()\n      else:\n        # Assume urls and pypi identifiers are immutable.\n        return package\n\n    return json.dumps({","sourceCodeStart":1327,"sourceCodeEnd":1363,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/yaml/yaml_provider.py#L1327-L1363","documentation":"Raised by the PythonPackageProvider (venv-based external provider) constructor when the 'packages' argument is not an iterable or is itself a plain string. A string is explicitly rejected because iterating it would treat each character as a package name.","triggerScenarios":"Constructing PythonPackageProvider with packages=None, an int, or a single string like 'pandas==1.0' instead of a list of package specs; or passing a comma-joined string from config instead of a YAML list.","commonSituations":"YAML provider specs where packages was written as a scalar instead of a list, or programmatic construction where a single package was passed without wrapping in a list.","solutions":["Wrap the value in a list: PythonPackageProvider(['pandas==1.0']).","In YAML provider config, write packages as a list: packages: ['pandas', 'numpy'].","If the input may be a scalar, convert defensively: packages = [packages] if isinstance(packages, str) else list(packages)."],"exampleFix":"// before\nPythonPackageProvider('pandas==2.0')\n// after\nPythonPackageProvider(['pandas==2.0'])","handlingStrategy":"type-guard","validationCode":"def normalize_packages(packages):\n    if isinstance(packages, str):\n        packages = [packages]\n    if not isinstance(packages, Iterable):\n        raise TypeError('packages must be an iterable of strings')\n    return list(packages)","typeGuard":"def is_valid_packages_arg(packages):\n    return isinstance(packages, Iterable) and not isinstance(packages, str)","tryCatchPattern":"try:\n    provider = PythonPackageProvider(packages)\nexcept TypeError as e:\n    if 'Packages must be an iterable' in str(e):\n        logging.warning('Wrapping scalar packages into a list')\n        provider = PythonPackageProvider([packages])\n    else:\n        raise","preventionTips":["Always pass a list, even for one package.","In YAML provider specs, use block-list syntax for packages.","Split comma-joined strings before passing them in."],"tags":["python","apache-beam","type-mismatch","provider"],"backgroundTag":"invalid-constructor-argument","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}