Repository: IAMconsortium/pyam

Issue:
When appending inconsistent dfs in the new pyam, inplace=True suppresses errors
As the title indicates. the new pyam appears to produce a viable output when appending dfs with different numbers of meta columns. It will discover it is broken at a later point. 
Example code: 

import pyam
import pandas as pd
_mc = "model_c"
_sa = "scen_a"
_sb = "scen_b"
_eco2 = "Emissions|CO2"
_gtc = "Gt C/yr"
_ech4 = "Emissions|CH4"
_mtch4 = "Mt CH4/yr"
_msrvu = ["model", "scenario", "region", "variable", "unit"]
simple_df = pd.DataFrame(
    [
        [_mc, _sa, "World", _eco2, _gtc, 0, 1000, 5000],
        [_mc, _sb, "World", _eco2, _gtc, 1, 1000, 5000],
        [_mc, _sa, "World", _ech4, _mtch4, 0, 300, 500],
        [_mc, _sb, "World", _ech4, _mtch4, 1, 300, 500],
    ],
    columns=_msrvu + [2010, 2030, 2050],
)
simple_df_2 = pyam.IamDataFrame(simple_df)
simple_df["model"] = simple_df["model"] + "_original"
simple_df["meta"] = "Joke goes here"
simple_df = pyam.IamDataFrame(simple_df)

# an error is produced if I add inconsistent dataframes normally:
try: 
    simple_df.append(simple_df_2)
except Exception as e:
    print(e)
# But no error if inplace.
simple_df.append(simple_df_2, inplace=True)
# It is quietly broken
simple_df.variables()

Discussion:
Rlamboll: Looks good. It might be best to test that the error message produced is the intended one, since these sorts of things can go wrong in a lot of ways. 
danielhuppmann: Added explicit checks for the error messages per suggestion by @Rlamboll 

Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 6044222..a0f58b8 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -11,6 +11,7 @@ via getter and setter functions.
 
 ## Individual updates
 
+- [#436](https://github.com/IAMconsortium/pyam/pull/436) Raise an error with appending mismatching timeseries index dimensions
 - [#427](https://github.com/IAMconsortium/pyam/pull/427) Add an `info()` function and use in `print(IamDataFrame)`
 - [#424](https://github.com/IAMconsortium/pyam/pull/424) Add a tutorial reading results from a GAMS model (via a gdx file).
 - [#420](https://github.com/IAMconsortium/pyam/pull/420) Add a `_data` object (implemented as a pandas.Series) to handle timeseries data internally.
diff --git a/pyam/core.py b/pyam/core.py
index 648e10e..4f05b01 100755
--- a/pyam/core.py
+++ b/pyam/core.py
@@ -322,7 +322,6 @@ class IamDataFrame(object):
             .drop_duplicates().sort_values('variable').reset_index(drop=True)
         )
 
-
     def append(self, other, ignore_meta_conflict=False, inplace=False,
                **kwargs):
         """Append any IamDataFrame-like object to this object
@@ -334,22 +333,37 @@ class IamDataFrame(object):
         Parameters
         ----------
         other : IamDataFrame, ixmp.Scenario, pandas.DataFrame or data file
-            any object castable as IamDataFrame to be appended
+            Any object castable as IamDataFrame to be appended
         ignore_meta_conflict : bool, default False
-            if False and `other` is an IamDataFrame, raise an error if
+            If False and `other` is an IamDataFrame, raise an error if
             any meta columns present in `self` and `other` are not identical.
         inplace : bool, default False
-            if True, do operation inplace and return None
+            If True, do operation inplace and return None
         kwargs
-            passed to :class:`IamDataFrame(other, **kwargs) <IamDataFrame>`
+            Passed to :class:`IamDataFrame(other, **kwargs) <IamDataFrame>`
             if `other` is not already an IamDataFrame
+
+        Returns
+        -------
+        IamDataFrame
+            If *inplace* is :obj:`False`.
+        None
+            If *inplace* is :obj:`True`.
+
+        Raises
+        ------
+        ValueError
+            If time domain or other timeseries data index dimension don't match
         """
         if not isinstance(other, IamDataFrame):
             other = IamDataFrame(other, **kwargs)
             ignore_meta_conflict = True
 
         if self.time_col != other.time_col:
-            raise ValueError('incompatible time format (years vs. datetime)!')
+            raise ValueError('Incompatible time format (`year` vs. `time`)')
+
+        if self._data.index.names != other._data.index.names:
+            raise ValueError('Incompatible timeseries data index dimensions')
 
         ret = self.copy() if not inplace else self
 


Test patch:
diff --git a/tests/test_feature_append_rename.py b/tests/test_feature_append_rename.py
index dc4a284..0a95605 100644
--- a/tests/test_feature_append_rename.py
+++ b/tests/test_feature_append_rename.py
@@ -129,9 +129,20 @@ def test_append_extra_col(test_df, shuffle_cols):
     check_meta_is(res, "col_2", "bye")
 
 
-def test_append_duplicates(test_df_year):
+def test_append_duplicates_raises(test_df_year):
+    # Merging objects with overlapping values (merge conflict) raises an error
     other = copy.deepcopy(test_df_year)
-    pytest.raises(ValueError, test_df_year.append, other=other)
+    with pytest.raises(ValueError, match='Indexes have overlapping values:'):
+        test_df_year.append(other=other)
+
+
+def test_append_incompatible_col_raises(test_pd_df):
+    # Merging objects with different data index dimensions raises an error
+    df = IamDataFrame(test_pd_df)
+    test_pd_df['foo'] = 'baz'
+    other = IamDataFrame(test_pd_df)
+    with pytest.raises(ValueError, match='Incompatible timeseries data index'):
+        df.append(other=other)
 
 
 def test_rename_data_cols_by_dict():