Optionally disallow duplicate labels
This adds a property to NDFrame to disallow duplicate labels. This fixes a vexing issue with using pandas for ETL pipelines, where accidentally introducing duplicate labels can lead to confusing downstream behavior (e.g. `NDFrame.__getitem__` not reducing dimensionality).

When set (via the construction with `allow_duplicate_labels=False` or afterward via `.allows_duplicate_labels=False`), the presence of duplicate labels causes a DuplicateLabelError exception to be raised:

```pytb
In [3]: df = pd.DataFrame({"A": [1, 2]}, index=['a', 'a'], allow_duplicate_labels=False)
---------------------------------------------------------------------------
DuplicateLabelError                       Traceback (most recent call last)
<ipython-input-3-1c8833763dfc> in <module>
----> 1 df = pd.DataFrame({"A": [1, 2]}, index=['a', 'a'], allow_duplicate_labels=False)

~/sandbox/pandas/pandas/core/frame.py in __init__(self, data, index, columns, dtype, copy, allow_duplicate_labels)
    493
    494         NDFrame.__init__(
--> 495             self, mgr, fastpath=True, allow_duplicate_labels=allow_duplicate_labels
    496         )
    497

~/sandbox/pandas/pandas/core/generic.py in __init__(self, data, axes, copy, dtype, allow_duplicate_labels, fastpath)
    202
    203         if not self.allows_duplicate_labels:
--> 204             self._maybe_check_duplicate_labels()
    205
    206     def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):

~/sandbox/pandas/pandas/core/generic.py in _maybe_check_duplicate_labels(self, force)
    240         if force or not self.allows_duplicate_labels:
    241             for ax in self.axes:
--> 242                 ax._maybe_check_unique()
    243
    244     @property

~/sandbox/pandas/pandas/core/indexes/base.py in _maybe_check_unique(self)
    540             # TODO: position, value, not too large.
    541             msg = "Index has duplicates."
--> 542             raise DuplicateLabelError(msg)
    543
    544     # --------------------------------------------------------------------

DuplicateLabelError: Index has duplicates.
```

This property is preserved through operations (using `_metadata` and `__finalize__`).

```pytb
In [4]: df = pd.DataFrame(index=['a', 'A'], allow_duplicate_labels=False)

In [5]: df.rename(str.upper)
---------------------------------------------------------------------------
DuplicateLabelError                       Traceback (most recent call last)
<ipython-input-5-17c8fb0b7c7f> in <module>
----> 1 df.rename(str.upper)
...

~/sandbox/pandas/pandas/core/indexes/base.py in _maybe_check_unique(self)
    540             # TODO: position, value, not too large.
    541             msg = "Index has duplicates."
--> 542             raise DuplicateLabelError(msg)
    543
    544     # --------------------------------------------------------------------

DuplicateLabelError: Index has duplicates.

```

---

### API design questions

- [ ] Do we want to be positive or negative?

```python
pd.Series(..., allow_duplicate_labels=True/False)
pd.Series(..., disallow_duplicate_labels=False/True)
pd.Series(..., require_unique_labels=False/True)
```

- [ ] In my proposal, the argument is different from the property. Do we like that? The rational is the argument is making a statement about what *to* do, while the property is making a statement about what *is* allowed.
```python
In [7]: s = pd.Series(allow_duplicate_labels=False)

In [8]: s.allows_duplicate_labels
Out[8]: False
```

- [ ] I'd like a method-chainable way of saying "duplicates aren't allowed." Some options

```python
# two methods.
s.disallow_duplicate().allow_duplicate()
```

I dislike that in combination with `.allows_duplicates`, since we'd have a property and a method that only differ by an `s`. Perhaps something like

```python
s.update_metdata(allows_duplicates=False)
```

But do people know that "allows_duplicates" is considered metadata?

---

TODO:

- [ ] many, many more tests
- [ ] Propagate metadata in more places (groupby, rolling, etc.)
- [ ] Backwards compatibility in `__finalize__` (we may be changing when we call it, and what we pass)

*Apologies for using the PR for discussion, but we needed a way to compare this to https://github.com/pandas-dev/pandas/pull/28334.*