Repository: great-expectations/great_expectations

Issue:
SQL Alchemy error when running the Custom Query Expectation example
Hi, 
Background - we would like to develop custom query expectations (that will be ran on trino). 

I am running the example file for creating Custom Query Expectation as it is, and getting an error **on the 2 last** expectations in the examples: 
```
(sqlite, positive_test_row_condition)
  ERROR: "module 'sqlalchemy.sql' has no attribute 'Subquery'"
  
Traceback (most recent call last):
  File "/Users/venv/lib/python3.7/site-packages/great_expectations/execution_engine/execution_engine.py", line 394, in resolve_metrics
    **metric_provider_kwargs
  File "/Users/venv/lib/python3.7/site-packages/great_expectations/expectations/metrics/metric_provider.py", line 34, in inner_func
    return metric_fn(*args, **kwargs)
  File "/Users/venv/lib/python3.7/site-packages/great_expectations/expectations/metrics/query_metrics/query_table.py", line 47, in _sqlalchemy
    selectable, sa.sql.Subquery
AttributeError: module 'sqlalchemy.sql' has no attribute 'Subquery'
```
**To Reproduce**
Steps to reproduce the behavior:
1. Go to [example](https://github.com/great-expectations/great_expectations/blob/develop/contrib/experimental/great_expectations_experimental/expectations/expect_queried_table_row_count_to_be.py)
2. add to the examples part the required test backends: 
```
 "test_backends": [
                {
                    "backend": "sqlalchemy",
                    "dialects": ["sqlite"],
                }]
```
3. Copy to Pycharm and run it. 
4. By the way - The first 2 tests are passing.

Can you please help? 
thanks a lot!

**Environment (please complete the following information):**
 - Operating System: MacOS
 - Great Expectations Version:  0.15.17




Discussion:
Hey @itaise 👋 

Thanks for reporting this issue. I'll consult with the team and let your know.

Resolving patch:
diff --git a/great_expectations/expectations/metrics/query_metrics/query_column.py b/great_expectations/expectations/metrics/query_metrics/query_column.py
--- a/great_expectations/expectations/metrics/query_metrics/query_column.py
+++ b/great_expectations/expectations/metrics/query_metrics/query_column.py
@@ -17,6 +17,7 @@
 from great_expectations.expectations.metrics.query_metric_provider import (
     QueryMetricProvider,
 )
+from great_expectations.util import get_sqlalchemy_subquery_type
 
 
 class QueryColumn(QueryMetricProvider):
@@ -48,7 +49,7 @@ def _sqlalchemy(
         if isinstance(selectable, sa.Table):
             query = query.format(col=column, active_batch=selectable)
         elif isinstance(
-            selectable, sa.sql.Subquery
+            selectable, get_sqlalchemy_subquery_type()
         ):  # Specifying a runtime query in a RuntimeBatchRequest returns the active bacth as a Subquery; sectioning the active batch off w/ parentheses ensures flow of operations doesn't break
             query = query.format(col=column, active_batch=f"({selectable})")
         elif isinstance(
diff --git a/great_expectations/expectations/metrics/query_metrics/query_table.py b/great_expectations/expectations/metrics/query_metrics/query_table.py
--- a/great_expectations/expectations/metrics/query_metrics/query_table.py
+++ b/great_expectations/expectations/metrics/query_metrics/query_table.py
@@ -17,6 +17,7 @@
 from great_expectations.expectations.metrics.query_metric_provider import (
     QueryMetricProvider,
 )
+from great_expectations.util import get_sqlalchemy_subquery_type
 
 
 class QueryTable(QueryMetricProvider):
@@ -45,8 +46,8 @@ def _sqlalchemy(
         if isinstance(selectable, sa.Table):
             query = query.format(active_batch=selectable)
         elif isinstance(
-            selectable, sa.sql.Subquery
-        ):  # Specifying a runtime query in a RuntimeBatchRequest returns the active bacth as a Subquery; sectioning the active batch off w/ parentheses ensures flow of operations doesn't break
+            selectable, get_sqlalchemy_subquery_type()
+        ):  # Specifying a runtime query in a RuntimeBatchRequest returns the active batch as a Subquery or Alias; sectioning the active batch off w/ parentheses ensures flow of operations doesn't break
             query = query.format(active_batch=f"({selectable})")
         elif isinstance(
             selectable, sa.sql.Select
diff --git a/great_expectations/util.py b/great_expectations/util.py
--- a/great_expectations/util.py
+++ b/great_expectations/util.py
@@ -1574,6 +1574,19 @@ def get_sqlalchemy_selectable(selectable: Union[Table, Select]) -> Union[Table,
     return selectable
 
 
+def get_sqlalchemy_subquery_type():
+    """
+    Beginning from SQLAlchemy 1.4, `sqlalchemy.sql.Alias` has been deprecated in favor of `sqlalchemy.sql.Subquery`.
+    This helper method ensures that the appropriate type is returned.
+
+    https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#change-4617
+    """
+    try:
+        return sa.sql.Subquery
+    except AttributeError:
+        return sa.sql.Alias
+
+
 def get_sqlalchemy_domain_data(domain_data):
     if version.parse(sa.__version__) < version.parse("1.4"):
         # Implicit coercion of SELECT and SELECT constructs is deprecated since 1.4


Test patch: