Repository: regionmask/regionmask

Issue:
add regionmask.set_options
Add global options to regionmask. At first only some display options. However, the main reason is to remove the `method` keyword from the `mask` methods (https://github.com/regionmask/regionmask/issues/122#issuecomment-1193977127). More might follow later.

To show all regions we could then do:
```python
with regionmask.set_options(display_max_rows=None):
     print(srex)
```





Discussion:

Patch:
diff --git a/docs/api.rst b/docs/api.rst
index 3e483e4..502f999 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -16,7 +16,8 @@ Top-level functions
    mask_3D_geopandas
    from_geopandas
    plot_3D_mask
-
+   set_options
+   get_options
 
 Regions
 =======
diff --git a/docs/whats_new.rst b/docs/whats_new.rst
index 2511f55..80c9287 100644
--- a/docs/whats_new.rst
+++ b/docs/whats_new.rst
@@ -29,6 +29,8 @@ Enhancements
 - The masking functions (e.g. :py:meth:`Regions.mask`) now warn if the `units` of the
   coordinates(``lat.attrs["units"]`` ) are given as "radians" (:issue:`279`).
 - Better error when passing a single region without wrapping it into a list or tuple (:issue:`372`).
+- Added :py:class:`set_options` to regionmask which can, currently, be used to control
+  the number of displayed rows of :py:class:`Regions` (:issue:`#376`).
 
 Deprecations
 ~~~~~~~~~~~~
diff --git a/regionmask/__init__.py b/regionmask/__init__.py
index 3482f47..78f2b11 100644
--- a/regionmask/__init__.py
+++ b/regionmask/__init__.py
@@ -2,6 +2,7 @@
 
 from . import core, defined_regions
 from .core._geopandas import from_geopandas, mask_3D_geopandas, mask_geopandas
+from .core.options import get_options, set_options
 from .core.plot import plot_3D_mask
 from .core.regions import Regions, _OneRegion
 
diff --git a/regionmask/core/options.py b/regionmask/core/options.py
new file mode 100644
index 0000000..3f263f0
--- /dev/null
+++ b/regionmask/core/options.py
@@ -0,0 +1,83 @@
+# adapted from xarray under the terms of its license - see licences/XARRAY_LICENSE
+
+
+OPTIONS = {
+    "display_max_rows": 10,
+}
+
+
+def _optional_positive_integer(name: str, value: int) -> bool:
+
+    if not (value is None or isinstance(value, int) and value > 0):
+        raise ValueError(f"'{name}' must be a positive integer or None, got '{value}'")
+
+
+_VALIDATORS = {
+    "display_max_rows": _optional_positive_integer,
+}
+
+
+class set_options:
+    """
+    Set options for regionmask in a controlled context.
+
+    Parameters
+    ----------
+    display_max_rows : int, default: 10
+        Maximum display rows.
+
+    Examples
+    --------
+    It is possible to use ``set_options`` either as a context manager:
+
+    >>> srex = regionmask.defined_regions.srex
+    >>> with regionmask.set_options(display_max_rows=2):
+    ...     print(srex)
+    <regionmask.Regions 'SREX'>
+    Source:   Seneviratne et al., 2012 (https://www.ipcc.ch/site/assets/uploads/2...
+    overlap:  False
+
+    Regions:
+    1 ALA       Alaska/N.W. Canada
+    ..  ..                      ...
+    26 SAU S. Australia/New Zealand
+
+    [26 regions]
+    Or to set global options:
+
+    >>> regionmask.set_options(display_max_rows=None)  # doctest: +ELLIPSIS
+    <regionmask.core.options.set_options at 0x...>
+    """
+
+    def __init__(self, **kwargs):
+        self.old = {}
+        for key, value in kwargs.items():
+            if key not in OPTIONS:
+                raise ValueError(
+                    f"{key!r} is not in the set of valid options {set(OPTIONS)!r}"
+                )
+
+            _VALIDATORS[key](key, value)
+
+            self.old[key] = OPTIONS[key]
+        self._apply_update(kwargs)
+
+    def _apply_update(self, options_dict):
+        OPTIONS.update(options_dict)
+
+    def __enter__(self):
+        return
+
+    def __exit__(self, type, value, traceback):
+        self._apply_update(self.old)
+
+
+def get_options():
+    """
+    Get options for regionmask.
+
+    See Also
+    ----------
+    set_options
+    """
+    return OPTIONS
diff --git a/regionmask/core/regions.py b/regionmask/core/regions.py
index 71654c1..4adefaf 100644
--- a/regionmask/core/regions.py
+++ b/regionmask/core/regions.py
@@ -175,9 +175,6 @@ class Regions:
 
         return key
 
-    def __repr__(self):  # pragma: no cover
-        return self._display()
-
     def __iter__(self):
         for i in self.numbers:
             yield self[i]
@@ -261,7 +258,7 @@ class Regions:
         """if the regions extend from 0 to 360"""
         return not self.lon_180
 
-    def _display(self, max_rows=10, max_width=None, max_colwidth=50):
+    def _display(self, max_rows=10, max_width=None):
         """Render ``Regions`` object to a console-friendly tabular output.
 
         Parameters
@@ -271,8 +268,6 @@ class Regions:
             not affect the displayed metadata.
         max_width : int, optional
             Width to wrap a line in characters. If none uses console width.
-        max_colwidth : int, optional
-            Max width to truncate each column in characters. Default 50.
 
         Returns
         -------
@@ -286,6 +281,13 @@ class Regions:
         """
         return _display(self, max_rows, max_width)
 
+    def __repr__(self):  # pragma: no cover
+        from .options import OPTIONS
+
+        max_rows = OPTIONS["display_max_rows"]
+
+        return self._display(max_rows=max_rows)
+
     @_deprecate_positional_args("0.10.0")
     def mask(
         self,


Test patch:
diff --git a/regionmask/tests/test_options.py b/regionmask/tests/test_options.py
new file mode 100644
index 0000000..70f0ad3
--- /dev/null
+++ b/regionmask/tests/test_options.py
@@ -0,0 +1,31 @@
+import pytest
+
+import regionmask
+
+
+def test_options_display_max_rows_errors():
+
+    default = regionmask.core.options.OPTIONS["display_max_rows"]
+    assert default == 10
+
+    with pytest.raises(ValueError, match="'display_max_rows' must be a positive"):
+        regionmask.set_options(display_max_rows=0)
+
+    with pytest.raises(ValueError, match="'display_max_rows' must be a positive"):
+        regionmask.set_options(display_max_rows=-3)
+
+    with pytest.raises(ValueError, match="'display_max_rows' must be a positive"):
+        regionmask.set_options(display_max_rows=3.5)
+
+
+@pytest.mark.parametrize("n, expected", [(3, 3), (5, 5), (8, 9), (25, 25), (None, 26)])
+def test_options_display_max_rows(n, expected):
+    # NOTE: pandas has a strange behaviour here
+
+    from regionmask.defined_regions import srex
+
+    with regionmask.set_options(display_max_rows=n):
+        result = srex.__repr__()
+        result = len(result.split("\n"))
+
+        assert result == expected + 6 + 1