@@ -64,11 +64,11 @@ from the :code:`ds` instance
6464
6565 ds.Query.hero.select(ds.Character.name)
6666
67- The select method return the same instance, so it is possible to chain the calls::
67+ The select method returns the same instance, so it is possible to chain the calls::
6868
6969 ds.Query.hero.select(ds.Character.name).select(ds.Character.id)
7070
71- Or do it sequencially ::
71+ Or do it sequentially ::
7272
7373 hero_query = ds.Query.hero
7474
@@ -279,7 +279,7 @@ will generate the request::
279279Multiple operations in a document
280280^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
281281
282- It is possible to create an Document with multiple operations::
282+ It is possible to create a Document with multiple operations::
283283
284284 query = dsl_gql(
285285 operation_name_1=DSLQuery( ... ),
@@ -384,6 +384,305 @@ you can use the :class:`DSLMetaField <gql.dsl.DSLMetaField>` class::
384384 DSLMetaField("__typename")
385385 )
386386
387+ Directives
388+ ^^^^^^^^^^
389+
390+ `Directives `_ provide a way to describe alternate runtime execution and type validation
391+ behavior in a GraphQL document. The DSL module supports both built-in GraphQL directives
392+ (:code: `@skip `, :code: `@include `) and custom schema-defined directives.
393+
394+ To add directives to DSL elements, use the :meth: `DSLSchema.__call__ <gql.dsl.DSLSchema.__call__> `
395+ factory method and the :meth: `directives <gql.dsl.DSLDirectable.directives> ` method::
396+
397+ # Using built-in @skip directive with DSLSchema.__call__ factory
398+ ds.Query.hero.select(
399+ ds.Character.name.directives(ds("@skip").args(**{"if": True}))
400+ )
401+
402+ Directive Arguments
403+ """""""""""""""""""
404+
405+ Directive arguments can be passed using the :meth: `args <gql.dsl.DSLDirective.args> ` method.
406+ For arguments that don't conflict with Python reserved words, you can pass them directly::
407+
408+ # Using the args method for non-reserved names
409+ ds("@custom").args(value="foo", reason="testing")
410+
411+ It can also be done by calling the directive directly::
412+
413+ ds("@custom")(value="foo", reason="testing")
414+
415+ However, when the GraphQL directive argument name conflicts with a Python reserved word
416+ (like :code: `if `), you need to unpack a dictionary to escape it::
417+
418+ # Dictionary unpacking for Python reserved words
419+ ds("@skip").args(**{"if": True})
420+ ds("@include")(**{"if": False})
421+
422+ This ensures that the exact GraphQL argument name is passed to the directive and that
423+ no post-processing of arguments is required.
424+
425+ The :meth: `DSLSchema.__call__ <gql.dsl.DSLSchema.__call__> ` factory method automatically handles
426+ schema lookup and validation for both built-in directives (:code: `@skip `, :code: `@include `)
427+ and custom schema-defined directives using the same syntax.
428+
429+ Directive Locations
430+ """""""""""""""""""
431+
432+ The DSL module supports all executable directive locations from the GraphQL specification:
433+
434+ .. list-table ::
435+ :header-rows: 1
436+ :widths: 25 35 40
437+
438+ * - GraphQL Spec Location
439+ - DSL Class/Method
440+ - Description
441+ * - QUERY
442+ - :code: `DSLQuery.directives() `
443+ - Directives on query operations
444+ * - MUTATION
445+ - :code: `DSLMutation.directives() `
446+ - Directives on mutation operations
447+ * - SUBSCRIPTION
448+ - :code: `DSLSubscription.directives() `
449+ - Directives on subscription operations
450+ * - FIELD
451+ - :code: `DSLField.directives() `
452+ - Directives on fields (including meta-fields)
453+ * - FRAGMENT_DEFINITION
454+ - :code: `DSLFragment.directives() `
455+ - Directives on fragment definitions
456+ * - FRAGMENT_SPREAD
457+ - :code: `DSLFragmentSpread.directives() `
458+ - Directives on fragment spreads (via .spread())
459+ * - INLINE_FRAGMENT
460+ - :code: `DSLInlineFragment.directives() `
461+ - Directives on inline fragments
462+ * - VARIABLE_DEFINITION
463+ - :code: `DSLVariable.directives() `
464+ - Directives on variable definitions
465+
466+ Examples by Location
467+ """"""""""""""""""""
468+
469+ **Operation directives **::
470+
471+ # Query operation
472+ query = DSLQuery(ds.Query.hero.select(ds.Character.name)).directives(
473+ ds("@customQueryDirective")
474+ )
475+
476+ # Mutation operation
477+ mutation = DSLMutation(
478+ ds.Mutation.createReview.args(episode=6, review={"stars": 5}).select(
479+ ds.Review.stars
480+ )
481+ ).directives(ds("@customMutationDirective"))
482+
483+ **Field directives **::
484+
485+ # Single directive on field
486+ ds.Query.hero.select(
487+ ds.Character.name.directives(ds("@customFieldDirective"))
488+ )
489+
490+ # Multiple directives on a field
491+ ds.Query.hero.select(
492+ ds.Character.appearsIn.directives(
493+ ds("@repeat").args(value="first"),
494+ ds("@repeat").args(value="second"),
495+ ds("@repeat").args(value="third"),
496+ )
497+ )
498+
499+ **Fragment directives **:
500+
501+ You can add directives to fragment definitions and to fragment spread instances.
502+ To do this, first define your fragment in the usual way::
503+
504+ name_and_appearances = (
505+ DSLFragment("NameAndAppearances")
506+ .on(ds.Character)
507+ .select(ds.Character.name, ds.Character.appearsIn)
508+ )
509+
510+ Then, use :meth: `spread() <gql.dsl.DSLFragment.spread> ` when you need to add
511+ directives to the fragment spread::
512+
513+ query_with_fragment = DSLQuery(
514+ ds.Query.hero.select(
515+ name_and_appearances.spread().directives(
516+ ds("@customFragmentSpreadDirective")
517+ )
518+ )
519+ )
520+
521+ The :meth: `spread() <gql.dsl.DSLFragment.spread> ` method creates a
522+ :class: `DSLFragmentSpread <gql.dsl.DSLFragmentSpread> ` instance that allows you to add
523+ directives specific to the fragment spread location, separate from directives on the
524+ fragment definition itself.
525+
526+ Example with fragment definition and spread-specific directives::
527+
528+ # Fragment definition with directive
529+ name_and_appearances = (
530+ DSLFragment("CharacterInfo")
531+ .on(ds.Character)
532+ .select(ds.Character.name, ds.Character.appearsIn)
533+ .directives(ds("@customFragmentDefinitionDirective"))
534+ )
535+
536+ # Using fragment with spread-specific directives
537+ query_without_spread_directive = DSLQuery(
538+ # Direct usage (no spread directives)
539+ ds.Query.hero.select(name_and_appearances)
540+ )
541+ query_with_spread_directive = DSLQuery(
542+ # Enhanced usage with spread directives
543+ name_and_appearances.spread().directives(
544+ ds("@customFragmentSpreadDirective")
545+ )
546+ )
547+
548+ # Don't forget to include the fragment definition in dsl_gql
549+ query = dsl_gql(
550+ name_and_appearances,
551+ BaseQuery=query_without_spread_directive,
552+ QueryWithDirective=query_with_spread_directive,
553+ )
554+
555+ This generates GraphQL equivalent to::
556+
557+ fragment CharacterInfo on Character @customFragmentDefinitionDirective {
558+ name
559+ appearsIn
560+ }
561+
562+ {
563+ BaseQuery hero {
564+ ...CharacterInfo
565+ }
566+ QueryWithDirective hero {
567+ ...CharacterInfo @customFragmentSpreadDirective
568+ }
569+ }
570+
571+ **Inline fragment directives **:
572+
573+ Inline fragments also support directives using the
574+ :meth: `directives <gql.dsl.DSLInlineFragment.directives> ` method::
575+
576+ query_with_directive = ds.Query.hero.args(episode=6).select(
577+ ds.Character.name,
578+ DSLInlineFragment().on(ds.Human).select(ds.Human.homePlanet).directives(
579+ ds("@customInlineFragmentDirective")
580+ )
581+ )
582+
583+ This generates::
584+
585+ {
586+ hero(episode: JEDI) {
587+ name
588+ ... on Human @customInlineFragmentDirective {
589+ homePlanet
590+ }
591+ }
592+ }
593+
594+ **Variable definition directives **:
595+
596+ You can also add directives to variable definitions using the
597+ :meth: `directives <gql.dsl.DSLVariable.directives> ` method::
598+
599+ var = DSLVariableDefinitions()
600+ var.episode.directives(ds("@customVariableDirective"))
601+ # Note: the directive is attached to the `.episode` variable definition (singular),
602+ # and not the `var` variable definitions (plural) holder.
603+
604+ op = DSLQuery(ds.Query.hero.args(episode=var.episode).select(ds.Character.name))
605+ op.variable_definitions = var
606+
607+ This will generate::
608+
609+ query ($episode: Episode @customVariableDirective) {
610+ hero(episode: $episode) {
611+ name
612+ }
613+ }
614+
615+ Complete Example for Directives
616+ """""""""""""""""""""""""""""""
617+
618+ Here's a comprehensive example showing directives on multiple locations:
619+
620+ .. code-block :: python
621+
622+ from gql.dsl import DSLFragment, DSLInlineFragment, DSLQuery, dsl_gql
623+
624+ # Create variables for directive conditions
625+ var = DSLVariableDefinitions()
626+
627+ # Fragment with directive on definition
628+ character_fragment = DSLFragment(" CharacterInfo" ).on(ds.Character).select(
629+ ds.Character.name, ds.Character.appearsIn
630+ ).directives(ds(" @fragmentDefinition" ))
631+
632+ # Query with directives on multiple locations
633+ query = DSLQuery(
634+ ds.Query.hero.args(episode = var.episode).select(
635+ # Field with directive
636+ ds.Character.name.directives(ds(" @skip" ).args(** {" if" : var.skipName})),
637+
638+ # Fragment spread with directive
639+ character_fragment.spread().directives(
640+ ds(" @include" ).args(** {" if" : var.includeFragment})
641+ ),
642+
643+ # Inline fragment with directive
644+ DSLInlineFragment().on(ds.Human).select(ds.Human.homePlanet).directives(
645+ ds(" @skip" ).args(** {" if" : var.skipHuman})
646+ ),
647+
648+ # Meta field with directive
649+ DSLMetaField(" __typename" ).directives(
650+ ds(" @include" ).args(** {" if" : var.includeType})
651+ )
652+ )
653+ ).directives(ds(" @query" )) # Operation directive
654+
655+ # Variable definition with directive
656+ var.episode.directives(ds(" @variableDefinition" ))
657+ query.variable_definitions = var
658+
659+ # Generate the document
660+ document = dsl_gql(character_fragment, query)
661+
662+ This generates GraphQL equivalent to::
663+
664+ fragment CharacterInfo on Character @fragmentDefinition {
665+ name
666+ appearsIn
667+ }
668+
669+ query (
670+ $episode: Episode @variableDefinition
671+ $skipName: Boolean!
672+ $includeFragment: Boolean!
673+ $skipHuman: Boolean!
674+ $includeType: Boolean!
675+ ) @query {
676+ hero(episode: $episode) {
677+ name @skip(if: $skipName)
678+ ...CharacterInfo @include(if: $includeFragment)
679+ ... on Human @skip(if: $skipHuman) {
680+ homePlanet
681+ }
682+ __typename @include(if: $includeType)
683+ }
684+ }
685+
387686Executable examples
388687-------------------
389688
@@ -399,4 +698,5 @@ Sync example
399698
400699.. _Fragment : https://graphql.org/learn/queries/#fragments
401700.. _Inline Fragment : https://graphql.org/learn/queries/#inline-fragments
701+ .. _Directives : https://graphql.org/learn/queries/#directives
402702.. _issue #308 : https://github.com/graphql-python/gql/issues/308
0 commit comments