Repository: great-expectations/great_expectations

Issue:
Lack of optional dependency crashes init
When I install Great Expectations without SQL Alchemy, `great_expectations init` will fail due to a missing type information.

**To Reproduce**
Steps to reproduce the behavior:

```
docker run -it --rm python:3-slim bash
pip3 install great_expectations
great_expectations init
```

**Expected behavior**
I expected init to run. (After I manually installed sqlachemy, it did work.)

The reason for this is that you allow SQL Alchemy to be not imported (https://github.com/great-expectations/great_expectations/blob/develop/great_expectations/dataset/util.py#L16), but later require its type information (https://github.com/great-expectations/great_expectations/blob/develop/great_expectations/dataset/util.py#L578).

**Environment (please complete the following information):**
 - OS: macOS, Ubuntu
 - GE Version: latest, 0.10.11

**Additional context**

traceback:

<details>
<pre>
$ great_expectations -v init
Traceback (most recent call last):
  File "/Users/okokes/.pyenv/versions/3.7.7/bin/great_expectations", line 5, in <module>
    from great_expectations.cli import main
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/__init__.py", line 7, in <module>
    from great_expectations.data_context import DataContext
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/data_context/__init__.py", line 3, in <module>
    from .data_context import BaseDataContext, DataContext, ExplorerDataContext
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/data_context/data_context.py", line 20, in <module>
    from great_expectations.core.usage_statistics.usage_statistics import (
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/core/usage_statistics/usage_statistics.py", line 20, in <module>
    from great_expectations.core.usage_statistics.anonymizers.data_docs_site_anonymizer import (
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/core/usage_statistics/anonymizers/data_docs_site_anonymizer.py", line 2, in <module>
    from great_expectations.core.usage_statistics.anonymizers.site_builder_anonymizer import (
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/core/usage_statistics/anonymizers/site_builder_anonymizer.py", line 2, in <module>
    from great_expectations.render.renderer.site_builder import (
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/render/renderer/__init__.py", line 7, in <module>
    from .other_section_renderer import ProfilingResultsOverviewSectionRenderer
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/render/renderer/other_section_renderer.py", line 4, in <module>
    from great_expectations.profile.basic_dataset_profiler import BasicDatasetProfiler
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/profile/__init__.py", line 1, in <module>
    from .basic_dataset_profiler import BasicDatasetProfiler
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/profile/basic_dataset_profiler.py", line 3, in <module>
    from great_expectations.profile.base import (
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/profile/base.py", line 8, in <module>
    from ..dataset import Dataset
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/dataset/__init__.py", line 3, in <module>
    from .dataset import Dataset
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/dataset/dataset.py", line 13, in <module>
    from great_expectations.dataset.util import (
  File "/Users/okokes/.pyenv/versions/3.7.7/lib/python3.7/site-packages/great_expectations/dataset/util.py", line 578, in <module>
    selects: List[WithinGroup], sql_engine_dialect: DefaultDialect
NameError: name 'WithinGroup' is not defined
</pre>
</details>

Use cleaner solution for non-truncating division in python 2
Prefer `from __future__ import division` to `1.*x/y`


Discussion:
@kokes SQLAlchemy is one of the optional dependencies in GE. We made the decision to make it optional in order to keep the size of the package smaller and not to force dependencies that some users do not need. Same logic applies to pyspark.

When you run the CLI and choose to connect to a database, the command displays an error message saying that SQLAlchemy is missing. 

We just had a hackathon where one of the projects goes one step beyond displaying the error - the command offers to install SQLAlchemy and keep running. This feels like a more pleasant user experience and we are planning to finalize and merge this change.
@eugmandel I understand that, but GE crashes *before* I connect to a database, before I do anything, really. And it’s not a “I don’t have SQLAlchemy error”, it’s due to a lack of type information.

I even included a trivial way to reproduce the problem. 
Done inline during improve_partitions push.

Resolving patch:
diff --git a/great_expectations/cli/cli.py b/great_expectations/cli/cli.py
--- a/great_expectations/cli/cli.py
+++ b/great_expectations/cli/cli.py
@@ -40,7 +40,7 @@ def cli(verbose):
 
 - great_expectations init : create a new great_expectations project
 
-- great_expectations datasource profile : profile a  datasource
+- great_expectations datasource profile : profile a datasource
 
 - great_expectations docs build : compile documentation from expectations
 """
diff --git a/great_expectations/dataset/sqlalchemy_dataset.py b/great_expectations/dataset/sqlalchemy_dataset.py
--- a/great_expectations/dataset/sqlalchemy_dataset.py
+++ b/great_expectations/dataset/sqlalchemy_dataset.py
@@ -6,7 +6,7 @@
 from datetime import datetime
 from functools import wraps
 from importlib import import_module
-from typing import List
+from typing import List, Any
 
 import numpy as np
 import pandas as pd
@@ -412,7 +412,7 @@ def __init__(
         super().__init__(*args, **kwargs)
 
     @property
-    def sql_engine_dialect(self) -> DefaultDialect:
+    def sql_engine_dialect(self) -> Any:
         return self.engine.dialect
 
     def attempt_allowing_relative_error(self):
@@ -607,7 +607,7 @@ def get_column_quantiles(
     ):
         if self.sql_engine_dialect.name.lower() == "mssql":
             # mssql requires over(), so we add an empty over() clause
-            selects: List[WithinGroup] = [
+            selects = [
                 sa.func.percentile_disc(quantile)
                 .within_group(sa.column(column).asc())
                 .over()
@@ -615,12 +615,12 @@ def get_column_quantiles(
             ]
         elif self.sql_engine_dialect.name.lower() == "bigquery":
             # BigQuery does not support "WITHIN", so we need a special case for it
-            selects: List[WithinGroup] = [
+            selects: List = [
                 sa.func.percentile_disc(sa.column(column), quantile).over()
                 for quantile in quantiles
             ]
         else:
-            selects: List[WithinGroup] = [
+            selects: List = [
                 sa.func.percentile_disc(quantile).within_group(sa.column(column).asc())
                 for quantile in quantiles
             ]
diff --git a/great_expectations/dataset/util.py b/great_expectations/dataset/util.py
--- a/great_expectations/dataset/util.py
+++ b/great_expectations/dataset/util.py
@@ -2,7 +2,7 @@
 
 import logging
 import warnings
-from typing import List
+from typing import List, Any
 
 import numpy as np
 import pandas as pd
@@ -575,7 +575,7 @@ def create_multiple_expectations(df, columns, expectation_type, *args, **kwargs)
 
 
 def get_approximate_percentile_disc_sql(
-    selects: List[WithinGroup], sql_engine_dialect: DefaultDialect
+    selects: List, sql_engine_dialect: Any
 ) -> str:
     return ", ".join(
         [
@@ -591,8 +591,8 @@ def get_approximate_percentile_disc_sql(
 
 
 def check_sql_engine_dialect(
-    actual_sql_engine_dialect: DefaultDialect,
-    candidate_sql_engine_dialect: DefaultDialect,
+    actual_sql_engine_dialect: Any,
+    candidate_sql_engine_dialect: Any,
 ) -> bool:
     try:
         return isinstance(actual_sql_engine_dialect, candidate_sql_engine_dialect)


Test patch:
diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py
--- a/tests/cli/test_cli.py
+++ b/tests/cli/test_cli.py
@@ -33,7 +33,7 @@ def test_cli_command_entrance(caplog):
 
   - great_expectations init : create a new great_expectations project
 
-  - great_expectations datasource profile : profile a  datasource
+  - great_expectations datasource profile : profile a datasource
 
   - great_expectations docs build : compile documentation from expectations