From 12e07ac5536b3156b716149946ae4c3f8554b7cd Mon Sep 17 00:00:00 2001 From: Katie Baker Date: Wed, 3 Sep 2025 22:32:27 -0700 Subject: [PATCH 1/3] Add DSLSchema.__call__ shortcuts for DSLMetafield, DSLInlineFragment, and DSLFragment --- gql/dsl.py | 35 ++++++++++++++++++++++++----------- tests/starwars/test_dsl.py | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/gql/dsl.py b/gql/dsl.py index da4cf64c..93079b9f 100644 --- a/gql/dsl.py +++ b/gql/dsl.py @@ -334,25 +334,38 @@ def __call__(self, shortcut: Any) -> "DSLDirective": ... # pragma: no cover def __call__( self, shortcut: str, name: Optional[str] = None ) -> Union["DSLMetaField", "DSLInlineFragment", "DSLFragment", "DSLDirective"]: - """Factory method for creating DSL objects. + """Factory method for creating DSL objects from a shortcut string. - Currently, supports creating DSLDirective instances when name starts with '@'. - Future support planned for meta-fields (__typename), inline fragments (...), - and fragment definitions (fragment). + The shortcut determines which DSL object is created: - :param shortcut: the name of the object to create + * "__typename", "__schema", "__type" -> :class:`DSLMetaField` + * "..." -> :class:`DSLInlineFragment` + * "fragment" -> :class:`DSLFragment` (requires ``name`` to be a string) + * "@" -> :class:`DSLDirective` + + :param shortcut: The shortcut string identifying the DSL object. :type shortcut: str - :return: :class:`DSLDirective` instance + :param name: The fragment name, required when ``shortcut == "fragment"``. + :type name: Optional[str] + + :return: A DSL object corresponding to the given shortcut. + :rtype: DSLMetaField | DSLInlineFragment | DSLFragment | DSLDirective - :raises ValueError: if shortcut format is not supported + :raises ValueError: If the shortcut is not recognized, + or if ``name`` is missing for a fragment shortcut. """ + + if shortcut in ("__typename", "__schema", "__type"): + return DSLMetaField(name=shortcut) + if shortcut == "...": + return DSLInlineFragment() + if shortcut == "fragment": + if not isinstance(name, str): + raise ValueError(f"Missing name: {name} for fragment shortcut") + return DSLFragment(name=name) if shortcut.startswith("@"): return DSLDirective(name=shortcut[1:], dsl_schema=self) - # Future support: - # if name.startswith("__"): return DSLMetaField(name) - # if name == "...": return DSLInlineFragment() - # if name.startswith("fragment "): return DSLFragment(name[9:]) raise ValueError(f"Unsupported shortcut: {shortcut}") diff --git a/tests/starwars/test_dsl.py b/tests/starwars/test_dsl.py index a3d1ef8c..0c6a1f84 100644 --- a/tests/starwars/test_dsl.py +++ b/tests/starwars/test_dsl.py @@ -23,6 +23,7 @@ from gql import Client, gql from gql.dsl import ( + DSLDirective, DSLField, DSLFragment, DSLFragmentSpread, @@ -1297,9 +1298,35 @@ def test_legacy_fragment_with_variables(ds): assert print_ast(query.document) == expected -def test_dsl_schema_call_validation(ds): - with pytest.raises(ValueError, match="(?i)unsupported shortcut"): - ds("foo") +@pytest.mark.parametrize( + "shortcut,expected", + [ + ("__typename", DSLMetaField("__typename")), + ("__schema", DSLMetaField("__schema")), + ("__type", DSLMetaField("__type")), + ("...", DSLInlineFragment()), + ("@skip", DSLDirective(name="skip", dsl_schema=DSLSchema(StarWarsSchema))), + ], +) +def test_dsl_schema_call_shortcuts(ds, shortcut, expected): + actual = ds(shortcut) + assert getattr(actual, "name", None) == getattr(expected, "name", None) + assert isinstance(actual, type(expected)) + + +def test_dsl_schema_call_fragment(ds): + fragment = ds("fragment", "foo") + assert fragment.name == "foo" + assert isinstance(fragment, DSLFragment) + + +@pytest.mark.parametrize( + "shortcut,match", + [("foo", "(?i)unsupported shortcut"), ("fragment", "(?i)missing name")], +) +def test_dsl_schema_call_validation(ds, shortcut, match): + with pytest.raises(ValueError, match=match): + ds(shortcut) def test_executable_directives(ds, var): From 54d780907a5a8c6c32b22283844197c3c4ba433b Mon Sep 17 00:00:00 2001 From: Katie Baker Date: Wed, 3 Sep 2025 22:47:57 -0700 Subject: [PATCH 2/3] Update docs with examples of DSLSchema.__call__ shortcut syntax --- docs/advanced/dsl_module.rst | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/advanced/dsl_module.rst b/docs/advanced/dsl_module.rst index c6ee035a..78ae329c 100644 --- a/docs/advanced/dsl_module.rst +++ b/docs/advanced/dsl_module.rst @@ -328,6 +328,16 @@ The above example will generate the following request:: } } +Alternatively, you can use the DSL shortcut syntax to create a fragment by +passing the string ``"fragment"`` directly to the :meth:`__call__ ` method. +When using the shortcut, you must also provide the fragment name via the ``name`` parameter:: + + name_and_appearances = ( + ds("fragment", "NameAndAppearances") + .on(ds.Character) + .select(ds.Character.name, ds.Character.appearsIn) + ) + Inline Fragments ^^^^^^^^^^^^^^^^ @@ -373,6 +383,14 @@ this can be written in a concise manner:: DSLInlineFragment().on(ds.Human).select(ds.Human.homePlanet) ) +Alternatively, you can use the DSL shortcut syntax to create an inline fragment by +passing the string ``"..."`` directly to the :meth:`__call__ ` method:: + + query_with_inline_fragment = ds.Query.hero.args(episode=6).select( + ds.Character.name, + ds("...").on(ds.Human).select(ds.Human.homePlanet) + ) + Meta-fields ^^^^^^^^^^^ @@ -384,6 +402,15 @@ you can use the :class:`DSLMetaField ` class:: DSLMetaField("__typename") ) +Alternatively, you can use the DSL shortcut syntax to create the same meta-field by +passing the ``"__typename"`` string directly to the :meth:`__call__ ` method:: + + query = ds.Query.hero.select( + ds.Character.name, + ds("__typename") + ) + + Directives ^^^^^^^^^^ From 27a478760d4674824f220d1bbff16b2a496f1982 Mon Sep 17 00:00:00 2001 From: Katie Baker Date: Thu, 4 Sep 2025 19:13:05 -0700 Subject: [PATCH 3/3] Remove DSLSchema.__call__ shortcut for DSLFragment --- docs/advanced/dsl_module.rst | 10 ---------- gql/dsl.py | 22 ++++------------------ tests/starwars/test_dsl.py | 16 +++------------- 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/docs/advanced/dsl_module.rst b/docs/advanced/dsl_module.rst index 78ae329c..e30655b5 100644 --- a/docs/advanced/dsl_module.rst +++ b/docs/advanced/dsl_module.rst @@ -328,16 +328,6 @@ The above example will generate the following request:: } } -Alternatively, you can use the DSL shortcut syntax to create a fragment by -passing the string ``"fragment"`` directly to the :meth:`__call__ ` method. -When using the shortcut, you must also provide the fragment name via the ``name`` parameter:: - - name_and_appearances = ( - ds("fragment", "NameAndAppearances") - .on(ds.Character) - .select(ds.Character.name, ds.Character.appearsIn) - ) - Inline Fragments ^^^^^^^^^^^^^^^^ diff --git a/gql/dsl.py b/gql/dsl.py index 93079b9f..2e6d3967 100644 --- a/gql/dsl.py +++ b/gql/dsl.py @@ -323,47 +323,33 @@ def __call__( self, shortcut: Literal["..."] ) -> "DSLInlineFragment": ... # pragma: no cover - @overload - def __call__( - self, shortcut: Literal["fragment"], name: str - ) -> "DSLFragment": ... # pragma: no cover - @overload def __call__(self, shortcut: Any) -> "DSLDirective": ... # pragma: no cover def __call__( - self, shortcut: str, name: Optional[str] = None - ) -> Union["DSLMetaField", "DSLInlineFragment", "DSLFragment", "DSLDirective"]: + self, shortcut: str + ) -> Union["DSLMetaField", "DSLInlineFragment", "DSLDirective"]: """Factory method for creating DSL objects from a shortcut string. The shortcut determines which DSL object is created: * "__typename", "__schema", "__type" -> :class:`DSLMetaField` * "..." -> :class:`DSLInlineFragment` - * "fragment" -> :class:`DSLFragment` (requires ``name`` to be a string) * "@" -> :class:`DSLDirective` :param shortcut: The shortcut string identifying the DSL object. :type shortcut: str - :param name: The fragment name, required when ``shortcut == "fragment"``. - :type name: Optional[str] - :return: A DSL object corresponding to the given shortcut. - :rtype: DSLMetaField | DSLInlineFragment | DSLFragment | DSLDirective + :rtype: DSLMetaField | DSLInlineFragment | DSLDirective - :raises ValueError: If the shortcut is not recognized, - or if ``name`` is missing for a fragment shortcut. + :raises ValueError: If the shortcut is not recognized. """ if shortcut in ("__typename", "__schema", "__type"): return DSLMetaField(name=shortcut) if shortcut == "...": return DSLInlineFragment() - if shortcut == "fragment": - if not isinstance(name, str): - raise ValueError(f"Missing name: {name} for fragment shortcut") - return DSLFragment(name=name) if shortcut.startswith("@"): return DSLDirective(name=shortcut[1:], dsl_schema=self) diff --git a/tests/starwars/test_dsl.py b/tests/starwars/test_dsl.py index 0c6a1f84..7f042a07 100644 --- a/tests/starwars/test_dsl.py +++ b/tests/starwars/test_dsl.py @@ -1314,19 +1314,9 @@ def test_dsl_schema_call_shortcuts(ds, shortcut, expected): assert isinstance(actual, type(expected)) -def test_dsl_schema_call_fragment(ds): - fragment = ds("fragment", "foo") - assert fragment.name == "foo" - assert isinstance(fragment, DSLFragment) - - -@pytest.mark.parametrize( - "shortcut,match", - [("foo", "(?i)unsupported shortcut"), ("fragment", "(?i)missing name")], -) -def test_dsl_schema_call_validation(ds, shortcut, match): - with pytest.raises(ValueError, match=match): - ds(shortcut) +def test_dsl_schema_call_validation(ds): + with pytest.raises(ValueError, match="(?i)unsupported shortcut"): + ds("foo") def test_executable_directives(ds, var):