1import sys
2import warnings
3
4from .utils import FrozenDict
5
6# TODO: Remove this check once python 3.7 is not supported:
7if sys.version_info >= (3, 8):
8    from typing import TYPE_CHECKING, Literal, TypedDict, Union
9else:
10    from typing import TYPE_CHECKING, Union
11
12    from typing_extensions import Literal, TypedDict
13
14
15if TYPE_CHECKING:
16    try:
17        from matplotlib.colors import Colormap
18    except ImportError:
19        Colormap = str
20
21
22class T_Options(TypedDict):
23    arithmetic_join: Literal["inner", "outer", "left", "right", "exact"]
24    cmap_divergent: Union[str, "Colormap"]
25    cmap_sequential: Union[str, "Colormap"]
26    display_max_rows: int
27    display_style: Literal["text", "html"]
28    display_width: int
29    display_expand_attrs: Literal["default", True, False]
30    display_expand_coords: Literal["default", True, False]
31    display_expand_data_vars: Literal["default", True, False]
32    display_expand_data: Literal["default", True, False]
33    enable_cftimeindex: bool
34    file_cache_maxsize: int
35    keep_attrs: Literal["default", True, False]
36    warn_for_unclosed_files: bool
37    use_bottleneck: bool
38
39
40OPTIONS: T_Options = {
41    "arithmetic_join": "inner",
42    "cmap_divergent": "RdBu_r",
43    "cmap_sequential": "viridis",
44    "display_max_rows": 12,
45    "display_style": "html",
46    "display_width": 80,
47    "display_expand_attrs": "default",
48    "display_expand_coords": "default",
49    "display_expand_data_vars": "default",
50    "display_expand_data": "default",
51    "enable_cftimeindex": True,
52    "file_cache_maxsize": 128,
53    "keep_attrs": "default",
54    "use_bottleneck": True,
55    "warn_for_unclosed_files": False,
56}
57
58_JOIN_OPTIONS = frozenset(["inner", "outer", "left", "right", "exact"])
59_DISPLAY_OPTIONS = frozenset(["text", "html"])
60
61
62def _positive_integer(value):
63    return isinstance(value, int) and value > 0
64
65
66_VALIDATORS = {
67    "arithmetic_join": _JOIN_OPTIONS.__contains__,
68    "display_max_rows": _positive_integer,
69    "display_style": _DISPLAY_OPTIONS.__contains__,
70    "display_width": _positive_integer,
71    "display_expand_attrs": lambda choice: choice in [True, False, "default"],
72    "display_expand_coords": lambda choice: choice in [True, False, "default"],
73    "display_expand_data_vars": lambda choice: choice in [True, False, "default"],
74    "display_expand_data": lambda choice: choice in [True, False, "default"],
75    "enable_cftimeindex": lambda value: isinstance(value, bool),
76    "file_cache_maxsize": _positive_integer,
77    "keep_attrs": lambda choice: choice in [True, False, "default"],
78    "use_bottleneck": lambda value: isinstance(value, bool),
79    "warn_for_unclosed_files": lambda value: isinstance(value, bool),
80}
81
82
83def _set_file_cache_maxsize(value):
84    from ..backends.file_manager import FILE_CACHE
85
86    FILE_CACHE.maxsize = value
87
88
89def _warn_on_setting_enable_cftimeindex(enable_cftimeindex):
90    warnings.warn(
91        "The enable_cftimeindex option is now a no-op "
92        "and will be removed in a future version of xarray.",
93        FutureWarning,
94    )
95
96
97_SETTERS = {
98    "enable_cftimeindex": _warn_on_setting_enable_cftimeindex,
99    "file_cache_maxsize": _set_file_cache_maxsize,
100}
101
102
103def _get_boolean_with_default(option, default):
104    global_choice = OPTIONS[option]
105
106    if global_choice == "default":
107        return default
108    elif global_choice in [True, False]:
109        return global_choice
110    else:
111        raise ValueError(
112            f"The global option {option} must be one of True, False or 'default'."
113        )
114
115
116def _get_keep_attrs(default):
117    return _get_boolean_with_default("keep_attrs", default)
118
119
120class set_options:
121    """
122    Set options for xarray in a controlled context.
123
124    Parameters
125    ----------
126    arithmetic_join : {"inner", "outer", "left", "right", "exact"}, default: "inner"
127        DataArray/Dataset alignment in binary operations.
128    cmap_divergent : str or matplotlib.colors.Colormap, default: "RdBu_r"
129        Colormap to use for divergent data plots. If string, must be
130        matplotlib built-in colormap. Can also be a Colormap object
131        (e.g. mpl.cm.magma)
132    cmap_sequential : str or matplotlib.colors.Colormap, default: "viridis"
133        Colormap to use for nondivergent data plots. If string, must be
134        matplotlib built-in colormap. Can also be a Colormap object
135        (e.g. mpl.cm.magma)
136    display_expand_attrs : {"default", True, False}:
137        Whether to expand the attributes section for display of
138        ``DataArray`` or ``Dataset`` objects. Can be
139
140        * ``True`` : to always expand attrs
141        * ``False`` : to always collapse attrs
142        * ``default`` : to expand unless over a pre-defined limit
143    display_expand_coords : {"default", True, False}:
144        Whether to expand the coordinates section for display of
145        ``DataArray`` or ``Dataset`` objects. Can be
146
147        * ``True`` : to always expand coordinates
148        * ``False`` : to always collapse coordinates
149        * ``default`` : to expand unless over a pre-defined limit
150    display_expand_data : {"default", True, False}:
151        Whether to expand the data section for display of ``DataArray``
152        objects. Can be
153
154        * ``True`` : to always expand data
155        * ``False`` : to always collapse data
156        * ``default`` : to expand unless over a pre-defined limit
157    display_expand_data_vars : {"default", True, False}:
158        Whether to expand the data variables section for display of
159        ``Dataset`` objects. Can be
160
161        * ``True`` : to always expand data variables
162        * ``False`` : to always collapse data variables
163        * ``default`` : to expand unless over a pre-defined limit
164    display_max_rows : int, default: 12
165        Maximum display rows.
166    display_style : {"text", "html"}, default: "html"
167        Display style to use in jupyter for xarray objects.
168    display_width : int, default: 80
169        Maximum display width for ``repr`` on xarray objects.
170    file_cache_maxsize : int, default: 128
171        Maximum number of open files to hold in xarray's
172        global least-recently-usage cached. This should be smaller than
173        your system's per-process file descriptor limit, e.g.,
174        ``ulimit -n`` on Linux.
175    keep_attrs : {"default", True, False}
176        Whether to keep attributes on xarray Datasets/dataarrays after
177        operations. Can be
178
179        * ``True`` : to always keep attrs
180        * ``False`` : to always discard attrs
181        * ``default`` : to use original logic that attrs should only
182          be kept in unambiguous circumstances
183    use_bottleneck : bool, default: True
184        Whether to use ``bottleneck`` to accelerate 1D reductions and
185        1D rolling reduction operations.
186    warn_for_unclosed_files : bool, default: False
187        Whether or not to issue a warning when unclosed files are
188        deallocated. This is mostly useful for debugging.
189
190    Examples
191    --------
192    It is possible to use ``set_options`` either as a context manager:
193
194    >>> ds = xr.Dataset({"x": np.arange(1000)})
195    >>> with xr.set_options(display_width=40):
196    ...     print(ds)
197    ...
198    <xarray.Dataset>
199    Dimensions:  (x: 1000)
200    Coordinates:
201      * x        (x) int64 0 1 2 ... 998 999
202    Data variables:
203        *empty*
204
205    Or to set global options:
206
207    >>> xr.set_options(display_width=80)  # doctest: +ELLIPSIS
208    <xarray.core.options.set_options object at 0x...>
209    """
210
211    def __init__(self, **kwargs):
212        self.old = {}
213        for k, v in kwargs.items():
214            if k not in OPTIONS:
215                raise ValueError(
216                    f"argument name {k!r} is not in the set of valid options {set(OPTIONS)!r}"
217                )
218            if k in _VALIDATORS and not _VALIDATORS[k](v):
219                if k == "arithmetic_join":
220                    expected = f"Expected one of {_JOIN_OPTIONS!r}"
221                elif k == "display_style":
222                    expected = f"Expected one of {_DISPLAY_OPTIONS!r}"
223                else:
224                    expected = ""
225                raise ValueError(
226                    f"option {k!r} given an invalid value: {v!r}. " + expected
227                )
228            self.old[k] = OPTIONS[k]
229        self._apply_update(kwargs)
230
231    def _apply_update(self, options_dict):
232        for k, v in options_dict.items():
233            if k in _SETTERS:
234                _SETTERS[k](v)
235        OPTIONS.update(options_dict)
236
237    def __enter__(self):
238        return
239
240    def __exit__(self, type, value, traceback):
241        self._apply_update(self.old)
242
243
244def get_options():
245    """
246    Get options for xarray.
247
248    See Also
249    ----------
250    set_options
251
252    """
253    return FrozenDict(OPTIONS)
254