Repository: tartiflette/tartiflette
Pull request #469 (2021-01-27 10:05:54)

Update nonIntrospectable builtIn directive to be used on Schema SDL Object
* [x] Code
* [x] Test
* [x] Doc


Patch:
diff --git a/changelogs/next.md b/changelogs/next.md
index f13a4ff..d1d7a33 100644
--- a/changelogs/next.md
+++ b/changelogs/next.md
@@ -1,1 +1,10 @@
 # [Next]
+
+## Added
+
+- Built-in introspection directive `@nonIntrospectable` now supports SCHEMA object decoration.
+```
+schema @nonIntrospectable {
+    ...
+}
+```
diff --git a/setup.py b/setup.py
index 30dacb4..755d29f 100644
--- a/setup.py
+++ b/setup.py
@@ -50,7 +50,7 @@ class BuildPyCmd(build_py):
 
 _TEST_REQUIRE = [
     "pytest==6.2.1",
-    "pytest-cov==2.11.1",
+    "pytest-cov==2.10.1",
     "pytest-asyncio==0.14.0",
     "pytest-xdist==2.2.0",
     "pylint==2.6.0",
diff --git a/tartiflette/directive/builtins/non_introspectable.py b/tartiflette/directive/builtins/non_introspectable.py
index 9a698b8..293ee79 100644
--- a/tartiflette/directive/builtins/non_introspectable.py
+++ b/tartiflette/directive/builtins/non_introspectable.py
@@ -36,6 +36,30 @@ class NonIntrospectableDirective:
         # pylint: disable=unused-argument
         return None
 
+    @staticmethod
+    async def on_schema_execution(
+        directive_args,
+        next_directive,
+        schema,
+        document,
+        parsing_errors,
+        operation_name,
+        context,
+        variables,
+        initial_value,
+    ):
+        # pylint: disable=unused-argument
+        schema.is_introspectable = False
+        return await next_directive(
+            schema,
+            document,
+            parsing_errors,
+            operation_name,
+            context,
+            variables,
+            initial_value,
+        )
+
 
 def bake(schema_name: str, config: Optional[Dict[str, Any]] = None) -> str:
     """
@@ -54,5 +78,5 @@ def bake(schema_name: str, config: Optional[Dict[str, Any]] = None) -> str:
     )
     return '''
     """Directs the executor to hide the element on introspection queries."""
-    directive @nonIntrospectable on FIELD_DEFINITION
+    directive @nonIntrospectable on FIELD_DEFINITION | SCHEMA
     '''
diff --git a/tartiflette/schema/introspection.py b/tartiflette/schema/introspection.py
index 90096e3..dc707ec 100644
--- a/tartiflette/schema/introspection.py
+++ b/tartiflette/schema/introspection.py
@@ -2,6 +2,7 @@ from functools import partial
 from typing import Any, Dict, Optional
 
 from tartiflette.types.argument import GraphQLArgument
+from tartiflette.types.exceptions.tartiflette import TartifletteError
 from tartiflette.types.field import GraphQLField
 from tartiflette.types.non_null import GraphQLNonNull
 
@@ -32,8 +33,10 @@ async def __schema_resolver(
     :rtype: Any
     """
     # pylint: disable=unused-argument
-    info.is_introspection = True
-    return info.schema
+    if info.schema.is_introspectable:
+        info.is_introspection = True
+        return info.schema
+    raise TartifletteError("Introspection is disabled for this schema")
 
 
 SCHEMA_ROOT_FIELD_DEFINITION = partial(
@@ -65,6 +68,9 @@ async def __type_resolver(
     :rtype: GraphQLType
     """
     # pylint: disable=unused-argument
+    if not info.schema.is_introspectable:
+        raise TartifletteError("Introspection is disabled for this schema")
+
     info.is_introspection = True
     try:
         return info.schema.find_type(args["name"])
diff --git a/tartiflette/schema/schema.py b/tartiflette/schema/schema.py
index 9db74bb..a9d945a 100644
--- a/tartiflette/schema/schema.py
+++ b/tartiflette/schema/schema.py
@@ -220,6 +220,7 @@ class GraphQLSchema:
         self.extensions: List["GraphQLExtension"] = []
         self._schema_directives: List["DirectiveNode"] = []
         self._json_loader = None
+        self.is_introspectable = True
 
     @property
     def json_loader(self):


Test patch:
diff --git a/tests/functional/regressions/test_issue92.py b/tests/functional/regressions/test_issue92.py
index 31d1487..a0524ba 100644
--- a/tests/functional/regressions/test_issue92.py
+++ b/tests/functional/regressions/test_issue92.py
@@ -515,7 +515,7 @@ async def test_issue92_fragment_inordered(ttftt_engine):
                     },
                     {
                         "name": "nonIntrospectable",
-                        "locations": ["FIELD_DEFINITION"],
+                        "locations": ["FIELD_DEFINITION", "SCHEMA"],
                         "args": [],
                     },
                     {
diff --git a/tests/functional/test_introspection.py b/tests/functional/test_introspection.py
index d37f1c9..22f9a23 100644
--- a/tests/functional/test_introspection.py
+++ b/tests/functional/test_introspection.py
@@ -254,7 +254,7 @@ async def test_tartiflette_execute_schema_introspection_output():
                     {
                         "name": "nonIntrospectable",
                         "description": "Directs the executor to hide the element on introspection queries.",
-                        "locations": ["FIELD_DEFINITION"],
+                        "locations": ["FIELD_DEFINITION", "SCHEMA"],
                         "args": [],
                     },
                     {
@@ -617,7 +617,7 @@ fragment TypeRef on __Type {
                     },
                     {
                         "name": "nonIntrospectable",
-                        "locations": ["FIELD_DEFINITION"],
+                        "locations": ["FIELD_DEFINITION", "SCHEMA"],
                         "args": [],
                     },
                     {
@@ -1011,3 +1011,84 @@ async def test_introspection_type_fields_include_deprecated(
     )
 
     assert result == expected
+
+
+@pytest.mark.asyncio
+async def test_tartiflette_execute_schema_introspection_non_introspectable_output():
+    schema_sdl = """
+    schema @nonIntrospectable {
+        query: CustomRootQuery
+        mutation: CustomRootMutation
+        subscription: CustomRootSubscription
+    }
+
+    type CustomRootQuery {
+        test: String
+    }
+
+    type CustomRootMutation {
+        test: Int
+    }
+
+    type CustomRootSubscription {
+        test: String
+    }
+    """
+
+    ttftt = await create_engine(
+        schema_sdl,
+        schema_name="test_tartiflette_execute_schema_introspection_non_introspectable_output",
+    )
+
+    result = await ttftt.execute(
+        """
+    query Test {
+        __schema {
+            queryType { name }
+            mutationType { name }
+            subscriptionType { name }
+            types {
+                kind
+                name
+            }
+            directives {
+                name
+                description
+                locations
+                args {
+                    name
+                    description
+                    type {
+                        kind
+                        name
+                    }
+                    defaultValue
+                }
+            }
+        }
+        __typename
+        __type(name: "CustomRootMutation") {
+            __typename
+            kind
+            name
+        }
+    }
+    """,
+        operation_name="Test",
+    )
+
+    assert {
+        "data": None,
+        "errors": [
+            {
+                "message": "Introspection is disabled for this schema",
+                "path": ["__type"],
+                "locations": [{"line": 27, "column": 9}],
+            },
+            {
+                "message": "Introspection is disabled for this schema",
+                "path": ["__schema"],
+                "locations": [{"line": 3, "column": 9}],
+            },
+        ],
+    } == result
diff --git a/tests/functional/test_introspection_schema.py b/tests/functional/test_introspection_schema.py
index 0e2b19f..2259574 100644
--- a/tests/functional/test_introspection_schema.py
+++ b/tests/functional/test_introspection_schema.py
@@ -115,7 +115,7 @@ import pytest
                                 "__typename": "__Directive",
                                 "name": "nonIntrospectable",
                                 "description": "Directs the executor to hide the element on introspection queries.",
-                                "locations": ["FIELD_DEFINITION"],
+                                "locations": ["FIELD_DEFINITION", "SCHEMA"],
                                 "args": [],
                             },
                             {