Repository: plotly/plotly.py

Issue:
Since Plotly 3.5.0 i get KeyError: 'titlefont'
This code worked fine until i updated to `plotly` 3.5.0

```python
        fig['layout'].update(
            title=coin,
            titlefont=dict(
                size=26
            ),
            yaxis=dict(
                title=base_coin,
                titlefont=dict(
                    size=18
                )
            )
        )
```

But since 3.5.0 i get

```
File "/usr/local/lib/python3.7/site-packages/plotly/basedatatypes.py", line 3021, in update
    BaseFigure._perform_update(self, kwargs)
  File "/usr/local/lib/python3.7/site-packages/plotly/basedatatypes.py", line 2249, in _perform_update
    validator = plotly_obj._get_prop_validator(key)
  File "/usr/local/lib/python3.7/site-packages/plotly/basedatatypes.py", line 3706, in _get_prop_validator
    return super(BaseLayoutHierarchyType, self)._get_prop_validator(prop)
  File "/usr/local/lib/python3.7/site-packages/plotly/basedatatypes.py", line 2622, in _get_prop_validator
    return self._validators[prop]
KeyError: 'titlefont'
```

Error seems to be at this line `size=18`
Is this a bug or do i have to change the way i set the title font?

Here is a bit more context if needed
```python
        fig = fif.create_candlestick(o, h, l, c, pd.to_datetime(t, unit='s'))

        fig['layout']['yaxis'].update(
            tickformat=tickformat,
            tickprefix="   ",
            ticksuffix=f"  ")

        fig['layout'].update(
            title=coin,
            titlefont=dict(
                size=26
            ),
            yaxis=dict(
                title=base_coin,
                titlefont=dict(
                    size=18
                )
            )
        )

        fig['layout'].update(
            shapes=[{
                "type": "line",
                "xref": "paper",
                "yref": "y",
                "x0": 0,
                "x1": 1,
                "y0": c[len(c) - 1],
                "y1": c[len(c) - 1],
                "line": {
                    "color": "rgb(50, 171, 96)",
                    "width": 1,
                    "dash": "dot"
                }
            }])

        fig['layout'].update(
            paper_bgcolor='rgb(233,233,233)',
            plot_bgcolor='rgb(233,233,233)',
            autosize=False,
            width=800,
            height=600,
            margin=go.layout.Margin(
                l=margin_l,
                r=50,
                b=85,
                t=100,
                pad=4
            ))
```

EDIT: Actually, i'm not sure if this is only an issue in 3.5.0 since i realized that i previously used 3.3.0


Discussion:
Hi @Endogen ,

Thanks for taking the time to report this.  It looks like a backwards compatibility oversight from the port to plotly.js 1.43 (https://github.com/plotly/plotly.py/pull/1376) that was released in plotly.py 3.5.0.  Here's a minimal reproducible example:

```python
import plotly.graph_objs as go
fig = go.Figure()
fig['layout'].update(
    titlefont=dict(
        size=26
    ),
)
```

For some context, plotly.js rearranged the `title*` properties in version 1.43.0 and moved them under the corresponding `title` object.  So to update the title text and font using the structure in plotly.js 1.43.0 you would do this:

```python
import plotly.graph_objs as go
fig = go.Figure()
fig['layout'].update(
    title=dict(
        text='Title',
        font=dict(
            size=26
        )
    ),
)
```
So if you don't need to keep compatibility with older versions of plotly.py you can go ahead and switch to this form.

That said, the old structure should still work everywhere and it looks like we're not handling something related to the `update` method properly.
> So if you don't need to keep compatibility with older versions of plotly.py you can go ahead and switch to this form.

Ah great! I don't need any compatibility :smile:  Thanks for the MWE - that should have been my job :see_no_evil: 

Resolving patch:
diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py
--- a/plotly/basedatatypes.py
+++ b/plotly/basedatatypes.py
@@ -2619,7 +2619,19 @@ def _get_prop_validator(self, prop):
         -------
         BaseValidator
         """
-        return self._validators[prop]
+
+        # Handle remapping
+        # ----------------
+        if prop in self._mapped_properties:
+            prop_path = self._mapped_properties[prop]
+            plotly_obj = self[prop_path[:-1]]
+            prop = prop_path[-1]
+        else:
+            plotly_obj = self
+
+        # Return validator
+        # ----------------
+        return plotly_obj._validators[prop]
 
     @property
     def parent(self):


Test patch:
diff --git a/plotly/tests/test_core/test_graph_objs/test_graph_objs.py b/plotly/tests/test_core/test_graph_objs/test_graph_objs.py
--- a/plotly/tests/test_core/test_graph_objs/test_graph_objs.py
+++ b/plotly/tests/test_core/test_graph_objs/test_graph_objs.py
@@ -59,6 +59,14 @@ def test_title_as_string_layout(self):
             self.assertEqual(obj.to_plotly_json(),
                              {'title': {'text': 'A title 2'}})
 
+            # Update titlefont
+            obj.update(titlefont={'size': 23})
+            self.assertEqual(obj.title.font.size, 23)
+            self.assertEqual(obj.to_plotly_json(),
+                             {'title':
+                                  {'text': 'A title 2',
+                                   'font': {'size': 23}}})
+
         # Pie
         obj = go.Pie()
         obj.title = 'A title'
@@ -67,6 +75,22 @@ def test_title_as_string_layout(self):
                          {'title': {'text': 'A title'},
                           'type': 'pie'})
 
+        # And update
+        obj.update(title='A title 2')
+        self.assertEqual(obj.title.text, 'A title 2')
+        self.assertEqual(obj.to_plotly_json(),
+                         {'type': 'pie',
+                          'title': {'text': 'A title 2'}})
+
+        # Update titlefont
+        obj.update(titlefont={'size': 23})
+        self.assertEqual(obj.title.font.size, 23)
+        self.assertEqual(obj.to_plotly_json(),
+                         {'type': 'pie',
+                          'title':
+                              {'text': 'A title 2',
+                               'font': {'size': 23}}})
+
     def test_legacy_title_props_remapped(self):
 
         # plain Layout