{"record":{"id":"32059323ea044a46","repo":"pola-rs/polars","slug":"path-argument-has-invalid-type-qualified-type-n","errorCode":null,"errorMessage":"`path` argument has invalid type {qualified_type_name(path)!r}, and cannot be turned into a sink target","messagePattern":"`path` argument has invalid type (.+?), and cannot be turned into a sink target","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"py-polars/src/polars/lazyframe/engine.py","lineNumber":64,"sourceCode":"\n\ndef _to_sink_target(\n    path: str | Path | IO[bytes] | IO[str] | PartitionBy,\n) -> str | Path | IO[bytes] | IO[str] | PartitionBy:\n    from polars.io.partition import PartitionBy\n\n    if isinstance(path, (str, Path)):\n        return normalize_filepath(path)\n    elif isinstance(path, io.IOBase):\n        return path\n    elif isinstance(path, PartitionBy):\n        return path\n    elif callable(getattr(path, \"write\", None)):\n        # This allows for custom writers\n        return path\n    else:\n        msg = f\"`path` argument has invalid type {qualified_type_name(path)!r}, and cannot be turned into a sink target\"\n        raise TypeError(msg)\n\n\ndef _with_monitoring(optimizations: QueryOptFlags) -> QueryOptFlags:\n    \"\"\"Register the query observer, and flag `optimizations` accordingly.\"\"\"\n    monitor = os.environ.get(\"POLARS_QUERY_MONITORING\") == \"1\"\n    if monitor:\n        import polars._plr as plr\n\n        plr.set_query_monitoring(True)\n\n    optimizations = optimizations.__copy__()\n    optimizations._pyoptflags.query_monitoring = monitor\n    return optimizations\n\n\ndef _apply_retries_deprecation(\n    retries: int | None, storage_options: StorageOptionsDict | None\n) -> StorageOptionsDict | None:","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/pola-rs/polars/blob/df599052daf96e7a9cc30a3b0c6bd25d6947e3c0/py-polars/src/polars/lazyframe/engine.py#L46-L82","documentation":"The private helper `_to_sink_target` (py-polars/src/polars/lazyframe/engine.py:64) normalizes the `path` argument of every local sink operation (`sink_parquet`, `sink_ipc`, `sink_csv`, `sink_ndjson`). It only accepts `str`, `pathlib.Path`, an `io.IOBase` file object, a `PartitionBy` target, or any object with a callable `.write` attribute (custom writer). Any other type is rejected up front with this TypeError so the query never starts.","triggerScenarios":"Calling `lf.sink_parquet(path)` / `lf.sink_ipc(path)` / `lf.sink_csv(path)` / `lf.sink_ndjson(path)` (or the same on any local engine: in-memory, streaming, gpu, auto) with e.g. an int, `bytes`, a `list` of paths, `None`, `os.DirEntry`, or a test mock that has no `.write` method.","commonSituations":"Passing file content (`bytes`) instead of a file path; passing a list of paths expecting multi-file output (use `pl.PartitionBy` instead); passing a Path-like object from a third-party VFS library that is neither `io.IOBase` nor exposes `.write`; stubbing sinks in tests with objects that lack a callable `write`.","solutions":["Pass a `str` or `pathlib.Path` file path: `lf.sink_parquet('out.parquet')`","Pass a real opened binary file object, e.g. `open('out.parquet','wb')` or `io.BytesIO()`","For partitioned multi-file output pass `pl.PartitionBy(...)` as the target","For a custom destination, pass an object implementing a callable `.write` method, or use `lf.sink_batches(fn)` for per-batch callbacks"],"exampleFix":"# before\nlf.sink_parquet(b'not-a-path')\n\n# after\nlf.sink_parquet('out.parquet')  # str/Path, open('out.parquet','wb'), or pl.PartitionBy(...)","handlingStrategy":"type-guard","validationCode":"import io\nfrom pathlib import Path\nfrom polars.io.partition import PartitionBy\n\ndef is_sink_target(path: object) -> bool:\n    return (\n        isinstance(path, (str, Path, io.IOBase, PartitionBy))\n        or callable(getattr(path, 'write', None))\n    )\n\n# before sinking:\nassert is_sink_target(path), f'bad sink target: {type(path).__name__}'","typeGuard":"from typing import TypeGuard\nimport io\nfrom pathlib import Path\nfrom polars.io.partition import PartitionBy\n\ndef is_sink_target(path: object) -> TypeGuard[str | Path | io.IOBase | PartitionBy]:\n    return (\n        isinstance(path, (str, Path, io.IOBase, PartitionBy))\n        or callable(getattr(path, 'write', None))\n    )","tryCatchPattern":"try:\n    lf.sink_parquet(path)\nexcept TypeError as e:\n    if 'cannot be turned into a sink target' in str(e):\n        raise ValueError(f'unsupported sink path {path!r}') from e\n    raise","preventionTips":["Always build sink paths as str or pathlib.Path from the start","Wrap foreign file-like objects so they subclass io.IOBase or expose .write","Keep a single is_sink_target guard next to any API boundary that accepts user-supplied destinations","For multiple outputs use PartitionBy instead of passing a list of paths"],"tags":["polars","sink","path","typeerror"],"backgroundTag":null,"analyzedSha":"df599052daf96e7a9cc30a3b0c6bd25d6947e3c0","analyzedAt":"2026-08-16T12:10:03.978Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}