Repository: scikit-hep/awkward

Issue:
Highlevel ak.Array, ak.Record, ak.ArrayBuilder __repr__ and __str__ methods must not have parameters
So the body of these functions have to be moved to new underscored (private) methods that `__repr__` and `__str__` call.


Discussion:

Resolving patch:
diff --git a/src/awkward1/highlevel.py b/src/awkward1/highlevel.py
--- a/src/awkward1/highlevel.py
+++ b/src/awkward1/highlevel.py
@@ -27,12 +27,17 @@ class Array(
     awkward1._connect._numpy.NDArrayOperatorsMixin,
     awkward1._connect._pandas.PandasMixin,
 ):
-    """
+    u"""
     Args:
-        data (#ak.layout.Content, #ak.Array, np.ndarray, str, or iterable):
+        data (#ak.layout.Content, #ak.partition.PartitionedArray, #ak.Array,
+              np.ndarray, pyarrow.*, str, dict, or iterable):
             Data to wrap or convert into an array.
             If a NumPy array, the regularity of its dimensions is preserved
             and the data are viewed, not copied.
+            If a pyarrow object, calls #ak.from_arrow, preserving as much
+            metadata as possible, usually zero-copy.
+            If a dict of str \u2192 columns, combines the columns into an
+            array of records (like Pandas's DataFrame constructor).
             If a string, the data are assumed to be JSON.
             If an iterable, calls #ak.from_iter, which assumes all dimensions
             have irregular lengths.
@@ -189,20 +194,37 @@ def __init__(
             data, (awkward1.layout.Content, awkward1.partition.PartitionedArray)
         ):
             layout = data
+
         elif isinstance(data, Array):
             layout = data.layout
+
         elif isinstance(data, numpy.ndarray) and data.dtype != numpy.dtype("O"):
             layout = awkward1.operations.convert.from_numpy(data, highlevel=False)
-        elif isinstance(data, str):
-            layout = awkward1.operations.convert.from_json(data, highlevel=False)
+
+        elif type(data).__module__ == "pyarrow" or type(data).__module__.startswith("pyarrow."):
+            layout = awkward1.operations.convert.from_arrow(data, highlevel=False)
+
         elif isinstance(data, dict):
-            raise TypeError(
-                "could not convert dict into an awkward1.Array; " "try awkward1.Record"
+            keys = []
+            contents = []
+            for k, v in data.items():
+                keys.append(k)
+                contents.append(Array(v).layout)
+            parameters = None
+            if with_name is not None:
+                parameters = {"__record__": with_name}
+            layout = awkward1.layout.RecordArray(
+                contents, keys, parameters=parameters
             )
+
+        elif isinstance(data, str):
+            layout = awkward1.operations.convert.from_json(data, highlevel=False)
+
         else:
             layout = awkward1.operations.convert.from_iter(
                 data, highlevel=False, allow_record=False
             )
+
         if not isinstance(
             layout, (awkward1.layout.Content, awkward1.partition.PartitionedArray)
         ):
@@ -321,10 +343,16 @@ def __init__(self, array, valid_when):
             self._array = array
             self._valid_when = valid_when
 
-        def __str__(self, limit_value=85):
-            return self._array.__str__(limit_value=limit_value)
+        def __str__(self):
+            return self._str()
 
-        def __repr__(self, limit_value=40, limit_total=85):
+        def __repr__(self):
+            return self._repr()
+
+        def _str(self, limit_value=85):
+            return self._array._str(limit_value=limit_value)
+
+        def _repr(self, limit_value=40, limit_total=85):
             import awkward1.operations.structure
 
             layout = awkward1.operations.structure.with_cache(
@@ -1073,7 +1101,7 @@ def slot9(self):
         """
         return self["9"]
 
-    def __str__(self, limit_value=85):
+    def __str__(self):
         """
         Args:
             limit_value (int): Maximum number of characters to use when
@@ -1126,16 +1154,9 @@ def __str__(self, limit_value=85):
         See #ak.to_list and #ak.to_json to convert whole Arrays into Python
         data or JSON strings without loss (except for #type).
         """
-        import awkward1.operations.structure
-
-        layout = awkward1.operations.structure.with_cache(
-            self._layout, {}, chain="last", highlevel=False
-        )
-        return awkward1._util.minimally_touching_string(
-            limit_value, layout, self._behavior
-        )
+        return self._str()
 
-    def __repr__(self, limit_value=40, limit_total=85):
+    def __repr__(self):
         """
         Args:
             limit_value (int): Maximum number of characters to use when
@@ -1150,6 +1171,19 @@ def __repr__(self, limit_value=40, limit_total=85):
         The #type is truncated as well, but showing only the left side
         of its string (the outermost data structures).
         """
+        return self._repr()
+
+    def _str(self, limit_value=85):
+        import awkward1.operations.structure
+
+        layout = awkward1.operations.structure.with_cache(
+            self._layout, {}, chain="last", highlevel=False
+        )
+        return awkward1._util.minimally_touching_string(
+            limit_value, layout, self._behavior
+        )
+
+    def _repr(self, limit_value=40, limit_total=85):
         import awkward1.operations.structure
 
         layout = awkward1.operations.structure.with_cache(
@@ -1369,19 +1403,24 @@ def __init__(
     ):
         if isinstance(data, awkward1.layout.Record):
             layout = data
+
         elif isinstance(data, Record):
             layout = data.layout
+
         elif isinstance(data, str):
             layout = awkward1.operations.convert.from_json(data, highlevel=False)
+
         elif isinstance(data, dict):
             layout = awkward1.operations.convert.from_iter([data], highlevel=False)[0]
+
         elif isinstance(data, Iterable):
             raise TypeError(
-                "could not convert non-dict into an "
-                "awkward1.Record; try awkward1.Array"
+                "could not convert non-dict into an awkward1.Record; try awkward1.Array"
             )
+
         else:
             layout = None
+
         if not isinstance(layout, awkward1.layout.Record):
             raise TypeError("could not convert data into an awkward1.Record")
 
@@ -1787,7 +1826,7 @@ def slot9(self):
         """
         return self["9"]
 
-    def __str__(self, limit_value=85):
+    def __str__(self):
         """
         Args:
             limit_value (int): Maximum number of characters to use when
@@ -1797,16 +1836,9 @@ def __str__(self, limit_value=85):
 
         See #ak.Array.__str__ for a more complete description.
         """
-        import awkward1.operations.structure
-
-        layout = awkward1.operations.structure.with_cache(
-            self._layout, {}, chain="last", highlevel=False
-        )
-        return awkward1._util.minimally_touching_string(
-            limit_value + 2, layout, self._behavior
-        )[1:-1]
+        return self._str()
 
-    def __repr__(self, limit_value=40, limit_total=85):
+    def __repr__(self):
         """
         Args:
             limit_value (int): Maximum number of characters to use when
@@ -1818,6 +1850,19 @@ def __repr__(self, limit_value=40, limit_total=85):
 
         See #ak.Array.__repr__ for a more complete description.
         """
+        return self._repr()
+
+    def _str(self, limit_value=85):
+        import awkward1.operations.structure
+
+        layout = awkward1.operations.structure.with_cache(
+            self._layout, {}, chain="last", highlevel=False
+        )
+        return awkward1._util.minimally_touching_string(
+            limit_value + 2, layout, self._behavior
+        )[1:-1]
+
+    def _repr(self, limit_value=40, limit_total=85):
         import awkward1.operations.structure
 
         layout = awkward1.operations.structure.with_cache(
@@ -2097,7 +2142,7 @@ def __iter__(self):
         for x in self.snapshot():
             yield x
 
-    def __str__(self, limit_value=85, snapshot=None):
+    def __str__(self):
         """
         Args:
             limit_value (int): Maximum number of characters to use when
@@ -2108,11 +2153,9 @@ def __str__(self, limit_value=85, snapshot=None):
 
         See #ak.Array.__str__ for a more complete description.
         """
-        if snapshot is None:
-            snapshot = self.snapshot()
-        return snapshot.__str__(limit_value=limit_value)
+        return self._str()
 
-    def __repr__(self, limit_value=40, limit_total=85):
+    def __repr__(self):
         """
         Args:
             limit_value (int): Maximum number of characters to use when
@@ -2125,8 +2168,16 @@ def __repr__(self, limit_value=40, limit_total=85):
 
         See #ak.Array.__repr__ for a more complete description.
         """
+        return self._repr()
+
+    def _str(self, limit_value=85, snapshot=None):
+        if snapshot is None:
+            snapshot = self.snapshot()
+        return snapshot._str(limit_value=limit_value)
+
+    def _repr(self, limit_value=40, limit_total=85):
         snapshot = self.snapshot()
-        value = self.__str__(limit_value=limit_value, snapshot=snapshot)
+        value = self._str(limit_value=limit_value, snapshot=snapshot)
 
         limit_type = limit_total - len(value) - len("<ArrayBuilder  type=>")
         typestrs = awkward1._util.typestrs(self._behavior)
@@ -2434,7 +2485,7 @@ def __init__(self, arraybuilder):
 
         def __repr__(self, limit_value=40, limit_total=85):
             snapshot = self._arraybuilder.snapshot()
-            value = self._arraybuilder.__str__(
+            value = self._arraybuilder._str(
                 limit_value=limit_value, snapshot=snapshot
             )
 
diff --git a/src/awkward1/operations/convert.py b/src/awkward1/operations/convert.py
--- a/src/awkward1/operations/convert.py
+++ b/src/awkward1/operations/convert.py
@@ -2604,23 +2604,22 @@ def fill(layout, part):
             )
 
         elif isinstance(layout, awkward1.layout.RecordArray):
-            forms = []
-            for x in layout.contents:
-                forms.append(fill(x, part))
             if layout.istuple:
-                return awkward1.forms.RecordForm(
-                    forms,
-                    has_identities,
-                    parameters,
-                    node_format(key_index),
-                )
+                forms = [fill(x, part) for x in layout.contents]
+                keys = None
             else:
-                return awkward1.forms.RecordForm(
-                    dict(zip(layout.keys(), forms)),
-                    has_identities,
-                    parameters,
-                    node_format(key_index),
-                )
+                forms = []
+                keys = []
+                for k in layout.keys():
+                    forms.append(fill(layout[k], part))
+                    keys.append(k)
+            return awkward1.forms.RecordForm(
+                forms,
+                keys,
+                has_identities,
+                parameters,
+                node_format(key_index),
+            )
 
         elif isinstance(layout, awkward1.layout.RegularArray):
             return awkward1.forms.RegularForm(
@@ -3340,7 +3339,7 @@ def to_pandas(
         ...                        [[7.7]],
         ...                        [[8.8]]]))
                                     values
-        entry subentry subsubentry        
+        entry subentry subsubentry
         0     0        0               1.1
                        1               2.2
               2        0               3.3
@@ -3361,7 +3360,7 @@ def to_pandas(
                 a   b   x
                     i   y
                         z
-        entry            
+        entry
         0       0   0   0
         1      10  10  10
         2      20  20  20
@@ -3380,7 +3379,7 @@ def to_pandas(
         ...                        {"x": [1, 2, 3, 4], "y": []}]),
         ...                        how="inner")
                         x    y
-        entry subentry        
+        entry subentry
         1     0         1  3.3
         2     0         1  2.2
               1         2  1.1
@@ -3395,7 +3394,7 @@ def to_pandas(
         ...                        {"x": [1, 2, 3, 4], "y": []}]),
         ...                        how="outer")
                           x    y
-        entry subentry          
+        entry subentry
         0     0         NaN  4.4
               1         NaN  3.3
               2         NaN  2.2
@@ -3425,7 +3424,10 @@ def to_pandas(
         return out
 
     def recurse(layout, row_arrays, col_names):
-        if layout.purelist_depth > 1:
+        if layout.parameter("__array__") in ("string", "bytestring"):
+            return [(to_numpy(layout), row_arrays, col_names)]
+
+        elif layout.purelist_depth > 1:
             offsets, flattened = layout.offsets_and_flatten(axis=1)
             offsets = numpy.asarray(offsets)
             starts, stops = offsets[:-1], offsets[1:]
@@ -3454,20 +3456,9 @@ def recurse(layout, row_arrays, col_names):
             )
 
         else:
-            try:
-                return [
-                    (
-                        awkward1.operations.convert.to_numpy(layout),
-                        row_arrays,
-                        col_names,
-                    )
-                ]
-            except Exception:
-                return [(layout, row_arrays, col_names)]
+            return [(to_numpy(layout), row_arrays, col_names)]
 
-    layout = awkward1.operations.convert.to_layout(
-        array, allow_record=True, allow_other=False
-    )
+    layout = to_layout(array, allow_record=True, allow_other=False)
     if isinstance(layout, awkward1.partition.PartitionedArray):
         layout = layout.toContent()
 


Test patch:
diff --git a/tests/test_0355-mixins.py b/tests/test_0355-mixins.py
--- a/tests/test_0355-mixins.py
+++ b/tests/test_0355-mixins.py
@@ -6,42 +6,41 @@
 
 import pytest
 
-import numpy as np
-import awkward1 as ak
-from awkward1 import mixin_class, mixin_class_method
+import numpy
+import awkward1
 
 
 def test_make_mixins():
-    @mixin_class(ak.behavior)
+    @awkward1.mixin_class(awkward1.behavior)
     class Point(object):
         def distance(self, other):
-            return np.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
+            return numpy.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
 
-        @mixin_class_method(np.equal, {"Point"})
+        @awkward1.mixin_class_method(numpy.equal, {"Point"})
         def point_equal(self, other):
-            return np.logical_and(self.x == other.x, self.y == other.y)
+            return numpy.logical_and(self.x == other.x, self.y == other.y)
 
-        @mixin_class_method(np.abs)
+        @awkward1.mixin_class_method(numpy.abs)
         def point_abs(self):
-            return np.sqrt(self.x ** 2 + self.y ** 2)
+            return numpy.sqrt(self.x ** 2 + self.y ** 2)
 
-        @mixin_class_method(np.add, {"Point"})
+        @awkward1.mixin_class_method(numpy.add, {"Point"})
         def point_add(self, other):
             print("hi")
-            return ak.zip(
+            return awkward1.zip(
                 {"x": self.x + other.x, "y": self.y + other.y}, with_name="Point",
             )
 
-    @mixin_class(ak.behavior)
+    @awkward1.mixin_class(awkward1.behavior)
     class WeightedPoint(Point):
-        @mixin_class_method(np.equal, {"WeightedPoint"})
+        @awkward1.mixin_class_method(numpy.equal, {"WeightedPoint"})
         def weighted_equal(self, other):
-            return np.logical_and(self.point_equal(other), self.weight == other.weight)
+            return numpy.logical_and(self.point_equal(other), self.weight == other.weight)
 
-        @mixin_class_method(np.add, {"WeightedPoint"})
+        @awkward1.mixin_class_method(numpy.add, {"WeightedPoint"})
         def weighted_add(self, other):
             sumw = self.weight + other.weight
-            return ak.zip(
+            return awkward1.zip(
                 {
                     "x": (self.x * self.weight + other.x * other.weight) / sumw,
                     "y": (self.y * self.weight + other.y * other.weight) / sumw,
@@ -50,7 +49,7 @@ def weighted_add(self, other):
                 with_name="WeightedPoint",
             )
 
-    one = ak.Array(
+    one = awkward1.Array(
         [
             [{"x": 1, "y": 1.1}, {"x": 2, "y": 2.2}, {"x": 3, "y": 3.3}],
             [],
@@ -58,7 +57,7 @@ def weighted_add(self, other):
         ],
         with_name="Point",
     )
-    two = ak.Array(
+    two = awkward1.Array(
         [
             [{"x": 0.9, "y": 1}, {"x": 2, "y": 2.2}, {"x": 2.9, "y": 3}],
             [],
@@ -66,15 +65,15 @@ def weighted_add(self, other):
         ],
         with_name="Point",
     )
-    wone = ak.Array(ak.with_field(one, abs(one), "weight"), with_name="WeightedPoint")
-    wtwo = ak.Array(ak.with_field(two, abs(two), "weight"), with_name="WeightedPoint")
+    wone = awkward1.Array(awkward1.with_field(one, abs(one), "weight"), with_name="WeightedPoint")
+    wtwo = awkward1.Array(awkward1.with_field(two, abs(two), "weight"), with_name="WeightedPoint")
 
-    assert ak.to_list(one + wone) == [
+    assert awkward1.to_list(one + wone) == [
         [{"x": 2, "y": 2.2}, {"x": 4, "y": 4.4}, {"x": 6, "y": 6.6}],
         [],
         [{"x": 8, "y": 8.8}, {"x": 10, "y": 11.0}],
     ]
-    assert ak.to_list(wone + wtwo) == [
+    assert awkward1.to_list(wone + wtwo) == [
         [
             {
                 "x": 0.9524937500390619,
@@ -98,12 +97,12 @@ def weighted_add(self, other):
             {"x": 5.0, "y": 5.5, "weight": 14.866068747318506},
         ],
     ]
-    assert ak.to_list(abs(one)) == [
+    assert awkward1.to_list(abs(one)) == [
         [1.4866068747318506, 2.973213749463701, 4.459820624195552],
         [],
         [5.946427498927402, 7.433034373659253],
     ]
-    assert ak.to_list(one.distance(wtwo)) == [
+    assert awkward1.to_list(one.distance(wtwo)) == [
         [0.14142135623730953, 0.0, 0.31622776601683783],
         [],
         [0.4123105625617664, 0.0],
diff --git a/tests/test_0381-fill-documentation-stubs-3.py b/tests/test_0381-fill-documentation-stubs-3.py
new file mode 100644
--- /dev/null
+++ b/tests/test_0381-fill-documentation-stubs-3.py
@@ -0,0 +1,23 @@
+# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/master/LICENSE
+
+from __future__ import absolute_import
+
+import sys
+
+import pytest
+
+import numpy
+import awkward1
+
+
+def test_pandas_style_constructor():
+    a = awkward1.Array({"x": [1, 2, 3, 4], "y": [[1, 2, 3], [], [4, 5]]})
+    assert awkward1.to_list(a) == [{"x": 1, "y": [1, 2, 3]}, {"x": 2, "y": []}, {"x": 3, "y": [4, 5]}]
+
+
+pyarrow = pytest.importorskip("pyarrow")
+
+
+def test_pyarrow_constructor():
+    a = awkward1.Array(pyarrow.array([[1.1, 2.2, 3.3], [], [4.4, 5.5]]))
+    assert awkward1.to_list(a) == [[1.1, 2.2, 3.3], [], [4.4, 5.5]]