Skip to content

Conversation

@BaptisteGi
Copy link
Contributor

@BaptisteGi BaptisteGi commented Dec 27, 2025

GraphQL Query Analyzer - Migration & Implementation

Overview

The InfrahubQueryAnalyzer enables static analysis of GraphQL queries to determine if they target single or multiple objects. This is exposed via infrahubctl graphql check [query-path].

Architecture

Components

File Purpose
infrahub_sdk/query_analyzer.py Core analyzer with InfrahubQueryAnalyzer class and data models
infrahub_sdk/schema/main.py BranchSchema with node_names, generic_names, profile_names properties and getter methods
infrahub_sdk/ctl/graphql.py CLI check command implementation

Type Adaptations from Backend

Backend SDK
infrahub.core.constants.RelationshipCardinality infrahub_sdk.schema.RelationshipCardinality
infrahub.core.schema.GenericSchema infrahub_sdk.schema.GenericSchemaAPI
infrahub.core.schema.MainSchemaTypes NodeSchemaAPI | GenericSchemaAPI | ProfileSchemaAPI | TemplateSchemaAPI
SchemaBranch SchemaBranchProtocol (satisfied by BranchSchema)
SchemaNotFoundError KeyError

Implementation Details

Schema Data Flow

client.schema.all() returns MutableMapping[str, MainSchemaTypesAPI] — a flat dict with kind names as keys and schema objects as values. This is not the raw API response format.

# Correct usage:
schema_data = await client.schema.all(branch=branch)
branch_schema = BranchSchema(hash="", nodes=schema_data)

# Wrong - from_api_response expects {"nodes": [...], "generics": [...], ...}
# branch_schema = BranchSchema.from_api_response(schema_data)

Single-Target Detection Logic

A query is considered "single-target" when:

  1. All operations have infrahub_model with uniqueness_constraints
  2. Each operation has an argument matching the uniqueness constraint format
  3. The argument is either:
    • A required variable ($name: String!)
    • A static value (name__value: "my-tag")
    • The ids argument with a required variable

Critical: The uniqueness constraint must match the GraphQL argument name format (e.g., name__value, not name).

Edge Cases Handled

Scenario Behavior
Empty queries list only_has_unique_targets returns False
Unknown model in query Model skipped, top_level_kinds empty
No models found Warning displayed, exit code 1
Optional variable filter Returns False (multi-target)
No .gql files in path Error displayed, exit code 1
Multiple files with errors All files checked, exit code 1 if any errors

Usage

The check command accepts a file or directory path. If a directory is provided, it recursively finds all .gql files. If no path is provided, it defaults to the current directory.

# Check a single file
$ infrahubctl graphql check query1.gql
Checking 1 GraphQL file...

────────────────────────────────────────────────────────────
[1/1] query1.gql
  Top-level kinds: BuiltinTag
  Result: Single-target query (good)
    This query targets unique nodes, enabling selective artifact regeneration.
────────────────────────────────────────────────────────────

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added a new `check` CLI command that validates GraphQL query files, provides per-file analysis with error details, and displays a final summary with query counts.
  * Enhanced schema introspection with new accessor methods for faster schema object retrieval.

* **Tests**
  * Added unit tests for GraphQL query validation and schema reporting functionality.

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->:
  1 single-target

# Check all .gql files in a directory
$ infrahubctl graphql check queries/
Checking 3 GraphQL files...

────────────────────────────────────────────────────────────
[1/3] queries/query1.gql
  Top-level kinds: BuiltinTag
  Result: Single-target query (good)
    This query targets unique nodes, enabling selective artifact regeneration.
────────────────────────────────────────────────────────────
[2/3] queries/query3.gql
  Validation failed:
    - Cannot query field 'BuiltinUnknown' on type 'Query'. Did you mean 'BuiltinTag'?
────────────────────────────────────────────────────────────
[3/3] queries/query2.gql
  Top-level kinds: BuiltinTag
  Result: Multi-target query
    May cause excessive artifact regeneration. Fix: filter by ID or unique attribute.
────────────────────────────────────────────────────────────

Summary:
  1 single-target
  1 multi-target
    See: https://docs.infrahub.app/topics/graphql
  1 errors

Tests

Unit tests in tests/unit/sdk/test_infrahub_query_analyzer.py cover:

  • Empty queries handling
  • Multi-target detection (no filter)
  • Single-target detection (required unique filter, static filter, ids filter)
  • Optional filter handling
  • Top-level kinds extraction

Known Limitations & Future Improvements

1. BranchSchema Construction Workaround

Issue: Direct instantiation bypasses proper factory method.

# Current workaround
branch_schema = BranchSchema(hash="", nodes=schema_data)

Improvement: Add a factory method to BranchSchema that accepts the client.schema.all() output format:

@classmethod
def from_schema_dict(cls, nodes: MutableMapping[str, MainSchemaTypesAPI], hash: str = "") -> Self:
    return cls(hash=hash, nodes=nodes)

Or modify from_api_response to detect the input format.

2. Uniqueness Constraint Format Mismatch

Issue: The analyzer compares argument.name (e.g., name__value) against uniqueness_constraints (e.g., [["name"]]). These use different formats.

Current behavior: Only works if uniqueness_constraints are defined with the GraphQL argument format (name__value).

Improvement: Normalize the comparison — either strip __value suffix from arguments or expand constraint names to include the suffix.

3. Template Schema Handling

Issue: _get_operations() checks node_names, generic_names, profile_names but not templates.

if model_name in self.schema_branch.node_names:
    ...
elif model_name in self.schema_branch.generic_names:
    ...
elif model_name in self.schema_branch.profile_names:
    ...
else:
    continue  # Templates silently skipped

Improvement: Add template_names property and corresponding handling.

4. Schema Hash Not Populated

Issue: BranchSchema is constructed with empty hash.

branch_schema = BranchSchema(hash="", nodes=schema_data)

Improvement: Fetch and pass the actual schema hash for cache validation purposes.

5. GraphQL Schema Fetched Separately

Issue: Two API calls required — one for Infrahub schema, one for GraphQL schema.

Improvement: Consider caching or combining these calls if performance becomes an issue.

@coderabbitai
Copy link

coderabbitai bot commented Dec 27, 2025

Walkthrough

This change introduces a new GraphQL query analysis system for Infrahub SDK. A new query_analyzer.py module provides data structures and utilities to parse GraphQL queries, handle fragments with circular dependency detection, build query node trees mapping to Infrahub models, and extract query metadata including variables and mutations. The BranchSchema class gains type-specific accessor methods and name properties. A new CLI check command discovers and analyzes .gql files using the query analyzer. Unit tests validate the analyzer's behavior with various query shapes and uniqueness constraints.

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Graphql graph CTL command' is vague and contains unclear terminology ('graph' appears twice with different meanings) that doesn't clearly convey the main change. Revise title to be more specific and descriptive, such as 'Add GraphQL query check CLI command' or 'Introduce infrahubctl graphql check command for query validation'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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.

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Dec 27, 2025

Deploying infrahub-sdk-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: e40b48b
Status: ✅  Deploy successful!
Preview URL: https://9c789d97.infrahub-sdk-python.pages.dev
Branch Preview URL: https://bgi-grahql-check-command.infrahub-sdk-python.pages.dev

View logs

@BaptisteGi BaptisteGi marked this pull request as ready for review December 27, 2025 11:30
Copy link

@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: 3

🧹 Nitpick comments (1)
infrahub_sdk/query_analyzer.py (1)

417-424: Consider simplifying the schema_branch type annotation.

The schema_branch parameter is typed as SchemaBranchProtocol | BranchSchema, but since BranchSchema implements SchemaBranchProtocol, the union type is redundant. You could simplify to just SchemaBranchProtocol.

🔎 Proposed refactor
     def __init__(
         self,
         query: str,
-        schema_branch: SchemaBranchProtocol | BranchSchema,
+        schema_branch: SchemaBranchProtocol,
         schema: GraphQLSchema | None = None,
         query_variables: dict[str, Any] | None = None,
         operation_name: str | None = None,
     ) -> None:

This maintains flexibility while being more precise about the interface requirement.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f1cb0c and e40b48b.

📒 Files selected for processing (4)
  • infrahub_sdk/ctl/graphql.py
  • infrahub_sdk/query_analyzer.py
  • infrahub_sdk/schema/main.py
  • tests/unit/sdk/test_infrahub_query_analyzer.py
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Use type hints on all function signatures
Never mix async/sync inappropriately
Never bypass type checking without justification

Files:

  • infrahub_sdk/schema/main.py
  • infrahub_sdk/query_analyzer.py
  • infrahub_sdk/ctl/graphql.py
  • tests/unit/sdk/test_infrahub_query_analyzer.py
infrahub_sdk/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Follow async/sync dual pattern for new features in the Python SDK

Files:

  • infrahub_sdk/schema/main.py
  • infrahub_sdk/query_analyzer.py
  • infrahub_sdk/ctl/graphql.py
infrahub_sdk/ctl/**/*.py

📄 CodeRabbit inference engine (infrahub_sdk/ctl/AGENTS.md)

infrahub_sdk/ctl/**/*.py: Use @catch_exception(console=console) decorator on all async CLI commands
Include CONFIG_PARAM in all CLI command function parameters, even if unused
Use initialize_client() or initialize_client_sync() from ctl.client for client creation in CLI commands
Use Rich library for output formatting in CLI commands (tables, panels, console.print) instead of plain print() statements
Do not instantiate InfrahubClient directly; always use initialize_client() or initialize_client_sync() helper functions
Do not use plain print() statements in CLI commands; use Rich console.print() instead

Files:

  • infrahub_sdk/ctl/graphql.py
tests/**/*.py

📄 CodeRabbit inference engine (tests/AGENTS.md)

tests/**/*.py: Use httpx_mock fixture for HTTP mocking in tests instead of making real HTTP requests
Do not add @pytest.mark.asyncio decorator to async test functions (async auto-mode is globally enabled)

Files:

  • tests/unit/sdk/test_infrahub_query_analyzer.py
tests/unit/**/*.py

📄 CodeRabbit inference engine (tests/AGENTS.md)

Unit tests must be fast, mocked, and have no external dependencies

Files:

  • tests/unit/sdk/test_infrahub_query_analyzer.py
🧬 Code graph analysis (3)
infrahub_sdk/query_analyzer.py (2)
infrahub_sdk/analyzer.py (2)
  • GraphQLQueryAnalyzer (32-121)
  • operations (55-65)
infrahub_sdk/schema/main.py (15)
  • GenericSchemaAPI (288-292)
  • NodeSchemaAPI (312-314)
  • ProfileSchemaAPI (317-318)
  • TemplateSchemaAPI (321-322)
  • BranchSchema (361-460)
  • node_names (368-370)
  • generic_names (373-375)
  • profile_names (378-380)
  • get (382-395)
  • get_node (397-407)
  • get_generic (409-419)
  • get_profile (421-431)
  • kind (279-280)
  • attribute_names (223-224)
  • get_relationship_or_none (193-197)
infrahub_sdk/ctl/graphql.py (5)
infrahub_sdk/ctl/client.py (1)
  • initialize_client (10-25)
infrahub_sdk/ctl/utils.py (1)
  • catch_exception (78-106)
infrahub_sdk/graphql/utils.py (2)
  • insert_fragments_inline (13-31)
  • remove_fragment_import (34-40)
infrahub_sdk/query_analyzer.py (3)
  • InfrahubQueryAnalyzer (407-760)
  • only_has_unique_targets (386-404)
  • query_report (460-464)
infrahub_sdk/schema/main.py (1)
  • BranchSchema (361-460)
tests/unit/sdk/test_infrahub_query_analyzer.py (1)
infrahub_sdk/query_analyzer.py (4)
  • GraphQLQueryReport (289-404)
  • only_has_unique_targets (386-404)
  • query_report (460-464)
  • top_level_kinds (354-355)
⏰ 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). (2)
  • GitHub Check: integration-tests-latest-infrahub
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (3)
infrahub_sdk/schema/main.py (1)

367-431: LGTM! Well-structured schema accessors.

The new properties and getter methods provide clean, type-safe access to different schema types. The error handling with KeyError and TypeError is appropriate, and the docstrings clearly document the API compatibility with backend SchemaBranch.

tests/unit/sdk/test_infrahub_query_analyzer.py (1)

1-170: LGTM! Comprehensive test coverage.

The test suite effectively validates the query analyzer's behavior across various scenarios:

  • Empty queries
  • Single/multi-target detection with required, optional, and static filters
  • Top-level kinds extraction
  • Unknown model handling

The fixtures provide isolated, fast test execution without external dependencies.

infrahub_sdk/query_analyzer.py (1)

1-760: LGTM! Solid architecture for GraphQL query analysis.

The implementation provides comprehensive query parsing with:

  • Fragment handling with circular dependency detection
  • Hierarchical query node tree construction
  • Model and permission reasoning
  • Clean separation of concerns with dataclasses and protocols

The code is well-structured with proper type hints throughout.

Comment on lines +40 to +50
def _print_query_result(console: Console, report: object, results: CheckResults) -> None:
"""Print the result for a single query analysis."""
if report.only_has_unique_targets:
console.print("[green] Result: Single-target query (good)[/green]")
console.print(" This query targets unique nodes, enabling selective artifact regeneration.")
results.single_target_count += 1
else:
console.print("[yellow] Result: Multi-target query[/yellow]")
console.print(" May cause excessive artifact regeneration. Fix: filter by ID or unique attribute.")
results.multi_target_count += 1

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Improve type hint for report parameter.

The report parameter is typed as object, which bypasses type checking when accessing report.only_has_unique_targets on line 42. This should be GraphQLQueryReport for proper type safety.

🔎 Proposed fix

Add the import at the top of the file:

 from ..query_analyzer import InfrahubQueryAnalyzer
+from ..query_analyzer import GraphQLQueryReport
 from ..schema import BranchSchema

Then update the type hint:

-def _print_query_result(console: Console, report: object, results: CheckResults) -> None:
+def _print_query_result(console: Console, report: GraphQLQueryReport, results: CheckResults) -> None:
     """Print the result for a single query analysis."""

As per coding guidelines, type hints are required on all function signatures.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _print_query_result(console: Console, report: object, results: CheckResults) -> None:
"""Print the result for a single query analysis."""
if report.only_has_unique_targets:
console.print("[green] Result: Single-target query (good)[/green]")
console.print(" This query targets unique nodes, enabling selective artifact regeneration.")
results.single_target_count += 1
else:
console.print("[yellow] Result: Multi-target query[/yellow]")
console.print(" May cause excessive artifact regeneration. Fix: filter by ID or unique attribute.")
results.multi_target_count += 1
from ..query_analyzer import InfrahubQueryAnalyzer
from ..query_analyzer import GraphQLQueryReport
from ..schema import BranchSchema
def _print_query_result(console: Console, report: GraphQLQueryReport, results: CheckResults) -> None:
"""Print the result for a single query analysis."""
if report.only_has_unique_targets:
console.print("[green] Result: Single-target query (good)[/green]")
console.print(" This query targets unique nodes, enabling selective artifact regeneration.")
results.single_target_count += 1
else:
console.print("[yellow] Result: Multi-target query[/yellow]")
console.print(" May cause excessive artifact regeneration. Fix: filter by ID or unique attribute.")
results.multi_target_count += 1
🤖 Prompt for AI Agents
In infrahub_sdk/ctl/graphql.py around lines 40 to 50, the function
_print_query_result currently types its report parameter as object which
disables type checking when accessing report.only_has_unique_targets; change the
parameter type to GraphQLQueryReport and add the corresponding import at the top
of the file (import GraphQLQueryReport from the module where that
dataclass/typing is defined in the codebase), then update the function signature
to use GraphQLQueryReport so static type checkers can validate attribute access.

Comment on lines +293 to +296
client = initialize_client()

schema_data = await client.schema.all(branch=branch)
branch_schema = BranchSchema(hash="", nodes=schema_data)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the BranchSchema class definition
rg -n "class BranchSchema" --type=py

Repository: opsmill/infrahub-sdk-python

Length of output: 133


🏁 Script executed:

# Find the schema.all() method implementation
rg -n "def all\(" --type=py -A 10 -B 2 | grep -A 10 "def all"

Repository: opsmill/infrahub-sdk-python

Length of output: 8906


🏁 Script executed:

# Look for other usages of BranchSchema to understand the hash pattern
rg -n "BranchSchema\(" --type=py -B 3 -A 3

Repository: opsmill/infrahub-sdk-python

Length of output: 2290


🏁 Script executed:

# Check the specific file mentioned in the review
cat -n infrahub_sdk/ctl/graphql.py | sed -n '280,310p'

Repository: opsmill/infrahub-sdk-python

Length of output: 1287


🏁 Script executed:

# Look at the full schema.all() implementation
sed -n '281,320p' infrahub_sdk/schema/__init__.py

Repository: opsmill/infrahub-sdk-python

Length of output: 1855


🏁 Script executed:

# Search for schema_hash or hash-related methods in the schema module
rg -n "schema_hash|schema.*hash" --type=py -B 2 -A 2 infrahub_sdk/schema/

Repository: opsmill/infrahub-sdk-python

Length of output: 4010


🏁 Script executed:

# Look for other BranchSchema usages in production code (not tests)
rg -n "BranchSchema(" --type=py -B 3 -A 3 | grep -v "tests/"

Repository: opsmill/infrahub-sdk-python

Length of output: 147


🏁 Script executed:

# Check what SchemaCacheManager provides
rg -n "class SchemaCacheManager" --type=py -A 20

Repository: opsmill/infrahub-sdk-python

Length of output: 53


🏁 Script executed:

# Fix the regex for BranchSchema search
rg -n "BranchSchema\\\(" --type=py -B 3 -A 3 | grep -v "tests/"

Repository: opsmill/infrahub-sdk-python

Length of output: 151


🏁 Script executed:

# Look at the SchemaCacheManager/SchemaManager class to see if cache is accessible
sed -n '1,100p' infrahub_sdk/schema/__init__.py | head -60

Repository: opsmill/infrahub-sdk-python

Length of output: 1446


🏁 Script executed:

# Check what the _fetch method returns and how it creates BranchSchema
rg -n "async def _fetch" --type=py -A 30 infrahub_sdk/schema/__init__.py

Repository: opsmill/infrahub-sdk-python

Length of output: 1397


🏁 Script executed:

# See if there's a way to access the schema cache from client
rg -n "self.cache\[" --type=py -B 2 -A 2 infrahub_sdk/schema/__init__.py | head -40

Repository: opsmill/infrahub-sdk-python

Length of output: 1514


🏁 Script executed:

# Check if there's a method to get the full BranchSchema (not just nodes) from the schema manager
rg -n "def.*schema" --type=py infrahub_sdk/schema/__init__.py | head -20

Repository: opsmill/infrahub-sdk-python

Length of output: 1798


🏁 Script executed:

# Look at the InfrahubSchema class structure to see all available methods
sed -n '100,250p' infrahub_sdk/schema/__init__.py

Repository: opsmill/infrahub-sdk-python

Length of output: 5835


🏁 Script executed:

# Check if there's a cache property or way to access the BranchSchema with hash
rg -n "cache\[" --type=py -B 3 -A 1 infrahub_sdk/schema/__init__.py | head -50

Repository: opsmill/infrahub-sdk-python

Length of output: 2072


🏁 Script executed:

# Look at how BranchSchema.from_api_response works to understand how hash is populated
sed -n '400,500p' infrahub_sdk/schema/main.py

Repository: opsmill/infrahub-sdk-python

Length of output: 2314


🏁 Script executed:

# Verify cache is public and check if there are other usages accessing cache directly
rg -n "client\.schema\.cache" --type=py -B 2 -A 2

Repository: opsmill/infrahub-sdk-python

Length of output: 2160


🏁 Script executed:

# Check if branch variable is available at that point in the function
sed -n '270,300p' infrahub_sdk/ctl/graphql.py

Repository: opsmill/infrahub-sdk-python

Length of output: 1177


Use the schema from cache instead of constructing with empty hash.

After calling await client.schema.all(branch=branch), the full BranchSchema with the proper hash is available in client.schema.cache[branch]. Replace the manual construction with:

branch_schema = client.schema.cache[branch]

This avoids the empty hash placeholder and uses the actual schema hash from the API response.

🤖 Prompt for AI Agents
In infrahub_sdk/ctl/graphql.py around lines 293 to 296, the code calls
client.schema.all(branch=branch) then constructs BranchSchema with an empty hash
which loses the real schema hash; replace the manual construction and instead
obtain the full BranchSchema from the client's cache
(client.schema.cache[branch]) so the real hash and nodes from the API response
are used.

Comment on lines +645 to +665
def _populate_inline_fragment_node(
self, node: InlineFragmentNode, query_node: GraphQLQueryNode
) -> GraphQLQueryNode:
infrahub_model = self.schema_branch.get(name=node.type_condition.name.value, duplicate=False)
context_type = ContextType.DIRECT
current_node = GraphQLQueryNode(
parent=query_node,
path=node.type_condition.name.value,
context_type=context_type,
infrahub_model=infrahub_model,
)
if node.selection_set:
selections = self._get_selections(selection_set=node.selection_set)
for field_node in selections.field_nodes:
current_node.children.append(self._populate_field_node(node=field_node, query_node=current_node))
for inline_fragment_node in selections.inline_fragment_nodes:
current_node.children.append(
self._populate_inline_fragment_node(node=inline_fragment_node, query_node=current_node)
)

return current_node
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for unknown inline fragment types.

In _populate_inline_fragment_node (line 648), the call to self.schema_branch.get() can raise KeyError if the type is not found in the schema. Unlike _populate_named_fragments (lines 582-584) which handles this with try-except, this code will propagate the exception.

🔎 Proposed fix
     def _populate_inline_fragment_node(
         self, node: InlineFragmentNode, query_node: GraphQLQueryNode
     ) -> GraphQLQueryNode:
-        infrahub_model = self.schema_branch.get(name=node.type_condition.name.value, duplicate=False)
+        try:
+            infrahub_model = self.schema_branch.get(name=node.type_condition.name.value, duplicate=False)
+        except KeyError:
+            infrahub_model = None
         context_type = ContextType.DIRECT
         current_node = GraphQLQueryNode(
             parent=query_node,
             path=node.type_condition.name.value,
             context_type=context_type,
             infrahub_model=infrahub_model,
         )

This ensures consistent behavior when encountering unknown types in inline fragments, similar to how named fragments handle missing types.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _populate_inline_fragment_node(
self, node: InlineFragmentNode, query_node: GraphQLQueryNode
) -> GraphQLQueryNode:
infrahub_model = self.schema_branch.get(name=node.type_condition.name.value, duplicate=False)
context_type = ContextType.DIRECT
current_node = GraphQLQueryNode(
parent=query_node,
path=node.type_condition.name.value,
context_type=context_type,
infrahub_model=infrahub_model,
)
if node.selection_set:
selections = self._get_selections(selection_set=node.selection_set)
for field_node in selections.field_nodes:
current_node.children.append(self._populate_field_node(node=field_node, query_node=current_node))
for inline_fragment_node in selections.inline_fragment_nodes:
current_node.children.append(
self._populate_inline_fragment_node(node=inline_fragment_node, query_node=current_node)
)
return current_node
def _populate_inline_fragment_node(
self, node: InlineFragmentNode, query_node: GraphQLQueryNode
) -> GraphQLQueryNode:
try:
infrahub_model = self.schema_branch.get(name=node.type_condition.name.value, duplicate=False)
except KeyError:
infrahub_model = None
context_type = ContextType.DIRECT
current_node = GraphQLQueryNode(
parent=query_node,
path=node.type_condition.name.value,
context_type=context_type,
infrahub_model=infrahub_model,
)
if node.selection_set:
selections = self._get_selections(selection_set=node.selection_set)
for field_node in selections.field_nodes:
current_node.children.append(self._populate_field_node(node=field_node, query_node=current_node))
for inline_fragment_node in selections.inline_fragment_nodes:
current_node.children.append(
self._populate_inline_fragment_node(node=inline_fragment_node, query_node=current_node)
)
return current_node
🤖 Prompt for AI Agents
In infrahub_sdk/query_analyzer.py around lines 645 to 665, wrap the call to
self.schema_branch.get(...) in a try/except KeyError to match the named-fragment
handling: if the type is missing catch KeyError, log a warning (using the same
logger used elsewhere in this class) including the missing type name, and return
the parent query_node (i.e., skip creating/populating this inline fragment)
instead of allowing the exception to propagate.

@codecov
Copy link

codecov bot commented Dec 27, 2025

Codecov Report

❌ Patch coverage is 27.85714% with 404 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
infrahub_sdk/query_analyzer.py 31.59% 307 Missing and 7 partials ⚠️
infrahub_sdk/ctl/graphql.py 15.06% 62 Missing ⚠️
infrahub_sdk/schema/main.py 0.00% 28 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (1f1cb0c) and HEAD (e40b48b). Click for more details.

HEAD has 13 uploads less than BASE
Flag BASE (1f1cb0c) HEAD (e40b48b)
python-3.11 2 0
python-3.12 2 0
python-3.10 2 0
python-filler-3.12 2 0
python-3.14 2 0
python-3.13 2 0
integration-tests 2 1
@@             Coverage Diff             @@
##           stable     #711       +/-   ##
===========================================
- Coverage   76.03%   34.27%   -41.77%     
===========================================
  Files         113      114        +1     
  Lines        9744    10303      +559     
  Branches     1491     1621      +130     
===========================================
- Hits         7409     3531     -3878     
- Misses       1840     6459     +4619     
+ Partials      495      313      -182     
Flag Coverage Δ
integration-tests 34.27% <27.85%> (-0.38%) ⬇️
python-3.10 ?
python-3.11 ?
python-3.12 ?
python-3.13 ?
python-3.14 ?
python-filler-3.12 ?

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

Files with missing lines Coverage Δ
infrahub_sdk/schema/main.py 9.86% <0.00%> (-79.63%) ⬇️
infrahub_sdk/ctl/graphql.py 25.90% <15.06%> (-9.21%) ⬇️
infrahub_sdk/query_analyzer.py 31.59% <31.59%> (ø)

... and 83 files with indirect coverage changes

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

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.

2 participants