Repository: dask/dask
Pull request #11586 (2024-12-05 22:26:59)

asarray ignores dtype for array inputs
Fix bug where `da.asarray(x, dtype=...)` would not change the dtype when x is a non-dask array-like

github-actions[bot]: ## Unit Test Results
_See [test report](https://dask.github.io/dask/test_report.html) for an extended history of previous test failures. This is useful for diagnosing flaky tests._

     15 files  ±0       15 suites  ±0   3h 59m 17s ⏱️ +44s
 13 267 tests ±0   12 205 ✅ +1   1 062 💤 ±0  0 ❌  - 1 
164 601 runs  ±0  141 670 ✅  - 2  22 931 💤 +3  0 ❌  - 1 

Results for commit f19589d6. ± Comparison against base commit 02185bed.

[test-results]:data:application/gzip;base64,H4sIAJgwUmcC/03MTQ6DIBCG4asY1l3AAGPpZRodh4TUnwZh1fTuRWvQ5fvM5PsIH0ZexaNR9taINYd01pBjl8Iyb220bQuVa9rvGrD2c81EGwJIe+IrvDeUCNV8F8ZisgLHuMRDYp73aTQo1dF12ihsZcX/NIDT9fEyvfd1mZZpCqmE8MrZuxvQG9kBEXnmocfeeYeM0Bok4p69Ft8fM8N/DxoBAAA=


Patch:
diff --git a/dask/_task_spec.py b/dask/_task_spec.py
index c86c7252b..316f1805a 100644
--- a/dask/_task_spec.py
+++ b/dask/_task_spec.py
@@ -264,9 +264,7 @@ def convert_legacy_task(
         else:
             parsed_args = tuple(convert_legacy_task(None, t, all_keys) for t in task)
             if any(isinstance(a, GraphNode) for a in parsed_args):
-                return Task(
-                    key, _identity_cast, *parsed_args, typ=DataNode(None, type(task))
-                )
+                return Task(key, _identity_cast, *parsed_args, typ=type(task))
             else:
                 return cast(_T, type(task)(parsed_args))
     elif isinstance(task, TaskRef):
@@ -287,7 +285,7 @@ def convert_legacy_graph(
     new_dsk = {}
     for k, arg in dsk.items():
         t = convert_legacy_task(k, arg, all_keys)
-        if isinstance(t, Alias) and t.target.key == k:
+        if isinstance(t, Alias) and t.target == k:
             continue
         elif not isinstance(t, GraphNode):
             t = DataNode(k, t)
@@ -317,7 +315,7 @@ def resolve_aliases(dsk: dict, keys: set, dependents: dict) -> dict:
         seen.add(k)
         t = dsk[k]
         if isinstance(t, Alias):
-            target_key = t.target.key
+            target_key = t.target
             # Rules for when we allow to collapse an alias
             # 1. The target key is not in the keys set. The keys set is what the
             #    user is requesting and by collapsing we'd no longer be able to
@@ -494,7 +492,7 @@ _no_deps: frozenset = frozenset()
 
 
 class Alias(GraphNode):
-    target: TaskRef
+    target: KeyType
     __slots__ = tuple(__annotations__)
 
     def __init__(self, key: KeyType, target: Alias | TaskRef | KeyType | None = None):
@@ -503,10 +501,10 @@ class Alias(GraphNode):
             target = key
         if isinstance(target, Alias):
             target = target.target
-        if not isinstance(target, TaskRef):
-            target = TaskRef(target)
+        if isinstance(target, TaskRef):
+            target = target.key
         self.target = target
-        self._dependencies = frozenset((target.key,))
+        self._dependencies = frozenset((self.target,))
 
     def copy(self):
         return Alias(self.key, self.target)
@@ -514,10 +512,10 @@ class Alias(GraphNode):
     def substitute(
         self, subs: dict[KeyType, KeyType | GraphNode], key: KeyType | None = None
     ) -> GraphNode:
-        if self.key in subs or self.target.key in subs:
+        if self.key in subs or self.target in subs:
             sub_key = subs.get(self.key, self.key)
-            val = subs.get(self.target.key, self.target.key)
-            if sub_key == self.key and val == self.target.key:
+            val = subs.get(self.target, self.target)
+            if sub_key == self.key and val == self.target:
                 return self
             if isinstance(val, GraphNode):
                 return val.substitute({}, key=key)
@@ -529,15 +527,15 @@ class Alias(GraphNode):
         return self
 
     def __dask_tokenize__(self):
-        return (type(self).__name__, self.key, self.target.key)
+        return (type(self).__name__, self.key, self.target)
 
     def __call__(self, values=()):
         self._verify_values(values)
-        return values[self.target.key]
+        return values[self.target]
 
     def __repr__(self):
-        if self.key != self.target.key:
-            return f"Alias({self.key!r}->{self.target.key!r})"
+        if self.key != self.target:
+            return f"Alias({self.key!r}->{self.target!r})"
         else:
             return f"Alias({self.key!r})"
 
diff --git a/dask/array/core.py b/dask/array/core.py
index a62f63aee..7dfff2b0f 100644
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -4728,7 +4728,9 @@ def asarray(
             return a.map_blocks(np.asarray, like=like_meta, dtype=dtype, order=order)
         else:
             a = np.asarray(a, like=like_meta, dtype=dtype, order=order)
-    return from_array(a, getitem=getter_inline, **kwargs)
+
+    a = from_array(a, getitem=getter_inline, **kwargs)
+    return _as_dtype(a, dtype)
 
 
 def asanyarray(a, dtype=None, order=None, *, like=None, inline_array=False):
@@ -4800,13 +4802,15 @@ def asanyarray(a, dtype=None, order=None, *, like=None, inline_array=False):
             return a.map_blocks(np.asanyarray, like=like_meta, dtype=dtype, order=order)
         else:
             a = np.asanyarray(a, like=like_meta, dtype=dtype, order=order)
-    return from_array(
+
+    a = from_array(
         a,
         chunks=a.shape,
         getitem=getter_inline,
         asarray=False,
         inline_array=inline_array,
     )
+    return _as_dtype(a, dtype)
 
 
 def is_scalar_for_elemwise(arg):
diff --git a/docs/source/_static/custom.js b/docs/source/_static/custom.js
new file mode 100644
index 000000000..1b6f8f16e
--- /dev/null
+++ b/docs/source/_static/custom.js
@@ -0,0 +1,26 @@
+document.addEventListener("DOMContentLoaded", function () {
+  var script = document.createElement("script");
+  script.type = "module";
+  script.id = "runllm-widget-script"
+
+  script.src = "https://widget.runllm.com";
+
+  script.setAttribute("version", "stable");
+  script.setAttribute("runllm-keyboard-shortcut", "Mod+j"); // cmd-j or ctrl-j to open the widget.
+  script.setAttribute("runllm-name", "DaskBot");
+  script.setAttribute("runllm-position", "BOTTOM_RIGHT"); // put above ethical ads
+  script.setAttribute("runllm-position-x", "20px");
+  script.setAttribute("runllm-position-y", "50%");
+  script.setAttribute("runllm-assistant-id", "273");
+  script.setAttribute("runllm-theme-color", "#FFC11E");
+  script.setAttribute("runllm-slack-community-url", "https://dask.slack.com/");
+  script.setAttribute("runllm-per-user-usage-limit", 5);
+  script.setAttribute("runllm-usage-limit-effective-days", 30);
+  script.setAttribute("runllm-usage-limit-message", `Hi! You've hit the limit for questions here in the docs, but you can join us on the Dask community Slack and ask as many questions as you'd like in #DaskBot channel. Hope to see you there.`);
+  script.setAttribute("runllm-brand-logo", "_images/dask_icon.svg");
+  script.setAttribute("runllm-floating-button-text", "Ask DaskBot");
+  script.setAttribute("runllm-join-community-text", "Chat with DaskBot in Slack (join the #DaskBot channel)");
+
+  script.async = true;
+  document.head.appendChild(script);
+});
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 1badbb40c..737cfae6a 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -154,6 +154,8 @@ html_theme_options = {"logo_only": True}
 # so a file named "default.css" will overwrite the builtin "default.css".
 html_static_path = ["_static"]
 
+html_js_files = ["custom.js"]
+
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
 # html_last_updated_fmt = '%b %d, %Y'


Test patch:
diff --git a/.github/workflows/test-report.yaml b/.github/workflows/test-report.yaml
index 1fff48b7d..e84922b58 100644
--- a/.github/workflows/test-report.yaml
+++ b/.github/workflows/test-report.yaml
@@ -60,7 +60,7 @@ jobs:
           mv test_report.html test_short_report.html deploy/
 
       - name: Deploy 🚀
-        uses: JamesIves/github-pages-deploy-action@v4.6.9
+        uses: JamesIves/github-pages-deploy-action@v4.7.2
         with:
           branch: gh-pages
           folder: deploy
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
index c66154eda..cf39de2f6 100644
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -2826,10 +2826,24 @@ def test_asarray_array_dtype(asarray):
     x = asarray([1, 2])
     assert_eq(asarray(x, dtype=da.float32), np.asarray(x, dtype=np.float32))
 
+    # dask->dask
     x = asarray(x, dtype=da.float64)
     assert x.dtype == da.float64
     x = asarray(x, dtype=da.int32)
     assert x.dtype == da.int32
+    x = asarray(x)
+    assert x.dtype == da.int32
+    # Test explicit null dtype. astype(None) converts to float!
+    x = asarray(x, dtype=None)
+    assert x.dtype == da.int32
+
+    # non-dask->dask
+    x = asarray(np.asarray([1, 2], dtype=np.int8))
+    assert x.dtype == da.int8
+    x = asarray(np.asarray([1, 2], dtype=np.int8), dtype=None)
+    assert x.dtype == da.int8
+    x = asarray(np.asarray([1, 2], dtype=np.int8), dtype=da.float64)
+    assert x.dtype == da.float64
 
 
 @pytest.mark.parametrize("asarray", [da.asarray, da.asanyarray])