Repository: great-expectations/great_expectations

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


Discussion:
Done inline during improve_partitions push.

Resolving patch:
diff --git a/docs/sphinx_api_docs_source/public_api_report.py b/docs/sphinx_api_docs_source/public_api_report.py
--- a/docs/sphinx_api_docs_source/public_api_report.py
+++ b/docs/sphinx_api_docs_source/public_api_report.py
@@ -1843,7 +1843,7 @@ def main():
     # any methods or classes you are adding to documentation with the @public_api
     # decorator and any relevant "new" or "deprecated" public api decorators.
     # If the actual is lower than the threshold, please reduce the threshold.
-    PUBLIC_API_MISSING_THRESHOLD = 99  # TODO: reduce this number again once this works for the Fluent DS dynamic methods
+    PUBLIC_API_MISSING_THRESHOLD = 98  # TODO: reduce this number again once this works for the Fluent DS dynamic methods
     if len(printable_definitions) != PUBLIC_API_MISSING_THRESHOLD:
         error_msg_prefix = f"There are {len(printable_definitions)} items missing from the public API, we currently allow {PUBLIC_API_MISSING_THRESHOLD}."
         if len(printable_definitions) > PUBLIC_API_MISSING_THRESHOLD:
diff --git a/great_expectations/datasource/fluent/config.py b/great_expectations/datasource/fluent/config.py
--- a/great_expectations/datasource/fluent/config.py
+++ b/great_expectations/datasource/fluent/config.py
@@ -1,7 +1,6 @@
 """POC for loading config."""
 from __future__ import annotations
 
-import json
 import logging
 import pathlib
 from io import StringIO
@@ -24,10 +23,7 @@
 from ruamel.yaml import YAML
 from typing_extensions import Final
 
-from great_expectations.datasource.fluent.fluent_base_model import (
-    FluentBaseModel,
-    _recursively_set_config_value,
-)
+from great_expectations.datasource.fluent.fluent_base_model import FluentBaseModel
 from great_expectations.datasource.fluent.interfaces import (
     Datasource,  # noqa: TCH001
 )
@@ -40,7 +36,6 @@
 if TYPE_CHECKING:
     from pydantic.error_wrappers import ErrorDict as PydanticErrorDict
 
-    from great_expectations.core.config_provider import _ConfigurationProvider
     from great_expectations.datasource.fluent.fluent_base_model import (
         AbstractSetIntStr,
         MappingIntStrAny,
@@ -96,28 +91,42 @@ def _load_datasource_subtype(cls, v: Dict[str, dict]):
         logger.info(f"Loading 'datasources' ->\n{pf(v, depth=2)}")
         loaded_datasources: Dict[str, Datasource] = {}
 
-        for ds_name, config in v.items():
+        for ds_key, config in v.items():
             ds_type_name: str = config.get("type", "")
             if not ds_type_name:
                 # TODO: (kilo59 122222) ideally this would be raised by `Datasource` validation
                 # https://github.com/pydantic/pydantic/issues/734
-                raise ValueError(f"'{ds_name}' is missing a 'type' entry")
+                raise ValueError(f"'{ds_key}' is missing a 'type' entry")
 
             try:
                 ds_type: Type[Datasource] = _SourceFactories.type_lookup[ds_type_name]
-                logger.debug(f"Instantiating '{ds_name}' as {ds_type}")
+                logger.debug(f"Instantiating '{ds_key}' as {ds_type}")
             except KeyError as type_lookup_err:
                 raise ValueError(
-                    f"'{ds_name}' has unsupported 'type' - {type_lookup_err}"
+                    f"'{ds_key}' has unsupported 'type' - {type_lookup_err}"
                 ) from type_lookup_err
 
-            config["name"] = ds_name
+            if "name" in config:
+                ds_name: str = config["name"]
+                if ds_name != ds_key:
+                    raise ValueError(
+                        f'Datasource key "{ds_key}" is different from name "{ds_name}" in its configuration.'
+                    )
+            else:
+                config["name"] = ds_key
 
             if "assets" not in config:
                 config["assets"] = {}
 
-            for asset_name, asset_config in config["assets"].items():
-                asset_config["name"] = asset_name
+            for asset_key, asset_config in config["assets"].items():
+                if "name" in asset_config:
+                    asset_name: str = asset_config["name"]
+                    if asset_name != asset_key:
+                        raise ValueError(
+                            f'DataAsset key "{asset_key}" is different from name "{asset_name}" in its configuration.'
+                        )
+                else:
+                    asset_config["name"] = asset_key
 
             datasource = ds_type(**config)
 
@@ -253,120 +262,6 @@ def yaml(
 
         return stream_or_path.getvalue()
 
-    def json(
-        self,
-        *,
-        include: AbstractSetIntStr | MappingIntStrAny | None = None,
-        exclude: AbstractSetIntStr | MappingIntStrAny | None = None,
-        by_alias: bool = False,
-        # deprecated - use exclude_unset instead
-        skip_defaults: bool | None = None,
-        # Default to True to prevent serializing long configs full of unset default values
-        exclude_unset: bool = True,
-        exclude_defaults: bool = False,
-        exclude_none: bool = False,
-        encoder: Callable[[Any], Any] | None = None,
-        models_as_dict: bool = True,
-        **dumps_kwargs: Any,
-    ) -> str:
-        """
-        Generate a JSON representation of the model, `include` and `exclude` arguments
-        as per `dict()`.
-        `encoder` is an optional function to supply as `default` to json.dumps(), other
-        arguments as per `json.dumps()`.
-        Deviates from pydantic `exclude_unset` `True` by default instead of `False` by
-        default.
-        """
-        intermediate_json_dict = self._json_dict(
-            include=include,
-            exclude=exclude,
-            by_alias=by_alias,
-            exclude_unset=exclude_unset,
-            exclude_defaults=exclude_defaults,
-            exclude_none=exclude_none,
-            encoder=encoder,
-            models_as_dict=models_as_dict,
-        )
-        intermediate_json_dict = self._exclude_name_fields_from_fluent_datasources(
-            config=intermediate_json_dict
-        )
-        return self.__config__.json_dumps(
-            intermediate_json_dict, default=encoder, **dumps_kwargs
-        )
-
-    def _json_dict(
-        self,
-        *,
-        include: Union[AbstractSetIntStr, MappingIntStrAny, None] = None,
-        exclude: Union[AbstractSetIntStr, MappingIntStrAny, None] = None,
-        by_alias: bool = False,
-        exclude_unset: bool = True,
-        exclude_defaults: bool = False,
-        exclude_none: bool = False,
-        encoder: Union[Callable[[Any], Any], None] = None,
-        models_as_dict: bool = True,
-        **dumps_kwargs,
-    ) -> dict:
-        """
-        JSON compatible dictionary. All complex types removed.
-        Prefer `.dict()` or `.json()`
-        """
-        return json.loads(
-            super().json(
-                include=include,
-                exclude=exclude,
-                by_alias=by_alias,
-                exclude_unset=exclude_unset,
-                exclude_defaults=exclude_defaults,
-                exclude_none=exclude_none,
-                encoder=encoder,
-                models_as_dict=models_as_dict,
-                **dumps_kwargs,
-            )
-        )
-
-    def dict(
-        self,
-        *,
-        include: AbstractSetIntStr | MappingIntStrAny | None = None,
-        exclude: AbstractSetIntStr | MappingIntStrAny | None = None,
-        by_alias: bool = False,
-        # Default to True to prevent serializing long configs full of unset default values
-        exclude_unset: bool = True,
-        exclude_defaults: bool = False,
-        exclude_none: bool = False,
-        # deprecated - use exclude_unset instead
-        skip_defaults: bool | None = None,
-        # custom
-        config_provider: _ConfigurationProvider | None = None,
-    ) -> dict[str, Any]:
-        """
-        Generate a dictionary representation of the model, optionally specifying which
-        fields to include or exclude.
-        Deviates from pydantic `exclude_unset` `True` by default instead of `False` by
-        default.
-        """
-        intermediate_dict: dict = super().dict(
-            include=include,
-            exclude=exclude,
-            by_alias=by_alias,
-            exclude_unset=exclude_unset,
-            exclude_defaults=exclude_defaults,
-            exclude_none=exclude_none,
-            skip_defaults=skip_defaults,
-        )
-        result: dict = self._exclude_name_fields_from_fluent_datasources(
-            config=intermediate_dict
-        )
-
-        if config_provider:
-            logger.info(
-                f"{self.__class__.__name__}.dict() - substituting config values"
-            )
-            _recursively_set_config_value(data=result, config_provider=config_provider)
-
-        return result
-
     def _exclude_name_fields_from_fluent_datasources(
         self, config: Dict[str, Any]
     ) -> Dict[str, Any]:


Test patch:
diff --git a/tests/datasource/fluent/test_config.py b/tests/datasource/fluent/test_config.py
--- a/tests/datasource/fluent/test_config.py
+++ b/tests/datasource/fluent/test_config.py
@@ -46,9 +46,11 @@
     "fluent_datasources": {
         "my_pg_ds": {
             "connection_string": "postgresql://userName:@hostname/dbName",
+            "name": "my_pg_ds",
             "type": "postgres",
             "assets": {
                 "my_table_asset_wo_splitters": {
+                    "name": "my_table_asset_wo_splitters",
                     "table_name": "my_table",
                     "type": "table",
                 },
@@ -58,6 +60,7 @@
                         "method_name": "split_on_year_and_month",
                     },
                     "table_name": "another_table",
+                    "name": "with_splitter",
                     "type": "table",
                 },
                 "with_sorters": {
@@ -66,27 +69,32 @@
                         {"key": "month", "reverse": True},
                     ],
                     "table_name": "yet_another_table",
+                    "name": "with_sorters",
                     "type": "table",
                 },
                 "with_dslish_sorters": {
                     "order_by": ["year", "-month"],
                     "table_name": "yet_another_table",
+                    "name": "with_dslish_sorters",
                     "type": "table",
                 },
             },
         },
         "my_pandas_filesystem_ds": {
             "type": "pandas_filesystem",
+            "name": "my_pandas_filesystem_ds",
             "base_directory": __file__,
             "assets": {
                 "my_csv_asset": {
                     "type": "csv",
+                    "name": "my_csv_asset",
                     "batching_regex": r"yellow_tripdata_sample_(?P<year>\d{4})-(?P<month>\d{2}).csv",
                     "sep": "|",
                     "names": ["col1", "col2"],
                 },
                 "my_json_asset": {
                     "type": "json",
+                    "name": "my_json_asset",
                     "batching_regex": r"yellow_tripdata_sample_(?P<year>\d{4})-(?P<month>\d{2}).json",
                     "orient": "records",
                 },
@@ -100,7 +108,35 @@
     "fluent_datasources": {
         "my_ds": {
             "type": "sql",
+            "name": "my_ds",
+            "connection_string": "sqlite://",
+        }
+    }
+}
+
+SIMPLE_DS_DICT_WITH_DS_NAME_ERROR = {
+    "fluent_datasources": {
+        "my_ds": {
+            "type": "sql",
+            "name": "my_incorrect_ds",
+            "connection_string": "sqlite://",
+        }
+    }
+}
+
+SIMPLE_DS_DICT_WITH_ASSET_NAME_ERROR = {
+    "fluent_datasources": {
+        "my_ds": {
+            "type": "sql",
+            "name": "my_ds",
             "connection_string": "sqlite://",
+            "assets": {
+                "my_csv_asset": {
+                    "type": "csv",
+                    "name": "my_incorrect_csv_asset",
+                    "batching_regex": r"yellow_tripdata_sample_(?P<year>\d{4})-(?P<month>\d{2}).csv",
+                },
+            },
         }
     }
 }
@@ -109,6 +145,7 @@
     "fluent_datasources": {
         "my_ds": {
             "type": "sql",
+            "name": "my_ds",
             "connection_string": "sqlite://",
         }
     },
@@ -141,8 +178,10 @@
     "fluent_datasources": {
         DEFAULT_PANDAS_DATASOURCE_NAME: {
             "type": "pandas",
+            "name": DEFAULT_PANDAS_DATASOURCE_NAME,
             "assets": {
                 DEFAULT_PANDAS_DATA_ASSET_NAME: {
+                    "name": DEFAULT_PANDAS_DATA_ASSET_NAME,
                     "type": "csv",
                     "filepath_or_buffer": CSV_PATH
                     / "yellow_tripdata_sample_2018-04.csv",
@@ -150,6 +189,7 @@
                     "names": ["col1", "col2"],
                 },
                 "my_csv_asset": {
+                    "name": "my_csv_asset",
                     "type": "csv",
                     "filepath_or_buffer": CSV_PATH
                     / "yellow_tripdata_sample_2018-04.csv",
@@ -219,6 +259,7 @@ def test_from_gx_config(self, asset_dict: dict):
         # fill in required args
         asset_dict.update(
             {
+                "name": "my_asset",
                 "batching_regex": re.compile(
                     r"sample_(?P<year>\d{4})-(?P<month>\d{2}).csv"
                 ),
@@ -267,6 +308,60 @@ def test_load_config(inject_engine_lookup_double, load_method: Callable, input_)
         assert isinstance(datasource, Datasource)
 
 
+@pytest.mark.parametrize(
+    ["load_method", "input_"],
+    [
+        p(
+            GxConfig.parse_obj,
+            SIMPLE_DS_DICT_WITH_DS_NAME_ERROR,
+            id="simple pg config dict with ds name error",
+        ),
+        p(
+            GxConfig.parse_raw,
+            json.dumps(SIMPLE_DS_DICT_WITH_DS_NAME_ERROR),
+            id="simple pg json with ds name error",
+        ),
+    ],
+)
+def test_load_incorrect_ds_config_raises_error(
+    inject_engine_lookup_double, load_method: Callable, input_
+):
+    with pytest.raises(pydantic.ValidationError) as exc_info:
+        _ = load_method(input_)
+
+    assert (
+        str(exc_info.value)
+        == '1 validation error for GxConfig\nfluent_datasources\n  Datasource key "my_ds" is different from name "my_incorrect_ds" in its configuration. (type=value_error)'
+    )
+
+
+@pytest.mark.parametrize(
+    ["load_method", "input_"],
+    [
+        p(
+            GxConfig.parse_obj,
+            SIMPLE_DS_DICT_WITH_ASSET_NAME_ERROR,
+            id="simple pg config dict with asset name error",
+        ),
+        p(
+            GxConfig.parse_raw,
+            json.dumps(SIMPLE_DS_DICT_WITH_ASSET_NAME_ERROR),
+            id="simple pg json with asset name error",
+        ),
+    ],
+)
+def test_load_incorrect_asset_config_raises_error(
+    inject_engine_lookup_double, load_method: Callable, input_
+):
+    with pytest.raises(pydantic.ValidationError) as exc_info:
+        _ = load_method(input_)
+
+    assert (
+        str(exc_info.value)
+        == '1 validation error for GxConfig\nfluent_datasources\n  DataAsset key "my_csv_asset" is different from name "my_incorrect_csv_asset" in its configuration. (type=value_error)'
+    )
+
+
 @pytest.mark.unit
 @pytest.mark.parametrize(
     ["config", "expected_error_loc", "expected_msg"],
@@ -412,7 +507,6 @@ def from_dict_gx_config() -> GxConfig:
 @functools.lru_cache(maxsize=1)
 def from_json_gx_config() -> GxConfig:
     gx_config = GxConfig.parse_raw(COMPLEX_CONFIG_JSON)
-    assert gx_config
     return gx_config
 
 
@@ -420,7 +514,6 @@ def from_json_gx_config() -> GxConfig:
 @functools.lru_cache(maxsize=1)
 def from_yaml_gx_config() -> GxConfig:
     gx_config = GxConfig.parse_yaml(PG_CONFIG_YAML_STR)
-    assert gx_config
     return gx_config
 
 
@@ -430,11 +523,6 @@ def test_dict_config_round_trip(
     dumped: dict = from_dict_gx_config.dict()
     print(f"  Dumped Dict ->\n\n{pf(dumped)}\n")
 
-    assert "name" not in dumped["fluent_datasources"].keys()
-    for datasource in dumped["fluent_datasources"].values():
-        for asset in datasource["assets"].values():
-            assert "name" not in asset.keys()
-
     re_loaded: GxConfig = GxConfig.parse_obj(dumped)
     pp(re_loaded)
     assert re_loaded