Skip to content

Fix GraphQL parser to handle renamed objects correctly#2670

Merged
koxudaxi merged 10 commits intokoxudaxi:mainfrom
siminn-arnorgj:fix-types-casing
Dec 17, 2025
Merged

Fix GraphQL parser to handle renamed objects correctly#2670
koxudaxi merged 10 commits intokoxudaxi:mainfrom
siminn-arnorgj:fix-types-casing

Conversation

@siminn-arnorgj
Copy link
Copy Markdown
Contributor

@siminn-arnorgj siminn-arnorgj commented Dec 15, 2025

Summary

  • Changed _resolve_types in graphql.py to use model_resolver.get_class_name rather than the naive type name. This aligns more closely with how the type name is set on the references in the model_resolver and fixes the bug. (Looking more closely at the code, I think graphql.py should be using the reference system on the model_resolver like the other parsers, but I'm not familiar enough with the codebase to be sure that is the right approach).
  • In parse_field, non-enums now pick up their reference if one exists, so field annotations use the renamed class names instead of using the raw GraphQL name.
  • Created tests for the issue and combined with the enum-casing testfile to cover more cases.
  • This fix also caused the order of the generated classes to change, so I updated the snapshots accordingly. I'm not familiar enough with the codebase to be sure, but it seems like a step in the right direction to have the dependent classes generated after the classes they depend on.

Fixes: #2669

Summary by CodeRabbit

  • Bug Fixes

    • Improved GraphQL type resolution to avoid incorrect enum/Root-type fallbacks and ensure models map to correct types.
    • Ensured forward references in generated models are reliably resolved at runtime.
  • New Features

    • Added generated models, enums and type aliases to support additional GraphQL casing scenarios.
  • Tests

    • Updated and added tests validating casing, enum handling, model ordering, and reference resolution.

✏️ Tip: You can customize this high-level summary in your review settings.

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.50%. Comparing base (b458594) to head (5cae7e0).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2670      +/-   ##
==========================================
- Coverage   99.61%   99.50%   -0.11%     
==========================================
  Files          76       76              
  Lines       10648    10728      +80     
  Branches     1300     1314      +14     
==========================================
+ Hits        10607    10675      +68     
- Misses         21       32      +11     
- Partials       20       21       +1     
Flag Coverage Δ
unittests 99.50% <100.00%> (-0.11%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Dec 15, 2025

CodSpeed Performance Report

Merging #2670 will not alter performance

Comparing siminn-arnorgj:fix-types-casing (5cae7e0) with main (6aa5668)

Summary

✅ 50 untouched
⏩ 3 skipped1

Footnotes

  1. 3 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

self.references[type_.name] = Reference(
path=f"{paths!s}/{resolved_type.value}/{type_.name}",
name=type_.name,
name=self.model_resolver.get_class_name(type_.name, unique=False).name,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you elaborate:

  • the rationale of this change
  • Whether unique=False is safe or may impact the code generation

Thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I just realized this change is not necessary to fix my issue. The rationale was to move the GraphQL parser behaviour more in line with the jsonschema one. I was comparing the field names in the data models after calling parse_raw using the GraphQL parser and the jsonschema parser respectively. I noticed that the field names differed for equivalent schemas, and that the name used in the JSON Schema parser came from this line:

name = self.get_class_name(original_name, unique=False).name

The reason this change is not required is that Parser.__replace_duplicate_name_in_module is called after parsing, which fixes the names anyway. I will undo the change for now; let me know if this is actually desirable.

The unique flag actually doesn't do anything because there are no references at the model_resolver level.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 16, 2025

Walkthrough

Parser change: GraphQL field parsing now resolves named types by consulting the internal reference registry and sets a plain type name when no reference exists. Several GraphQL test fixtures and expected generated outputs were added, removed, or reordered to reflect the resolution and forward-ref updates.

Changes

Cohort / File(s) Change Summary
Core parser change
src/datamodel_code_generator/parser/graphql.py
Adjusted GraphQL field type resolution: removed explicit enum-only handling and now, after unwrapping to the named type, set data_type.reference from self.references when available; otherwise set data_type.type to the resolved object name.
GraphQL schemas added/removed
tests/data/graphql/casing.graphql, tests/data/graphql/enum-casing.graphql
Added casing.graphql (mixed-case and lowercase types); removed definitions from enum-casing.graphql.
Expected outputs added
tests/data/expected/main/graphql/casing.py
New expected generated module for casing.graphql (type aliases, enum, Pydantic models with __typename fields).
Expected outputs removed
tests/data/expected/main/graphql/enum_casing.py
Removed previously expected enum-casing output (consolidated into casing.py).
Forward-ref updates in expected outputs
tests/data/expected/main/graphql/simple_star_wars.py, tests/data/expected/main/graphql/simple_star_wars_extra_fields_allow.py
Added Film.update_forward_refs() and Person.update_forward_refs() calls at module end.
Class relocations in expected outputs
tests/data/expected/main/graphql/type_alias.py, tests/data/expected/main/graphql/type_alias_py312.py
Moved ModelWithTypeAliasField class to end of file without signature changes.
Union / resource reordering in expected outputs
tests/data/expected/parser/graphql/union-aliased-bug.py, tests/data/expected/parser/graphql/union-commented.py
Reordered / reintroduced Resource model declarations to follow aliases and fix forward-reference ordering.
Test rename and inputs
tests/main/graphql/test_main_graphql.py
Renamed test to test_main_graphql_casing, updated input from enum-casing.graphqlcasing.graphql and expected output enum_casing.pycasing.py.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Inspect src/datamodel_code_generator/parser/graphql.py for correctness of reference lookup vs. fallback assignment to data_type.type.
  • Verify enum handling paths and ensure enums still resolve correctly where intended.
  • Spot-check generated expected files (tests/data/expected/...) for consistent forward-ref placement and correct __typename literals.
  • Review test rename and updated fixtures for CI integration.

Poem

🐰 I hopped through schemas, names askew,
I nudged the parser, made refs true,
Lowercase, caps — now each one fits,
Forward-refs stitched up in bits,
A tiny rabbit dance — code anew 🌿

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: fixing the GraphQL parser to handle renamed objects correctly, which is the core objective of the PR.
Linked Issues check ✅ Passed The PR addresses all key coding requirements from issue #2669: field annotations now use renamed class names instead of raw GraphQL names, classes are ordered with dependencies first, and the parser correctly resolves renamed types.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives: parser fix in graphql.py, test additions/reorganizations for renamed objects, snapshot updates due to class reordering, and GraphQL schema files to support casing tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73f780a and 5cae7e0.

📒 Files selected for processing (1)
  • src/datamodel_code_generator/parser/graphql.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/datamodel_code_generator/parser/graphql.py (3)
src/datamodel_code_generator/model/base.py (1)
  • name (583-585)
src/datamodel_code_generator/parser/base.py (1)
  • data_type (969-971)
src/datamodel_code_generator/reference.py (1)
  • reference (77-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.14 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.9 on Windows
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: py312-black24 on Ubuntu
  • GitHub Check: 3.13 on macOS
  • GitHub Check: 3.11 on macOS
  • GitHub Check: 3.11 on Windows
  • GitHub Check: 3.13 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.14 on macOS
  • GitHub Check: Analyze (python)
  • GitHub Check: benchmarks
🔇 Additional comments (1)
src/datamodel_code_generator/parser/graphql.py (1)

485-489: LGTM! Type resolution now correctly uses renamed class names.

The uniform reference lookup successfully addresses the issue where renamed GraphQL types weren't being used in field annotations. By checking self.references first, field types now resolve to their renamed class names (e.g., Lowercasetype instead of lowercasetype).

The fallback to obj.name is appropriate for Query/Mutation root types that are intentionally excluded from references (lines 320-321). The pragma: no cover aligns with the maintainer's recommendation, as circular references involving these root types would generate non-functional code.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b458594 and b8f6591.

📒 Files selected for processing (12)
  • src/datamodel_code_generator/parser/graphql.py (2 hunks)
  • tests/data/expected/main/graphql/casing.py (1 hunks)
  • tests/data/expected/main/graphql/enum_casing.py (0 hunks)
  • tests/data/expected/main/graphql/simple_star_wars.py (1 hunks)
  • tests/data/expected/main/graphql/simple_star_wars_extra_fields_allow.py (1 hunks)
  • tests/data/expected/main/graphql/type_alias.py (1 hunks)
  • tests/data/expected/main/graphql/type_alias_py312.py (1 hunks)
  • tests/data/expected/parser/graphql/union-aliased-bug.py (1 hunks)
  • tests/data/expected/parser/graphql/union-commented.py (1 hunks)
  • tests/data/graphql/casing.graphql (1 hunks)
  • tests/data/graphql/enum-casing.graphql (0 hunks)
  • tests/main/graphql/test_main_graphql.py (1 hunks)
💤 Files with no reviewable changes (2)
  • tests/data/graphql/enum-casing.graphql
  • tests/data/expected/main/graphql/enum_casing.py
🧰 Additional context used
🧬 Code graph analysis (8)
tests/main/graphql/test_main_graphql.py (2)
tests/main/conftest.py (2)
  • output_file (94-96)
  • run_main_and_assert (196-352)
tests/test_main_kr.py (1)
  • output_file (37-39)
tests/data/expected/parser/graphql/union-commented.py (1)
tests/data/expected/parser/graphql/union-aliased-bug.py (2)
  • Resource (44-46)
  • UserMetadata (31-35)
tests/data/expected/main/graphql/type_alias.py (1)
tests/data/expected/main/graphql/type_alias_py312.py (1)
  • ModelWithTypeAliasField (50-56)
src/datamodel_code_generator/parser/graphql.py (2)
src/datamodel_code_generator/model/base.py (1)
  • name (583-585)
src/datamodel_code_generator/reference.py (3)
  • get_class_name (860-895)
  • reference (77-79)
  • get (843-845)
tests/data/expected/main/graphql/simple_star_wars.py (1)
tests/data/expected/main/graphql/simple_star_wars_dataclass.py (2)
  • Film (37-55)
  • Person (59-79)
tests/data/expected/main/graphql/simple_star_wars_extra_fields_allow.py (4)
tests/data/expected/main/graphql/simple_star_wars.py (2)
  • Film (36-54)
  • Person (57-77)
tests/data/expected/main/graphql/simple_star_wars_dataclass.py (2)
  • Film (37-55)
  • Person (59-79)
tests/data/expected/main/graphql/type_alias.py (1)
  • Person (33-36)
tests/data/expected/main/graphql/type_alias_py312.py (1)
  • Person (32-35)
tests/data/expected/main/graphql/casing.py (1)
src/datamodel_code_generator/model/type_alias.py (1)
  • TypeAlias (37-42)
tests/data/expected/parser/graphql/union-aliased-bug.py (1)
tests/data/expected/parser/graphql/union-commented.py (2)
  • Resource (64-66)
  • UserMetadata (35-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: 3.9 on Ubuntu
  • GitHub Check: 3.10 on Windows
  • GitHub Check: 3.10 on Ubuntu
  • GitHub Check: 3.11 on Windows
  • GitHub Check: py312-black22 on Ubuntu
  • GitHub Check: 3.9 on Windows
  • GitHub Check: 3.10 on macOS
  • GitHub Check: py312-isort7 on Ubuntu
  • GitHub Check: py312-isort6 on Ubuntu
  • GitHub Check: 3.12 on Windows
  • GitHub Check: 3.9 on macOS
  • GitHub Check: 3.11 on macOS
  • GitHub Check: benchmarks
  • GitHub Check: Analyze (python)
🔇 Additional comments (10)
tests/data/expected/parser/graphql/union-commented.py (1)

62-66: LGTM—Resource class correctly placed after its dependencies.

The Resource class is now generated after UserMetadata (which it depends on), aligning with the PR's improvement to dependency ordering. The class definition itself is unchanged.

src/datamodel_code_generator/parser/graphql.py (2)

330-330: Core fix: Use resolved class names for references.

This change addresses the reported bug where renamed GraphQL types weren't properly referenced. By using model_resolver.get_class_name(type_.name, unique=False).name, the reference now stores the resolved Python class name (e.g., "Lowercasetype") instead of the raw GraphQL name (e.g., "lowercasetype").

Regarding unique=False: This is safe here because we're establishing the initial canonical name during reference registration in _resolve_types. The unique=False parameter tells the resolver to apply naming conventions (PascalCase, etc.) without enforcing global uniqueness yet—uniqueness will be enforced later when actual model instances are created and added to the results. This avoids premature collisions during the initial type discovery phase.

Response to past review comment: The unique=False parameter is appropriate because this is the initial reference registration phase. Uniqueness enforcement happens later when models are instantiated via _create_data_model, where the reference is already established with the correctly cased name.


489-490: Essential fix: Wire up existing references before defaulting to primitives.

This change ensures that non-enum fields correctly use references from the self.references registry (populated during _resolve_types) before falling back to treating the type as a primitive. Without this, field annotations would use raw GraphQL type names even when renamed class references exist, causing the undefined type errors described in issue #2669.

tests/data/expected/parser/graphql/union-aliased-bug.py (1)

42-46: LGTM—Improved dependency ordering.

The Resource class is now generated after its dependency (UserMetadata), consistent with the PR's dependency ordering improvements.

tests/data/expected/main/graphql/type_alias.py (1)

49-57: LGTM—ModelWithTypeAliasField correctly ordered after its dependencies.

The class is now generated after the type aliases (SimpleString, String, UnionType) it depends on, consistent with improved dependency ordering.

tests/data/expected/main/graphql/type_alias_py312.py (1)

48-56: LGTM—Consistent with type_alias.py relocation.

Same dependency ordering improvement as the non-py312 variant.

tests/data/graphql/casing.graphql (1)

1-24: Excellent test coverage for the casing bug.

This schema comprehensively tests the scenarios addressed by the PR:

  • Lowercase enum and type names requiring capitalization (lines 1-7)
  • Name collision handling with "conflict" vs "Conflict" (lines 9-17)
  • References to renamed types (lines 19-24)

This test case directly validates that the parser correctly generates renamed class names in field annotations, which was the core issue in #2669.

tests/data/expected/main/graphql/casing.py (1)

54-59: Critical validation: Field annotations correctly use renamed class names.

This Ref class demonstrates the fix is working correctly:

  • Line 55: bar: Lowercase (not lowercase)
  • Line 56: baz: Lowercasetype (not lowercasetype)
  • Line 58: spam: ConflictModel (not conflict)

Before the fix, these fields would reference the raw GraphQL names (e.g., lowercasetype), causing undefined type errors. The fix ensures fields use the actual Python class names generated by the parser.

tests/data/expected/main/graphql/simple_star_wars_extra_fields_allow.py (1)

174-177: LGTM—Explicit forward reference resolution for circular dependencies.

Adding update_forward_refs() calls for Film and Person is appropriate since they have circular references (Film → List[Person] and Person → List[Film]). This ensures Pydantic properly resolves the forward-referenced types at runtime.

tests/data/expected/main/graphql/simple_star_wars.py (1)

156-159: LGTM!

The update_forward_refs() calls correctly resolve the circular dependencies between Film and Person classes. This is required for Pydantic models with forward references and aligns with the pattern used in related test expectations.

Comment thread tests/main/graphql/test_main_graphql.py
@siminn-arnorgj
Copy link
Copy Markdown
Contributor Author

siminn-arnorgj commented Dec 16, 2025

I further changed the references logic to remove the special handling for enums as now every type gets a reference. This causes the coverage to drop because the "else" branch is now unreachable in all tests. Should I exclude that branch from testing or create a test case with a schema like this?

type Query {
  foo: bar!
}

type bar {
  baz: Query!
}

This will hit the else branch but this kind of schema is currently not handled correctly. (Query will be undefined)

@koxudaxi
Copy link
Copy Markdown
Owner

@siminn-arnorgj
Generating something that can't actually be used is pointless, so adding a # pragma: no cover comment will suffice.

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.

GraphQL parser does not handle renamed objects correctly

3 participants