Repository: sdv-dev/RDT (language: python)

Issue:
Create `validate_transformer_code_style` function
A function should be implemented to automatically validate the code style of any new Transformer.

## Function Name and Module

The function should be implemented inside `tests/contributing.py` and should be called `validate_transformer_code_style`

## Inputs

The function should accept a single input:

* `transformer (class or str)`: Transformer class or full Python name of the class (e.g. `DatetimeTransformer` or `"rdt.transformers.time.DatetimeTransformer"`)

## Outputs

The function should return a single `bool` value indicating whether the code style is correct or not.

## Behavior

The function should run all the third party code style checkers (`flake8`, `isort`, `pylint`...) as well custom code analysis checks and report whether all of them pass, as well as feedback about why they do not otherwise.

### List of Checks

The following validations must be performed and reported:



Check | Correct If | What does it check
-- | -- | --
flake8 | flake8 check passes | PEP8 standards and basic static code analysis
isort | isort check passes | Imports are properly sorted
pylint | pylint check passes | Static code analysis and programming style
pydocstyle | pydocstyle check passes | Whether the docstrings are written in the right format
Transformer Name | The Transformer name is correct | Transformer name ends with `Transformer` (e.g. `DatetimeTransformer`)
Unit Tests Exist | The unit tests module exists and is located in the right place | The unit tests module should be located called `tests/unit/transformers/<transformer_family>/<data_type>.py`
Unit Test function names | The unit test function names are correct | The unit test function names should be called `test_<method_name>_<test_case_name>`

### Prints to console

The function prints several messages on the console indicating:
* The path to the source file of the Transformer, which is being analyzed.
* A message indicating whether the code style is correct or not.
* A list of things that were validated and whether they were correct or not (list above).

## Usage Examples

### Success

![image](https://user-images.githubusercontent.com/6129283/134681632-082d2a75-ec57-4fbb-a665-87d346b2899d.png)

### External tool failure

![image1](https://user-images.githubusercontent.com/6129283/134681447-45dbad16-9edf-470a-8ca8-77dc90babc30.png)

### Custom validation failure

![image8](https://user-images.githubusercontent.com/6129283/134681759-2018862c-58d5-4e79-adee-202d72c28667.png)



Discussion / hints:
The current format of our `docstrings` follows the [Google style guide](https://google.github.io/styleguide/pyguide.html), we can add `pydocstyle` and setting the `convention` to `google`. 

However there is a bug in their library which produces `D417 Missing arguments description`, which we do have, and occurs all over our `docstrings` and we will have to ignore the `D417` until this issue is fixed. Reference to the issue: https://github.com/PyCQA/pydocstyle/issues/449

Fix patch:
diff --git a/setup.cfg b/setup.cfg
index 93003fedf..2c6a820f3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,6 +34,7 @@ universal = 1
 [flake8]
 max-line-length = 99
 exclude = docs, .tox, .git, __pycache__, .ipynb_checkpoints, rdt/transformers/__init__.py
+inline-quotes = single
 extend-ignore = D107, SFS3
 per-file-ignores =
     tests/performance/profiling.py:T001
diff --git a/setup.py b/setup.py
index 4d9e67f6c..7a2a67719 100644
--- a/setup.py
+++ b/setup.py
@@ -73,6 +73,7 @@
     'flake8-print>=4.0.0,<4.1',
     'isort>=4.3.4,<5',
     'pylint>=2.5.3,<3',
+    'flake8-quotes>=3.3.0,<4',
     'flake8-expression-complexity>=0.0.9,<0.1',
     'pep8-naming>=0.12.1,<0.13',
 


Test patch:
diff --git a/tests/integration/test_transformers.py b/tests/integration/test_transformers.py
index deb990625..02db3a546 100644
--- a/tests/integration/test_transformers.py
+++ b/tests/integration/test_transformers.py
@@ -200,7 +200,7 @@ def test_transformer(subtests, transformer):
     assert len(dataset_generators) > 0
 
     for dg in dataset_generators:
-        with subtests.test(msg="test_transformer_with_dataset", generator=dg):
+        with subtests.test(msg='test_transformer_with_dataset', generator=dg):
             try:
                 data = pd.DataFrame({TEST_COL: dg.generate(DATA_SIZE)})
             except NotImplementedError:
diff --git a/tests/performance/test_performance.py b/tests/performance/test_performance.py
index 60253fd86..bd448ce27 100644
--- a/tests/performance/test_performance.py
+++ b/tests/performance/test_performance.py
@@ -136,7 +136,7 @@ def test_performance(dataset_generator):
 
 def _round_to_magnitude(value):
     if value == 0:
-        raise ValueError("Value cannot be exactly 0.")
+        raise ValueError('Value cannot be exactly 0.')
 
     for digits in range(-15, 15):
         rounded = np.round(value, digits)
@@ -144,7 +144,7 @@ def _round_to_magnitude(value):
             return rounded
 
     # We should never reach this line
-    raise ValueError("Value is too big")
+    raise ValueError('Value is too big')
 
 
 def find_transformer_boundaries(transformer, dataset_generator, fit_size,
diff --git a/tests/unit/transformers/test_boolean.py b/tests/unit/transformers/test_boolean.py
index 04894fb1c..29cd23f68 100644
--- a/tests/unit/transformers/test_boolean.py
+++ b/tests/unit/transformers/test_boolean.py
@@ -15,8 +15,8 @@ def test___init__(self):
         transformer = BooleanTransformer()
 
         # Asserts
-        self.assertEqual(transformer.nan, -1, "Unexpected nan")
-        self.assertIsNone(transformer.null_column, "null_column is None by default")
+        self.assertEqual(transformer.nan, -1, 'Unexpected nan')
+        self.assertIsNone(transformer.null_column, 'null_column is None by default')
 
     def test__fit_nan_ignore(self):
         """Test _fit nan equal to ignore"""
@@ -33,7 +33,7 @@ def test__fit_nan_ignore(self):
         self.assertEqual(
             transformer.null_transformer.fill_value,
             expect_fill_value,
-            "Unexpected fill value"
+            'Unexpected fill value'
         )
 
     def test__fit_nan_not_ignore(self):
@@ -51,7 +51,7 @@ def test__fit_nan_not_ignore(self):
         self.assertEqual(
             transformer.null_transformer.fill_value,
             expect_fill_value,
-            "Unexpected fill value"
+            'Unexpected fill value'
         )
 
     def test__fit_array(self):
@@ -69,7 +69,7 @@ def test__fit_array(self):
         self.assertEqual(
             transformer.null_transformer.fill_value,
             expect_fill_value,
-            "Unexpected fill value"
+            'Unexpected fill value'
         )
 
     def test__transform_series(self):
@@ -89,7 +89,7 @@ def test__transform_series(self):
         self.assertEqual(
             transformer.null_transformer.transform.call_count,
             expect_call_count,
-            "NullTransformer.transform must be called one time"
+            'NullTransformer.transform must be called one time'
         )
         pd.testing.assert_series_equal(
             transformer.null_transformer.transform.call_args[0][0],
@@ -113,7 +113,7 @@ def test__transform_array(self):
         self.assertEqual(
             transformer.null_transformer.transform.call_count,
             expect_call_count,
-            "NullTransformer.transform must be called one time"
+            'NullTransformer.transform must be called one time'
         )
         pd.testing.assert_series_equal(
             transformer.null_transformer.transform.call_args[0][0],
@@ -139,7 +139,7 @@ def test__reverse_transform_nan_ignore(self):
         self.assertEqual(
             transformer.null_transformer.reverse_transform.call_count,
             expect_call_count,
-            "NullTransformer.reverse_transform should not be called when nan is ignore"
+            'NullTransformer.reverse_transform should not be called when nan is ignore'
         )
 
     def test__reverse_transform_nan_not_ignore(self):
@@ -163,7 +163,7 @@ def test__reverse_transform_nan_not_ignore(self):
         self.assertEqual(
             transformer.null_transformer.reverse_transform.call_count,
             expect_call_count,
-            "NullTransformer.reverse_transform should not be called when nan is ignore"
+            'NullTransformer.reverse_transform should not be called when nan is ignore'
         )
 
     def test__reverse_transform_not_null_values(self):
diff --git a/tests/unit/transformers/test_null.py b/tests/unit/transformers/test_null.py
index 30e9c2c75..ac31de539 100644
--- a/tests/unit/transformers/test_null.py
+++ b/tests/unit/transformers/test_null.py
@@ -16,8 +16,8 @@ def test___init__(self):
         transformer = NullTransformer(None)
 
         # Asserts
-        self.assertIsNone(transformer.null_column, "null_column is None by default")
-        self.assertFalse(transformer.copy, "copy is False by default")
+        self.assertIsNone(transformer.null_column, 'null_column is None by default')
+        self.assertFalse(transformer.copy, 'copy is False by default')
 
     def test_fit_fill_value_mean(self):
         """If fill_value is mean, _fill_value is the mean of the input data."""
@@ -177,9 +177,9 @@ def test_transform_without_nulls(self):
         # Asserts
         expect = np.array([1.5, np.nan, 2.5])
 
-        np.testing.assert_array_equal(result, expect, "Unexpected transformed data")
+        np.testing.assert_array_equal(result, expect, 'Unexpected transformed data')
 
-    @pytest.mark.filterwarnings("ignore")
+    @pytest.mark.filterwarnings('ignore')
     def test_transform_with_nulls_fillvalue_no_copy(self):
         """Test transform data with nulls equal to True and fill_value
 
@@ -201,9 +201,9 @@ def test_transform_with_nulls_fillvalue_no_copy(self):
         # Asserts
         expect = np.array([1.5, 0, 2.5])
 
-        np.testing.assert_array_equal(result, expect, "Unexpected transformed data")
+        np.testing.assert_array_equal(result, expect, 'Unexpected transformed data')
 
-    @pytest.mark.filterwarnings("ignore")
+    @pytest.mark.filterwarnings('ignore')
     def test_transform_with_nulls_fillvalue_copy(self):
         """Test transform data with nulls equal to True and fill_value"""
         # Setup
@@ -221,7 +221,7 @@ def test_transform_with_nulls_fillvalue_copy(self):
         # Asserts
         expect = np.array([1.5, 0, 2.5])
 
-        np.testing.assert_array_equal(result, expect, "Unexpected transformed data")
+        np.testing.assert_array_equal(result, expect, 'Unexpected transformed data')
 
     def test_transform_with_nulls_null_column(self):
         """Test transform data with nulls equal to True and null_column"""
@@ -239,7 +239,7 @@ def test_transform_with_nulls_null_column(self):
         # Asserts
         expect = np.array([[1.5, 0], [np.nan, 1], [2.5, 0]])
 
-        np.testing.assert_array_equal(result, expect, "Unexpected transformed data")
+        np.testing.assert_array_equal(result, expect, 'Unexpected transformed data')
 
     def test_reverse_transform_no_nulls(self):
         """Test reverse_transform with no nulls"""
@@ -255,7 +255,7 @@ def test_reverse_transform_no_nulls(self):
         # Asserts
         expect = pd.Series([1.5, None, 2.5])
 
-        np.testing.assert_array_equal(result, expect, "Unextected reverse data")
+        np.testing.assert_array_equal(result, expect, 'Unextected reverse data')
 
     def test_reverse_transform_nulls_and_null_column(self):
         """Test reverse_transform with nulls and null_column"""
@@ -272,7 +272,7 @@ def test_reverse_transform_nulls_and_null_column(self):
         # Asserts
         expect = pd.Series([1.5, np.nan, 2.5])
 
-        np.testing.assert_array_equal(result, expect, "Unextected reverse data")
+        np.testing.assert_array_equal(result, expect, 'Unextected reverse data')
 
     def test_reverse_transform_nulls_and_not_null_column(self):
         """Test reverse_transform with nulls and not null_column"""
@@ -290,4 +290,4 @@ def test_reverse_transform_nulls_and_not_null_column(self):
         # Asserts
         expect = pd.Series([1.5, np.nan, 2.5])
 
-        np.testing.assert_array_equal(result, expect, "Unextected reverse data")
+        np.testing.assert_array_equal(result, expect, 'Unextected reverse data')