Skip to content

Expose attached schema directives through introspection - #4598

Merged
patrick91 merged 19 commits into
mainfrom
fix/schema-directive-introspection
Aug 31, 2026
Merged

Expose attached schema directives through introspection#4598
patrick91 merged 19 commits into
mainfrom
fix/schema-directive-introspection

Conversation

@patrick91

@patrick91 patrick91 commented Aug 29, 2026

Copy link
Copy Markdown
Member

Summary

  • expose custom schema directives attached throughout a Strawberry schema through standard GraphQL introspection
  • discover directives transitively, including directives attached to input types introduced only by another directive's arguments
  • collect the complete directive argument input graph before performing one final schema rebuild
  • include directive descriptions, arguments and defaults, locations, repeatability, and referenced argument types
  • deduplicate reused definitions and reconcile directives also supplied through types=
  • reject conflicting custom directive names and collisions with specified GraphQL directives during schema construction

SDL and release impact

This is a minor release change. Input, enum, and scalar types referenced only by directive arguments are now ordinary schema types and may appear in generated SDL in normal sorted order.

Federation-private directive support types such as _FieldSet remain available to runtime introspection but are omitted from strawberry.federation.Schema.as_str() as before. _Any remains in Federation SDL because it is part of the executable Federation API.

print_definition=False still omits the directive definition itself, while the runtime directive and its supporting types remain available to introspection. Using Federation directives with strawberry.Schema retains its existing warning behavior; strawberry.federation.Schema registers them for introspection.

Testing

  • uv run pytest tests/schema tests/test_printer tests/federation/printer tests/federation/test_schema.py -q with graphql-core 3.2.11 (812 passed, 27 skipped, 2 xfailed)
  • Federation printer/schema compatibility tests with graphql-core 3.3.0rc0 (57 passed)
  • core printer and schema-directive compatibility tests with graphql-core 3.3.0rc0 (51 passed, 1 skipped)
  • uv run ruff check .
  • uv run ruff format --check .
  • uv run mypy --config-file mypy.ini strawberry/schema/base.py strawberry/schema/schema.py strawberry/printer/printer.py strawberry/federation/schema.py

Summary by Sourcery

Expose attached schema directives through GraphQL introspection while constructing and validating their complete supporting type graph in a single schema build.

New Features:

  • Expose attached custom and Federation schema directives, including their metadata and supporting argument types, through standard GraphQL introspection.

Bug Fixes:

  • Discover directives transitively across schema elements and directive-only input graphs, ensuring all attached directives are registered during schema construction.
  • Make Federation directive applications on field arguments available to generated subgraph metadata and introspection.

Enhancements:

  • Deduplicate reused directive definitions and reconcile explicitly supplied directives with attached definitions.
  • Validate directive name collisions, specified-directive conflicts, and conflicting argument type names during schema construction.
  • Preserve runtime availability of hidden Federation support types while controlling their SDL output and improve SDL reachability and deduplication.

Documentation:

  • Document schema-directive introspection, runtime support types, naming constraints, and eager resolution of directive argument annotations.

Tests:

  • Add coverage for directive introspection, transitive discovery, Federation integration, deduplication, collision validation, hidden support types, and single-pass schema construction.

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Thanks for adding the RELEASE.md file!

Below is the changelog that will be used for the release.


This release fixes introspection for custom schema directives.

Schema directives attached to types, fields, arguments, and other schema elements
now appear in standard GraphQL introspection. Schema explorers, IDEs, code
generators, and other tools can discover each directive's description, arguments,
allowed locations, repeatability, and any input types it uses. Federation directives,
including generated @link and @composeDirective applications, are discoverable
in the same way.

Federation directives and custom composed directives used on field arguments are
also included in the generated subgraph metadata, so routers can recognize those
argument annotations without additional schema configuration.

A directive reused across the schema is defined only once. Input, enum, and scalar
types referenced by directive arguments are now part of the schema and may appear
in generated SDL even when they are not used by fields.

Because these directives and argument types are now part of the runtime schema,
their GraphQL names must be unique. Schema construction reports a clear error when
different directive definitions share a name, a custom directive replaces a
built-in directive such as @skip, or a directive argument type conflicts with
another schema type. Compatible custom @oneOf definitions continue to use
GraphQL's built-in directive. Strawberry now also resolves attached directive
argument annotations during schema construction, so unresolved forward references
are reported when the schema is created instead of later when its SDL is printed.

This release was contributed by @patrick91 in #4598

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="strawberry/schema/schema.py" line_range="559-585" />
<code_context>
+        for graphql_type in self._schema.type_map.values():
</code_context>
<issue_to_address>
**issue (broader_impact):** Schema directives attached to input objects, input fields, or other newly discovered directive-argument types are never registered when those types are reachable only through an attached directive argument. The method scans the initial schema type map, rebuilds the schema after discovering a directive, and does not rescan the newly added argument types, so introspection omits directives attached within nested directive input types.

**Triggers:** When an attached schema directive takes an input-object argument whose nested input object has its own attached schema directive and that input object is not otherwise used by the schema.

**Suggested fix:** After rebuilding the schema with newly discovered directive types, rescan the expanded type map (or recursively collect attached directives from Strawberry definitions before rebuilding).
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: strawberry/schema/schema.py:585


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread strawberry/schema/schema.py Outdated
@botberry

Copy link
Copy Markdown
Member

Apollo Federation Subgraph Compatibility Results

Federation 1 Support Federation 2 Support
_service🟢
@key (single)🟢
@key (multi)🟢
@key (composite)🟢
repeatable @key🟢
@requires🟢
@provides🟢
federated tracing🟢
@link🟢
@shareable🟢
@tag🟢
@override🟢
@inaccessible🟢
@composeDirective🟢
@interfaceObject🟢

Learn more:

@greptile-apps

greptile-apps Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR exposes attached schema directives and their supporting input-type graphs through runtime introspection while keeping SDL generation consistent.

  • Collects directives and directive argument types transitively before constructing the GraphQL schema.
  • Registers and validates custom, specified, permission, and Federation directive definitions.
  • Adds configurable SDL visibility for integration-support scalars and enums.
  • Extends tests and documentation for introspection, conflicts, Federation behavior, and SDL output.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the fixed-point collector now discovers directives on nested input types introduced solely through directive arguments before GraphQLSchema construction.

Important Files Changed

Filename Overview
strawberry/schema/directive_collector.py Adds fixed-point traversal of reachable GraphQL types, directive applications, and directive argument input graphs.
strawberry/schema/schema.py Integrates complete directive collection and conflict validation before the single GraphQLSchema construction.
strawberry/schema/schema_converter.py Resolves schema-directive argument annotations during construction and raises the established unresolved-type error.
strawberry/printer/printer.py Reuses registered directive definitions and filters private support types while retaining definitions required by visible SDL.
strawberry/federation/schema.py Moves generated Federation link and composition directives into the pre-construction collection lifecycle.
strawberry/federation/types.py Attaches runtime scalar metadata to Federation support types while keeping private definitions out of ordinary SDL.
strawberry/permission.py Stabilizes automatically generated permission directive identity across instances and concurrent schema construction.
tests/schema/test_schema_directives.py Covers transitive discovery through directive-only nested input graphs, including directives on nested input types and fields.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Root and explicit Strawberry types] --> B[Convert GraphQL types]
    B --> C[Collect attached directives]
    C --> D[Convert directive arguments]
    D --> E[Queue referenced input graphs]
    E --> C
    C --> F[Prepare generated Federation directives]
    F --> C
    C --> G[Build and validate GraphQLSchema]
    G --> H[Introspection]
    G --> I[SDL printer]
Loading

Reviews (2): Last reviewed commit: "Merge branch 'main' into fix/schema-dire..." | Re-trigger Greptile

Comment thread strawberry/schema/schema.py Outdated
@codspeed-hq

codspeed-hq Bot commented Aug 29, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 34 untouched benchmarks


Comparing fix/schema-directive-introspection (c0b9fbc) with main (0ea6ad9)

Open in CodSpeed

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Aug 29, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@patrick91
patrick91 marked this pull request as draft August 29, 2026 15:32
@patrick91

Copy link
Copy Markdown
Member Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="strawberry/schema/schema.py" line_range="1397-1400" />
<code_context>

         pass

+    def _should_register_schema_directive(self, directive: object) -> bool:
+        return True
+
</code_context>
<issue_to_address>
**issue (broader_impact):** Federation directives attached to schema types are explicitly excluded from `_register_schema_directives`, so they are absent from `schema._schema.directives` and cannot be discovered through `__schema.directives` introspection when using `strawberry.federation.Schema`.

**Triggers:** When a federation directive such as `@key`, `@external`, or `@tag` is attached to a federated schema element.

**Suggested fix:** Return `True` for federation schemas or otherwise add federation directives to the GraphQL schema directive registry while continuing to filter their definitions from federation SDL.
</issue_to_address>

### Comment 2
<location path="strawberry/schema/schema.py" line_range="428" />
<code_context>
                     GraphQLCoreConverter.DEFINITION_BACKREF: self,
                 },
             )
+            self._register_schema_directives()

         except TypeError as error:
</code_context>
<issue_to_address>
**issue (bug_risk):** Schema directives added by the federation constructor after `super().__init__` returns—such as generated `@link` and `@composeDirective` instances—are never passed through `_register_schema_directives`, so they remain unavailable through GraphQL introspection even if they should be registered.

**Triggers:** When a federation schema uses composed directives or generates federation link directives.

**Suggested fix:** Run directive registration after federation adds its generated directives, or register those directives as part of the federation schema construction before finalizing the GraphQL schema.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: strawberry/schema/schema.py:1400, strawberry/schema/schema.py:428


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread strawberry/schema/schema.py Outdated
Comment thread strawberry/schema/schema.py Outdated
Move the attached-directive discovery into a small SchemaDirectiveCollector
class with explicit state instead of nested closures, and pass the converted
root types and explicit directive classes to the collect helpers as arguments
instead of storing write-once scaffolding attributes on the Schema instance.

Extract the directive-name registry and the @OneOf compatibility check into
named module-level helpers, and normalize enum, argument, union and scalar
directives to tuples at definition time so schema construction no longer
needs to materialize one-shot iterables and write them back onto user
definitions.

Claude-Session: https://claude.ai/code/session_01TeQw3R6G4xK7qJxUTuKotZ
@patrick91
patrick91 marked this pull request as ready for review August 30, 2026 11:37
@sourcery-ai
sourcery-ai Bot dismissed their stale review August 30, 2026 11:38

Sourcery withdrew this approval because the latest commits introduced blocking findings.

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Aug 30, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread docs/types/enums.md Outdated

## Hiding integration support types from SDL

Integration authors can pass `print_definition=False` to `strawberry.enum` for

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if print_definition is clear enough, or some kind of internal flag would be better? Not sure 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I opened an issue in order to change this, maybe for now we avoid documenting it :D

Comment thread strawberry/printer/printer.py Outdated
Comment on lines +762 to +768
if schema.config.enable_experimental_incremental_execution:
directives.append(
"directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT"
)
directives.append(
"directive @stream(if: Boolean, label: String, initialCount: Int = 0) on FIELD"
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to import the symbols from graphql-core and print those, instead of hardcoding how they look like?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well spotted :D

@sourcery-ai
sourcery-ai Bot dismissed their stale review August 31, 2026 17:36

Sourcery withdrew this approval because the latest commits introduced blocking findings.

@patrick91
patrick91 merged commit 073c4b4 into main Aug 31, 2026
87 checks passed
@patrick91
patrick91 deleted the fix/schema-directive-introspection branch August 31, 2026 17:50
@botberry

Copy link
Copy Markdown
Member

This PR was published as 0.326.0. Thank you for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants