Repository: yt-project/unyt (language: python)

Issue:
3 new wrappable functions (NEP 18)
### Description

bleeding-edge CI is currently failing because numpy has 3 new wrappable functions
- `np.byte_bounts`
- `np.who`
- `np.safe_eval` (unreleased ?)

The first two are probably good left unwrapped, but `np.safe_eval` isn't documented yet (probably new in numpy dev). I'll dig further in later.


Discussion / hints:
actually I see now that all three functions were recently marked as deprecated in numpy dev. I assume they are now detected as wrappable as a side effect of this change. The immediate fix is just to ignore them, but long term it would be better to automatically filter out deprecated functions assuming it's possible.

Fix patch:
diff --git a/.github/workflows/bleeding-edge.yaml b/.github/workflows/bleeding-edge.yaml
index 54cf0bec..83dec0a9 100644
--- a/.github/workflows/bleeding-edge.yaml
+++ b/.github/workflows/bleeding-edge.yaml
@@ -39,15 +39,15 @@ jobs:
 
     - name: Install dependencies
       run: |
-        python3 -m pip install --upgrade pip
-        python3 -m pip install --upgrade setuptools wheel setuptools_scm
-        python3 -m pip install git+https://github.com/numpy/numpy.git
-        python3 -m pip install git+https://github.com/matplotlib/matplotlib.git
-        python3 -m pip install pytest
-        python3 -m pip install --pre sympy
+        python -m pip install --upgrade pip
+        python -m pip install --upgrade setuptools wheel setuptools_scm
+        python -m pip install --pre --extra-index \
+          https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy matplotlib
+        python -m pip install pytest
+        python -m pip install --pre sympy
 
     - name: Build unyt
-      run: python3 -m pip install --no-build-isolation .
+      run: python -m pip install --no-build-isolation .
 
     - name: Run Tests
       run: pytest -vvv unyt/


Test patch:
diff --git a/unyt/tests/test_array_functions.py b/unyt/tests/test_array_functions.py
index a09dfdbb..d1d8cfeb 100644
--- a/unyt/tests/test_array_functions.py
+++ b/unyt/tests/test_array_functions.py
@@ -185,6 +185,37 @@
         NOT_HANDLED_FUNCTIONS.add(getattr(np, func))
 
 
+def get_decorators(func):
+    # adapted from
+    # https://stackoverflow.com/questions/3232024/introspection-to-get-decorator-names-on-a-method
+    import ast
+    import inspect
+
+    target = func
+    decorators = {}
+
+    def visit_FunctionDef(node):
+        decorators[node.name] = []
+        for n in node.decorator_list:
+            name = ""
+            if isinstance(n, ast.Call):
+                name = n.func.attr if isinstance(n.func, ast.Attribute) else n.func.id
+            else:
+                name = n.attr if isinstance(n, ast.Attribute) else n.id
+
+            decorators[node.name].append(name)
+
+    node_iter = ast.NodeVisitor()
+    node_iter.visit_FunctionDef = visit_FunctionDef
+    try:
+        node_iter.visit(ast.parse(inspect.getsource(target)))
+        return decorators[func.__name__]
+    except TypeError:
+        # may be raised if inspecting a C compiled function
+        # in which case, we return with empty hands
+        return []
+
+
 def get_wrapped_functions(*modules):
     """get functions that support __array_function__ in modules
 
@@ -194,7 +225,11 @@ def get_wrapped_functions(*modules):
     for mod in modules:
         for name, f in mod.__dict__.items():
             if callable(f) and hasattr(f, "__wrapped__"):
-                if f is np.printoptions or f.__name__.startswith("_"):
+                if (
+                    f is np.printoptions
+                    or f.__name__.startswith("_")
+                    or "deprecate" in get_decorators(f)
+                ):
                     continue
                 wrapped_functions[mod.__name__ + "." + name] = f
     return dict(sorted(wrapped_functions.items()))