Repository: alteryx/woodwork (python)

Issue:
@property does not pass from Schema to Accessor
We access Schema `@properties` like `index`, `semantic_tags`, etc via the WoodworkTableAccessor with a `__getattr__` that makes the Schema call. However, the `property` is not being propagated, so users can set those values and interact with them in ways that they couldn't if they directly accessed them through the Schema.

Here's an example of behavior that we don't want to be possible:
```python
df.ww.init()
assert df.ww.index is None

df.ww.index = 'fake_column'

assert df.ww.index == 'fake_column'
assert df.ww.schema.index is None

df.ww.set_index('id')
assert df.ww.index == 'fake_column'
assert df.ww.schema.index == 'id'
```

We can check whether a attr being accessed is a `property` with the following `isinstance(getattr(Schema, attr), property)`, but I'm not sure how we'd go about making that attr a property on the WoodworkTableAccessor class programmatically.

We may need to define the necessary Schema properties on the Accessor. Whether that would mean keeping the properties on the Schema class and wrapping them in the Accessor class or defining them fully on the Accessor is tbd. Likely, we'll still want those properties on the Schema class so that users can interact with a Schema by itself.

Worth noting that to have `index` and `time_index` setters on the Table Accessor, we need to have corresponding getters. 

Pull request description:
Add schema properties to WoodworkTableAccessor
- We weren't getting proper property behavior with attributes that are properties on the Schema but are getting pulled in the WoodworkTableAccessor's getattr, so by adding them as properties on the WoodworkTableAccessor, we get the benefit of not being able to set those properties directly unless we define a setter, which is important for things like `index` and `time_index`
- Closes #602 

Patch:
diff --git a/docs/source/api_reference.rst b/docs/source/api_reference.rst
index dea1466..8c46ef1 100644
--- a/docs/source/api_reference.rst
+++ b/docs/source/api_reference.rst
@@ -19,6 +19,7 @@ WoodworkTableAccessor
     WoodworkTableAccessor.mutual_information_dict
     WoodworkTableAccessor.mutual_information
     WoodworkTableAccessor.pop
+    WoodworkTableAccessor.rename
     WoodworkTableAccessor.select
     WoodworkTableAccessor.set_index
     WoodworkTableAccessor.set_types
@@ -54,6 +55,7 @@ Schema
 
     Schema
     Schema.add_semantic_tags
+    Schema.rename
     Schema.remove_semantic_tags
     Schema.reset_semantic_tags
     Schema.set_index
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index c35b6ed..1375fb2 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -37,6 +37,7 @@ Release Notes
         * Add ``drop`` to WoodworkTableAccessor (:pr:`640`)
         * Add ``rename`` to WoodworkTableAccessor (:pr:`646`)
         * Add DaskTableAccessor (:pr:`648`)
+        * Add Schema properties to WoodworkTableAccessor (:pr:`651`)
     * Fixes
         * Create new Schema object when performing pandas operation on Accessors (:pr:`595`)
     * Changes
diff --git a/woodwork/table_accessor.py b/woodwork/table_accessor.py
index 4c5b106..1799cf2 100644
--- a/woodwork/table_accessor.py
+++ b/woodwork/table_accessor.py
@@ -208,6 +208,37 @@ class WoodworkTableAccessor:
         if self._schema:
             return self._schema._get_subset_schema(list(self.columns.keys()))
 
+    @property
+    def types(self):
+        """DataFrame containing the physical dtypes, logical types and semantic
+        tags for the Schema."""
+        return self._schema.types
+
+    @property
+    def logical_types(self):
+        """A dictionary containing logical types for each column"""
+        return self._schema.logical_types
+
+    @property
+    def physical_types(self):
+        """A dictionary containing physical types for each column"""
+        return self._schema.physical_types
+
+    @property
+    def semantic_tags(self):
+        """A dictionary containing semantic tags for each column"""
+        return self._schema.semantic_tags
+
+    @property
+    def index(self):
+        """The index column for the table"""
+        return self._schema.index
+
+    @property
+    def time_index(self):
+        """The time index column for the table"""
+        return self._schema.time_index
+
     def set_index(self, new_index):
         """Sets the index column of the DataFrame. Adds the 'index' semantic tag to the column
         and clears the tag from any previously set index column.


Test patch:
diff --git a/woodwork/tests/accessor/test_table_accessor.py b/woodwork/tests/accessor/test_table_accessor.py
index 3cfa83d..a40cf94 100644
--- a/woodwork/tests/accessor/test_table_accessor.py
+++ b/woodwork/tests/accessor/test_table_accessor.py
@@ -1606,3 +1606,25 @@ def test_accessor_rename_indices(sample_df):
 
     assert renamed_df.ww.index == 'renamed_index'
     assert renamed_df.ww.time_index == 'renamed_time_index'
+
+
+def test_accessor_schema_properties(sample_df):
+    xfail_koalas(sample_df)
+
+    sample_df.ww.init(index='id',
+                      time_index='signup_date')
+
+    schema_properties = ['types', 'logical_types', 'physical_types', 'semantic_tags', 'index', 'time_index']
+    for schema_property in schema_properties:
+        prop_from_accessor = getattr(sample_df.ww, schema_property)
+        prop_from_schema = getattr(sample_df.ww.schema, schema_property)
+
+        if schema_property == 'types':
+            pd.testing.assert_frame_equal(prop_from_accessor, prop_from_schema)
+        else:
+            assert prop_from_accessor == prop_from_schema
+
+        # Assumes we don't have setters for any of these attributes
+        error = "can't set attribute"
+        with pytest.raises(AttributeError, match=error):
+            setattr(sample_df.ww, schema_property, 'new_value')