Repository: plotly/plotly.py
Pull request: bug_2410: Allowing Ints to be passed for rows/cols and refactored int…
## Code PR

- [x] I have read through the [contributing notes](https://github.com/plotly/plotly.py/blob/master/contributing.md) and understand the structure of the package. In particular, if my PR modifies code of `plotly.graph_objects`, my modifications concern the `codegen` files and not generated files.
- [x] I have added tests (if submitting a new feature or correcting a bug) or
  modified existing tests.
- [x] For a new feature, I have added documentation examples in an existing or
  new tutorial notebook (please see the doc checklist as well).
- [x] I have added a CHANGELOG entry if fixing/changing/adding anything substantial.

**Changes in this PR:**

- Now allowing the "add_traces" method to take single integers for the rows/cols parameters.
- Also refactored the numpy integer check to be more reusable, and added more tests


Fixes #2410

Problem statement:
accept int for rows and cols in `fig.add_traces`
At the moment if you want to add several traces to the same subplot you have to pass an iterable to the `rows` and `cols` arguments of `fig.add_traces`. 
```
import plotly.express as px
df = px.data.tips()
fig = px.scatter(df, x='total_bill', y='tip', color='day')
from plotly.subplots import make_subplots
fig2 = make_subplots(1, 2)
fig2.add_traces(fig.data, rows=[1,]*len(fig.data), cols=[1,]*len(fig.data))
fig2.show()
```
We could also accept an int here, in which case all traces would be added to the same subplot.

Hints from issue discussion:
Did you close this because it's working now or because you no longer think this is a good idea?
Because it was fixed by #2451 (sorry for being lazy here!)
so this is the same as https://github.com/plotly/plotly.py/issues/2391 ?
ok I got mixed up here. This is not solved by #2391 (more flexible type of int), it's something different (relaxing the iterable constraint). So I'm reopening it...  
Ah ok, thanks for following up :) should #2391 be closed now then?
@MCBoarder289 if you're interested, solving this issue might involve the same area of the code you were just in :)
@nicolaskruchten Thanks! I'll take a look and see what I can do here
Awesome! Please don't feel any pressure to work on it or anything, I was just flagging it in case you were looking for a (possibly easy) entry-point into another PR here :)

Commit message: bug_2410: Allowing Ints to be passed for rows/cols and refactored int checks

Patch to review:
diff --git a/packages/python/plotly/_plotly_utils/utils.py b/packages/python/plotly/_plotly_utils/utils.py
index c1ba92951da..cbf8d3a6b98 100644
--- a/packages/python/plotly/_plotly_utils/utils.py
+++ b/packages/python/plotly/_plotly_utils/utils.py
@@ -247,3 +247,12 @@ def key(v):
         return tuple(v_parts)
 
     return sorted(vals, key=key, reverse=reverse)
+
+
+def _get_int_type():
+    np = get_module("numpy", should_load=False)
+    if np:
+        int_type = (int, np.integer)
+    else:
+        int_type = (int,)
+    return int_type
diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py
index f520a1669a5..cce3eb86c95 100644
--- a/packages/python/plotly/plotly/basedatatypes.py
+++ b/packages/python/plotly/plotly/basedatatypes.py
@@ -9,7 +9,7 @@
 from contextlib import contextmanager
 from copy import deepcopy, copy
 
-from _plotly_utils.utils import _natural_sort_strings
+from _plotly_utils.utils import _natural_sort_strings, _get_int_type
 from .optional_imports import get_module
 
 # Create Undefined sentinel value
@@ -1560,12 +1560,7 @@ def _validate_rows_cols(name, n, vals):
             if len(vals) != n:
                 BaseFigure._raise_invalid_rows_cols(name=name, n=n, invalid=vals)
 
-            try:
-                import numpy as np
-
-                int_type = (int, np.integer)
-            except ImportError:
-                int_type = (int,)
+            int_type = _get_int_type()
 
             if [r for r in vals if not isinstance(r, int_type)]:
                 BaseFigure._raise_invalid_rows_cols(name=name, n=n, invalid=vals)
@@ -1677,14 +1672,19 @@ def add_traces(self, data, rows=None, cols=None, secondary_ys=None):
                   - All remaining properties are passed to the constructor
                     of the specified trace type.
 
-        rows : None or list[int] (default None)
+        rows : None, list[int], or int (default None)
             List of subplot row indexes (starting from 1) for the traces to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+            If a single integer is added, all traces will be added to row number
+
         cols : None or list[int] (default None)
             List of subplot column indexes (starting from 1) for the traces
             to be added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+            If a single integer is added, all traces will be added to column number
+
+
         secondary_ys: None or list[boolean] (default None)
             List of secondary_y booleans for traces to be added. See the
             docstring for `add_trace` for more info.
@@ -1723,6 +1723,15 @@ def add_traces(self, data, rows=None, cols=None, secondary_ys=None):
         for ind, new_trace in enumerate(data):
             new_trace._trace_ind = ind + len(self.data)
 
+        # Allow integers as inputs to subplots
+        int_type = _get_int_type()
+
+        if isinstance(rows, int_type):
+            rows = [rows] * len(data)
+
+        if isinstance(cols, int_type):
+            cols = [cols] * len(data)
+
         # Validate rows / cols
         n = len(data)
         BaseFigure._validate_rows_cols("rows", n, rows)
diff --git a/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py b/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py
index 2322b30efa9..aac4cfa9723 100644
--- a/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py
+++ b/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py
@@ -63,3 +63,33 @@ def test_add_traces(self):
                 {"type": "histogram2dcontour", "line": {"color": "cyan"}},
             ]
         )
+
+
+class TestAddTracesRowsColsDataTypes(TestCase):
+    def test_add_traces_with_iterable(self):
+        import plotly.express as px
+
+        df = px.data.tips()
+        fig = px.scatter(df, x="total_bill", y="tip", color="day")
+        from plotly.subplots import make_subplots
+
+        fig2 = make_subplots(1, 2)
+        fig2.add_traces(fig.data, rows=[1,] * len(fig.data), cols=[1,] * len(fig.data))
+
+        expected_data_length = 4
+
+        self.assertEqual(expected_data_length, len(fig2.data))
+
+    def test_add_traces_with_integers(self):
+        import plotly.express as px
+
+        df = px.data.tips()
+        fig = px.scatter(df, x="total_bill", y="tip", color="day")
+        from plotly.subplots import make_subplots
+
+        fig2 = make_subplots(1, 2)
+        fig2.add_traces(fig.data, rows=1, cols=2)
+
+        expected_data_length = 4
+
+        self.assertEqual(expected_data_length, len(fig2.data))
diff --git a/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py b/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py
index d35a33d376f..a3732d85256 100644
--- a/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py
+++ b/packages/python/plotly/plotly/tests/test_core/test_utils/test_utils.py
@@ -70,3 +70,28 @@ def test_numpy_integer_import(self):
         value = get_by_path(fig, data_path)
         expected_value = (1,)
         self.assertEqual(value, expected_value)
+
+    def test_get_numpy_int_type(self):
+        import numpy as np
+        from _plotly_utils.utils import _get_int_type
+
+        int_type_tuple = _get_int_type()
+        expected_tuple = (int, np.integer)
+
+        self.assertEqual(int_type_tuple, expected_tuple)
+
+
+class TestNoNumpyIntegerBaseType(TestCase):
+    def test_no_numpy_int_type(self):
+        import sys
+        from _plotly_utils.utils import _get_int_type
+        from _plotly_utils.optional_imports import get_module
+
+        np = get_module("numpy", should_load=False)
+        if np:
+            sys.modules.pop("numpy")
+
+        int_type_tuple = _get_int_type()
+        expected_tuple = (int,)
+
+        self.assertEqual(int_type_tuple, expected_tuple)


Code review comments:
diff_hunk: @@ -1677,14 +1672,19 @@ def add_traces(self, data, rows=None, cols=None, secondary_ys=None):
                   - All remaining properties are passed to the constructor
                     of the specified trace type.
 
-        rows : None or list[int] (default None)
+        rows : None, list[int], or int (default None)
             List of subplot row indexes (starting from 1) for the traces to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+            If a single integer is added, all traces will be added to row number
path: packages/python/plotly/plotly/basedatatypes.py
text: @user1:
```suggestion
            If a single integer is passed, all traces will be added to row number
```

diff_hunk: @@ -1677,14 +1672,19 @@ def add_traces(self, data, rows=None, cols=None, secondary_ys=None):
                   - All remaining properties are passed to the constructor
                     of the specified trace type.
 
-        rows : None or list[int] (default None)
+        rows : None, list[int], or int (default None)
             List of subplot row indexes (starting from 1) for the traces to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+            If a single integer is added, all traces will be added to row number
+
         cols : None or list[int] (default None)
             List of subplot column indexes (starting from 1) for the traces
             to be added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+            If a single integer is added, all traces will be added to column number
path: packages/python/plotly/plotly/basedatatypes.py
text: @user1:
```suggestion
            If a single integer is passed, all traces will be added to column number
```