Repository: dask/dask
Pull request: Fix ``to_dask_array`` for single partition
Closes https://github.com/dask/dask/issues/11928 

Problem statement:
`FrameBase.to_dask_array` raises error when `lengths=True`
**Describe the issue**:

The `FrameBase.to_dask_array` raises `TypeError: 'int' object is not iterable` when called with `lengths=True`


**Minimal Complete Verifiable Example**:

```python
import numpy as np
import dask.dataframe as dd

data = dd.from_array(np.random.normal(size=(10_000, 2)))
data.to_dask_array(lengths=True)
```

<details>

<summary>Full Traceback</summary>

```bash
TypeError                                 Traceback (most recent call last)
Cell In[16], line 6
      4 data = np.random.normal(size=(10_000, 2))
      5 data = dd.from_array(data)
----> 6 data.to_dask_array(lengths=True)

File ~/mambaforge/envs/pymc-statespace/lib/python3.12/site-packages/dask/dataframe/dask_expr/_collection.py:1405, in FrameBase.to_dask_array(self, lengths, meta, optimize, **optimize_kwargs)
   1379 """Convert a dask DataFrame to a dask array.
   1380
   1381 Parameters
   (...)   1402 A Dask Array
   1403 """
   1404 if lengths is True:
-> 1405     lengths = tuple(self.map_partitions(len, enforce_metadata=False).compute())
   1407 arr = self.values
   1409 chunks = self._validate_chunks(arr, lengths)

TypeError: 'int' object is not iterable

```

</details>


**Anything else we need to know?**:

**Environment**:

- Dask version: 2025.4.1
- Python version: 3.12.10
- Operating System: Ubuntu (via WSL)
- Install method (conda, pip, source): conda

Hints from issue discussion:

Commit message: Fix to_dask_array for single partition

Patch to review:
diff --git a/dask/dataframe/dask_expr/_collection.py b/dask/dataframe/dask_expr/_collection.py
index 91fea784018..a851e81ca79 100644
--- a/dask/dataframe/dask_expr/_collection.py
+++ b/dask/dataframe/dask_expr/_collection.py
@@ -1404,7 +1404,11 @@ def to_dask_array(
         A Dask Array
         """
         if lengths is True:
-            lengths = tuple(self.map_partitions(len, enforce_metadata=False).compute())
+            lens = self.map_partitions(len).compute()
+            if is_scalar(lens):
+                lengths = (lens,)
+            else:
+                lengths = tuple(lens)
 
         arr = self.values
 
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
index acf7111ecb7..fc054c7cb77 100644
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -2849,6 +2849,17 @@ def test_to_dask_array(meta, as_frame, lengths):
     assert result.chunks == expected_chunks
 
 
+def test_to_dask_array_single_partition():
+    s = pd.Series([1, 2, 3, 4, 5], name="foo", dtype="i4")
+    a = dd.from_pandas(s)
+
+    result = a.to_dask_array(lengths=True)
+    assert isinstance(result, da.Array)
+    expected_chunks = (s.shape,)
+    assert result.chunks == expected_chunks
+    da.assert_eq(s.values, result)
+
+
 def test_apply():
     df = pd.DataFrame({"x": [1, 2, 3, 4], "y": [10, 20, 30, 40]})
     ddf = dd.from_pandas(df, npartitions=2)


Code review comments:
diff_hunk: @@ -1404,7 +1404,11 @@ def to_dask_array(
         A Dask Array
         """
         if lengths is True:
-            lengths = tuple(self.map_partitions(len, enforce_metadata=False).compute())
+            lens = self.map_partitions(len).compute()
path: dask/dataframe/dask_expr/_collection.py
text: @user1:
FWIW, just removing the `enforce_metadata` seems to be sufficient:

```
In [24]: s = dd.from_pandas(pd.Series([1, 2]), npartitions=1)

In [25]: tuple(s.map_partitions(len).compute())
Out[25]: (2,)
```

@author:
I noticed that as well but wasn't sure if ignoring metadata enforcement was added for some other reason. I actually meant to keep `enforce_metadata=False` so thanks for pointing that out. Though thinking about it more I think metadata enforcement here (with `len`) is okay, so I'll update with your suggestion