Repository: great-expectations/great_expectations
Pull request #7074 (2023-02-07 21:17:32)

[FEATURE] Enable `gx.get_context()` to work without any inputs
Changes proposed in this pull request:
```python
import great_expectations as gx

context = gx.get_context() # Should return an EphemeralDataContext with defaults
```


### Definition of Done
- [x] My code follows the Great Expectations [style guide](https://docs.greatexpectations.io/docs/contributing/style_guides/code_style)
- [x] I have performed a [self-review](https://docs.greatexpectations.io/docs/contributing/contributing_checklist) of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have added [unit tests](https://docs.greatexpectations.io/docs/contributing/contributing_test#writing-unit-and-integration-tests) where applicable and made sure that new and existing tests are passing.
- [x] I have run any local integration tests and made sure that nothing is broken.


netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *niobium-lead-7998* processing.


|  Name | Link |
|---------------------------------|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | a4e6394167460905af022809e2a0ba1bcdc11bf6 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/niobium-lead-7998/deploys/63e2bfefb8ba700008b6be1b |
codesee-maps[bot]: <details>
<summary><b>👇 <a href="https://app.codesee.io/r/reviews?pr=7074&src=https%3A%2F%2Fgithub.com%2Fgreat-expectations%2Fgreat_expectations">Click on the image</a> for a new way to code review</b>
</summary>

  - Make big changes easier — review code in small groups of related files
  - Know where to start — see the whole change at a glance
  - Take a code tour — explore the change with an interactive tour
  - Make comments and review — all fully sync’ed with github

    [Try it now!](https://app.codesee.io/r/reviews?pr=7074&src=https%3A%2F%2Fgithub.com%2Fgreat-expectations%2Fgreat_expectations)
</details>

[![Review these changes using an interactive CodeSee Map](https://s3.us-east-2.amazonaws.com/maps.codesee.io/images/github/great-expectations/great_expectations/7074/41ee64eb/a4e6394167460905af022809e2a0ba1bcdc11bf6.svg)](https://app.codesee.io/r/reviews?pr=7074&src=https%3A%2F%2Fgithub.com%2Fgreat-expectations%2Fgreat_expectations)

#### Legend
![CodeSee Map Legend](https://app.codesee.io/Legend_v2.png)

Patch:
diff --git a/great_expectations/cli/project.py b/great_expectations/cli/project.py
index 45a6e4cd6..be79bc75b 100644
--- a/great_expectations/cli/project.py
+++ b/great_expectations/cli/project.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 
 import sys
-from typing import TYPE_CHECKING, Optional, Tuple
 
 import click
 
@@ -13,11 +12,10 @@ from great_expectations.cli.toolkit import load_data_context_with_error_handling
 from great_expectations.cli.upgrade_helpers import GE_UPGRADE_HELPER_VERSION_MAP
 from great_expectations.core.usage_statistics.events import UsageStatsEvents
 from great_expectations.core.usage_statistics.util import send_usage_message
+from great_expectations.data_context.data_context.file_data_context import (
+    FileDataContext,
+)
 from great_expectations.data_context.types.base import CURRENT_GX_CONFIG_VERSION
-from great_expectations.util import get_context
-
-if TYPE_CHECKING:
-    from great_expectations.data_context import AbstractDataContext
 
 
 @click.group()
@@ -72,13 +70,13 @@ def project_upgrade(ctx: click.Context) -> None:
 
 
 def do_config_check(
-    target_directory: Optional[str],
-) -> Tuple[bool, str, Optional[AbstractDataContext]]:
+    target_directory: str | None,
+) -> tuple[bool, str, FileDataContext | None]:
     is_config_ok: bool = True
     upgrade_message: str = ""
-    context: Optional[AbstractDataContext]
+    context: FileDataContext | None = None
     try:
-        context = get_context(context_root_dir=target_directory)
+        context = FileDataContext(context_root_dir=target_directory)
         ge_config_version: int = context.get_config().config_version  # type: ignore[union-attr] # could be dict, str
         if int(ge_config_version) < CURRENT_GX_CONFIG_VERSION:
             is_config_ok = False
diff --git a/great_expectations/data_context/types/base.py b/great_expectations/data_context/types/base.py
index 601c10f56..271b800c5 100644
--- a/great_expectations/data_context/types/base.py
+++ b/great_expectations/data_context/types/base.py
@@ -6,6 +6,7 @@ import enum
 import itertools
 import json
 import logging
+import tempfile
 import uuid
 import warnings
 from typing import (
@@ -2080,6 +2081,7 @@ class InMemoryStoreBackendDefaults(BaseStoreBackendDefaults):
 
     def __init__(
         self,
+        init_temp_docs_sites: bool = False,
     ) -> None:
         # Initialize base defaults
         super().__init__()
@@ -2113,7 +2115,15 @@ class InMemoryStoreBackendDefaults(BaseStoreBackendDefaults):
                 },
             },
         }
-        self.data_docs_sites = {}
+        if init_temp_docs_sites:
+            temp_dir = tempfile.TemporaryDirectory()
+            path = temp_dir.name
+            logger.info(f"Created temporary directory '{path}' for ephemeral docs site")
+            self.data_docs_sites[  # type: ignore[index]
+                DataContextConfigDefaults.DEFAULT_DATA_DOCS_SITE_NAME.value
+            ]["store_backend"]["base_directory"] = path
+        else:
+            self.data_docs_sites = {}
 
 
 class GCSStoreBackendDefaults(BaseStoreBackendDefaults):
diff --git a/great_expectations/util.py b/great_expectations/util.py
index 3af485e99..14b46408a 100644
--- a/great_expectations/util.py
+++ b/great_expectations/util.py
@@ -55,6 +55,7 @@ from packaging import version
 from pkg_resources import Distribution
 from typing_extensions import Literal, TypeGuard
 
+import great_expectations.exceptions as gx_exceptions
 from great_expectations.core._docs_decorators import deprecated_argument, public_api
 from great_expectations.exceptions import (
     GXCloudConfigurationError,
@@ -1873,7 +1874,10 @@ def get_context(
         EphemeralDataContext,
         FileDataContext,
     )
-    from great_expectations.data_context.types.base import DataContextConfig
+    from great_expectations.data_context.types.base import (
+        DataContextConfig,
+        InMemoryStoreBackendDefaults,
+    )
 
     # If available and applicable, convert project_config mapping into a rich config type
     if project_config:
@@ -1926,12 +1930,26 @@ def get_context(
 
     # Second, check for which type of local
     # Prioritize FileDataContext but default to EphemeralDataContext if no context_root_dir
-    if context_root_dir or not project_config:
+    if not context_root_dir:
+        try:
+            context_root_dir = FileDataContext.find_context_root_dir()
+        except gx_exceptions.ConfigNotFoundError:
+            logger.info("Could not find local context root directory")
+
+    if context_root_dir:
         return FileDataContext(
             project_config=project_config,
             context_root_dir=context_root_dir,
             runtime_environment=runtime_environment,
         )
+
+    if not project_config:
+        project_config = DataContextConfig(
+            store_backend_defaults=InMemoryStoreBackendDefaults(
+                init_temp_docs_sites=True
+            )
+        )
+
     return EphemeralDataContext(
         project_config=project_config,
         runtime_environment=runtime_environment,


Test patch:
diff --git a/tests/data_context/test_data_context_data_docs_api.py b/tests/data_context/test_data_context_data_docs_api.py
index a82f2f0cb..a7544e2e7 100644
--- a/tests/data_context/test_data_context_data_docs_api.py
+++ b/tests/data_context/test_data_context_data_docs_api.py
@@ -47,6 +47,18 @@ def test_open_docs_with_single_local_site(mock_webbrowser, empty_data_context):
     )
 
 
+@pytest.mark.unit
+def test_get_context_no_args_successfully_builds_and_opens_docs():
+    context = get_context()
+
+    sites = context.build_data_docs()
+    assert len(sites) == 1
+
+    with mock.patch("webbrowser.open") as mock_open:
+        context.open_data_docs()
+    mock_open.assert_called_once()
+
+
 @pytest.fixture
 def context_with_multiple_built_sites(empty_data_context):
     context = empty_data_context
diff --git a/tests/data_context/test_get_data_context.py b/tests/data_context/test_get_data_context.py
index 056e417eb..79637f5be 100644
--- a/tests/data_context/test_get_data_context.py
+++ b/tests/data_context/test_get_data_context.py
@@ -9,7 +9,10 @@ from great_expectations.data_context.cloud_constants import GXCloudEnvironmentVa
 from great_expectations.data_context.data_context.file_data_context import (
     FileDataContext,
 )
-from great_expectations.data_context.types.base import DataContextConfig
+from great_expectations.data_context.types.base import (
+    DataContextConfig,
+    InMemoryStoreBackendDefaults,
+)
 from great_expectations.exceptions import ConfigNotFoundError
 from tests.test_utils import working_directory
 
@@ -233,3 +236,12 @@ def test_cloud_context_with_in_memory_config_overrides(
 def test_invalid_root_dir_gives_error(clear_env_vars):
     with pytest.raises(ConfigNotFoundError):
         gx.get_context(context_root_dir="i/dont/exist")
+
+
+@pytest.mark.unit
+def test_get_context_with_no_arguments_returns_ephemeral_with_sensible_defaults():
+    context = gx.get_context()
+    assert isinstance(context, EphemeralDataContext)
+
+    defaults = InMemoryStoreBackendDefaults(init_temp_docs_sites=True)
+    assert context.config.stores == defaults.stores