-
Notifications
You must be signed in to change notification settings - Fork 6
Add command to generate Pydantic model based on a GraphQL query #546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds an infrahubctl CLI group Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## develop #546 +/- ##
===========================================
- Coverage 76.35% 75.37% -0.99%
===========================================
Files 102 108 +6
Lines 10086 9298 -788
Branches 2081 1847 -234
===========================================
- Hits 7701 7008 -693
+ Misses 1831 1802 -29
+ Partials 554 488 -66
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 9 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Deploying infrahub-sdk-python with
|
| Latest commit: |
d490fd8
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://20553454.infrahub-sdk-python.pages.dev |
| Branch Preview URL: | https://dga-20250913-graphql-return.infrahub-sdk-python.pages.dev |
9f31369 to
6754510
Compare
6754510 to
911ca81
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (9)
tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (1)
24-33: Consider the verbosity of wrapper models for simple fields.The separate
BaseModelclasses forname,description, andstatus(each containing onlyvalue: Optional[str]) create a deeply nested structure. While this may accurately reflect the GraphQL schema, it adds verbosity.If these models are auto-generated from the GraphQL schema (as suggested by the fixture location), this is acceptable. However, if manually maintained, consider whether the GraphQL schema could be simplified.
pyproject.toml (1)
49-49: Approve ariadne-codegen dependencyVersion ^0.15.2 is available on PyPI and has no known security advisories; consider updating to ^0.15.3 for the latest patch fixes, though 0.15.2 is acceptable.
docs/docs/python-sdk/guides/python-typing.mdx (1)
130-151: Consider enhancing the example workflow with complete code samples.The workflow steps are clear, but the example would benefit from showing a complete
.gqlfile example and a more detailed usage example with error handling.Consider adding a concrete
.gqlfile example in step 1:# queries/get_devices.gql query GetDevices { InfraDevice { edges { node { id name { value } status { value } } } } }And enhancing step 3 with error handling:
from .queries.get_devices import GetDevicesQuery from pydantic import ValidationError try: response = await client.execute_graphql(query=MY_QUERY) data = GetDevicesQuery(**response) # Access type-safe fields for edge in data.infra_device.edges: print(f"Device: {edge.node.name.value}") except ValidationError as e: print(f"Invalid response structure: {e}")tests/unit/sdk/graphql/conftest.py (1)
6-13: Consider consolidating duplicate enum definitions.The
MyStrEnumandMyIntEnumclasses are duplicated in both this conftest file andtests/unit/sdk/graphql/test_renderer.py(lines 8-15). To follow DRY principles, consider keeping these enum definitions only in conftest.py and removing them from test_renderer.py since fixtures defined here are already shared across test modules.Apply this approach:
- Keep the enum definitions here in conftest.py
- Remove lines 8-15 from test_renderer.py
- Import the enums in test_renderer.py if needed:
from .conftest import MyStrEnum, MyIntEnuminfrahub_sdk/ctl/graphql.py (4)
38-54: Consider handling non-file, non-directory paths explicitly.If
query_pathexists but is neither a regular file nor a directory (e.g., a broken symlink or a special file), the function will return an empty list fromglob()without any indication to the user. This may lead to confusion when no files are found.Apply this diff to add an explicit check:
if not query_path.is_dir() and query_path.is_file(): return [query_path] + + if not query_path.is_dir(): + raise ValueError(f"{query_path} is neither a file nor a directory") return list(query_path.glob("**/*.gql"))
57-74: Consider catching parse exceptions for better error messages.
parse(queries_str)on line 66 can raiseGraphQLSyntaxErrorif the query file contains invalid GraphQL syntax. Currently, this exception propagates to the caller. While the caller ingenerate_return_typesdoes catch genericValueError,GraphQLSyntaxErroris not a subclass ofValueErrorand will not be caught, potentially causing an unhandled exception.Apply this diff to catch and re-raise as
ValueErrorfor consistent error handling:queries_str = queries_path.read_text(encoding="utf-8") - queries_ast = parse(queries_str) + try: + queries_ast = parse(queries_str) + except Exception as exc: + raise ValueError(f"Failed to parse GraphQL query: {exc}") from exc validation_errors = validate(
77-88: Ensure parent directory exists before writing generated files.Line 87 writes to
file_pathwithout verifying that the parent directory exists. If the directory structure has been modified or deleted between discovery and generation, this will raise aFileNotFoundError.Apply this diff to ensure the directory exists:
for file_name, module in package._result_types_files.items(): file_path = directory / file_name + file_path.parent.mkdir(parents=True, exist_ok=True) insert_fragments_inline(module, fragment)
Note on private attribute access:
This function accesses private attributes (
_result_types_files,_add_comments_to_code,_generated_files) fromPackageGenerator. While this is necessary for the plugin architecture and wrapper functionality, it creates tight coupling with ariadne-codegen internals. Consider adding a comment documenting this dependency or, if ariadne-codegen provides public APIs for these operations in the future, migrating to those.
112-118: Consider clarifying default query path behavior.The
queryparameter defaults toPath.cwd()(line 115), meaning the command will search the current working directory for.gqlfiles if no path is provided. This might be unexpected for users who expect an explicit path argument. Consider either:
- Making the parameter required (
query: Path = typer.Argument(..., help=...)), or- Adding a note in the help text that it defaults to the current directory.
Apply this diff to clarify the default behavior:
query: Path = typer.Argument(Path.cwd(), help="Location of the GraphQL query file(s)."), + # Or make it required: + # query: Path = typer.Argument(..., help="Location of the GraphQL query file(s)."),Update the help text:
- query: Path = typer.Argument(Path.cwd(), help="Location of the GraphQL query file(s)."), + query: Path = typer.Argument(Path.cwd(), help="Location of the GraphQL query file(s). Defaults to current directory."),infrahub_sdk/graphql/utils.py (1)
24-30: Refactor to avoid modifying list during iteration.Lines 26-28 remove an item from
module.bodywhile iterating over it. Although the immediatereturnprevents issues in practice, this pattern is fragile and will silently skip subsequent fragment imports if multiple exist.Apply this diff to handle all fragment imports safely:
def remove_fragment_import(module: ast.Module) -> ast.Module: """Remove the fragment import from the module.""" - for item in module.body: - if isinstance(item, ast.ImportFrom) and item.module == "fragments": - module.body.remove(item) - return module - return module + module.body = [ + item for item in module.body + if not (isinstance(item, ast.ImportFrom) and item.module == "fragments") + ] + return moduleAlternatively, if only the first fragment import should be removed (and multiples are invalid), document this explicitly in the docstring.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
poetry.lockis excluded by!**/*.lock
📒 Files selected for processing (21)
.vale/styles/Infrahub/branded-terms-case-swap.yml(0 hunks)changelog/+gql-command.added.md(1 hunks)docs/docs/infrahubctl/infrahubctl-graphql.mdx(1 hunks)docs/docs/python-sdk/guides/python-typing.mdx(1 hunks)infrahub_sdk/ctl/cli_commands.py(2 hunks)infrahub_sdk/ctl/graphql.py(1 hunks)infrahub_sdk/graphql/__init__.py(1 hunks)infrahub_sdk/graphql/constants.py(1 hunks)infrahub_sdk/graphql/plugin.py(1 hunks)infrahub_sdk/graphql/query.py(1 hunks)infrahub_sdk/graphql/renderers.py(3 hunks)infrahub_sdk/graphql/utils.py(1 hunks)pyproject.toml(5 hunks)tests/fixtures/unit/test_graphql_plugin/python01.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python02.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py(1 hunks)tests/unit/sdk/graphql/conftest.py(1 hunks)tests/unit/sdk/graphql/test_plugin.py(1 hunks)tests/unit/sdk/graphql/test_query.py(1 hunks)tests/unit/sdk/graphql/test_renderer.py(1 hunks)
💤 Files with no reviewable changes (1)
- .vale/styles/Infrahub/branded-terms-case-swap.yml
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method
Files:
tests/fixtures/unit/test_graphql_plugin/python02.pytests/fixtures/unit/test_graphql_plugin/python01.pytests/unit/sdk/graphql/conftest.pyinfrahub_sdk/graphql/__init__.pyinfrahub_sdk/graphql/query.pytests/unit/sdk/graphql/test_renderer.pyinfrahub_sdk/graphql/plugin.pyinfrahub_sdk/graphql/renderers.pyinfrahub_sdk/ctl/graphql.pyinfrahub_sdk/graphql/constants.pyinfrahub_sdk/graphql/utils.pytests/fixtures/unit/test_graphql_plugin/python01_after_annotation.pytests/unit/sdk/graphql/test_plugin.pyinfrahub_sdk/ctl/cli_commands.pytests/fixtures/unit/test_graphql_plugin/python02_after_annotation.pytests/unit/sdk/graphql/test_query.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing
Files:
tests/fixtures/unit/test_graphql_plugin/python02.pytests/fixtures/unit/test_graphql_plugin/python01.pytests/unit/sdk/graphql/conftest.pytests/unit/sdk/graphql/test_renderer.pytests/fixtures/unit/test_graphql_plugin/python01_after_annotation.pytests/unit/sdk/graphql/test_plugin.pytests/fixtures/unit/test_graphql_plugin/python02_after_annotation.pytests/unit/sdk/graphql/test_query.py
tests/unit/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Place and write unit tests under tests/unit/ (isolated component tests)
Files:
tests/unit/sdk/graphql/conftest.pytests/unit/sdk/graphql/test_renderer.pytests/unit/sdk/graphql/test_plugin.pytests/unit/sdk/graphql/test_query.py
infrahub_sdk/ctl/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
infrahub_sdk/ctl/**/*.py: Build CLI commands with Typer
Organize and keep all CLI commands within infrahub_sdk/ctl/
Files:
infrahub_sdk/ctl/graphql.pyinfrahub_sdk/ctl/cli_commands.py
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CLAUDE.md)
docs/**/*.{md,mdx}: Follow the Diataxis framework and classify docs as Tutorials, How-to guides, Explanation, or Reference
Structure How-to guides with required frontmatter and sections: introduction, prerequisites, step-by-step steps, validation, related resources
Structure Topics (Explanation) docs with introduction, concepts & definitions, background & context, architecture & design, connections, further reading
Use a professional, concise, informative tone with consistent structure across documents
Use proper language tags on all code blocks
Include both async and sync examples where applicable using the Tabs component
Add validation steps to guides to confirm success and expected outputs
Use callouts for warnings, tips, and important notes
Define new terms on first use and use domain-relevant terminology consistent with Infrahub’s model/UI
Conform to markdown style rules in .markdownlint.yaml and Vale styles in .vale/styles/
Files:
docs/docs/python-sdk/guides/python-typing.mdxdocs/docs/infrahubctl/infrahubctl-graphql.mdx
🧠 Learnings (1)
📚 Learning: 2025-08-24T13:35:12.998Z
Learnt from: CR
PR: opsmill/infrahub-sdk-python#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T13:35:12.998Z
Learning: Applies to infrahub_sdk/ctl/**/*.py : Build CLI commands with Typer
Applied to files:
infrahub_sdk/ctl/cli_commands.py
🧬 Code graph analysis (11)
tests/fixtures/unit/test_graphql_plugin/python02.py (1)
tests/fixtures/unit/test_graphql_plugin/python01.py (2)
CreateDevice(8-9)CreateDeviceInfraDeviceUpsert(12-14)
tests/fixtures/unit/test_graphql_plugin/python01.py (2)
tests/fixtures/unit/test_graphql_plugin/python02.py (2)
CreateDevice(6-7)CreateDeviceInfraDeviceUpsert(10-12)tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py (2)
CreateDevice(9-10)CreateDeviceInfraDeviceUpsert(13-15)
tests/unit/sdk/graphql/conftest.py (1)
infrahub_sdk/protocols_base.py (1)
Enum(111-112)
infrahub_sdk/graphql/__init__.py (2)
infrahub_sdk/graphql/query.py (2)
Mutation(46-77)Query(31-43)infrahub_sdk/graphql/renderers.py (3)
render_input_block(152-212)render_query_block(83-149)render_variables_to_string(69-80)
infrahub_sdk/graphql/query.py (1)
infrahub_sdk/graphql/renderers.py (3)
render_input_block(152-212)render_query_block(83-149)render_variables_to_string(69-80)
tests/unit/sdk/graphql/test_renderer.py (1)
infrahub_sdk/graphql/renderers.py (2)
render_input_block(152-212)render_query_block(83-149)
infrahub_sdk/ctl/graphql.py (4)
infrahub_sdk/async_typer.py (2)
AsyncTyper(11-31)command(29-31)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-21)remove_fragment_import(24-30)
tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (1)
tests/fixtures/unit/test_graphql_plugin/python02.py (2)
CreateDevice(6-7)CreateDeviceInfraDeviceUpsert(10-12)
tests/unit/sdk/graphql/test_plugin.py (1)
infrahub_sdk/graphql/plugin.py (4)
FutureAnnotationPlugin(12-28)generate_result_types_module(23-28)generate_result_types_module(56-62)generate_result_types_module(81-86)
tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py (3)
tests/fixtures/unit/test_graphql_plugin/python01.py (2)
CreateDevice(8-9)CreateDeviceInfraDeviceUpsert(12-14)tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (2)
CreateDevice(8-9)CreateDeviceInfraDeviceUpsert(12-14)tests/fixtures/unit/test_graphql_plugin/python02.py (2)
CreateDevice(6-7)CreateDeviceInfraDeviceUpsert(10-12)
tests/unit/sdk/graphql/test_query.py (1)
infrahub_sdk/graphql/query.py (2)
Mutation(46-77)Query(31-43)
⏰ 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). (7)
- GitHub Check: unit-tests (3.9)
- GitHub Check: unit-tests (3.13)
- GitHub Check: unit-tests (3.12)
- GitHub Check: unit-tests (3.11)
- GitHub Check: integration-tests-latest-infrahub
- GitHub Check: unit-tests (3.10)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (35)
pyproject.toml (2)
73-73: LGTM! ariadne-codegen properly added to extras.The dependency is correctly included in both
ctlandallextras, making it available for CLI GraphQL codegen functionality.Also applies to: 86-86
135-135: LGTM! Test fixture exclusions are appropriate.Excluding the generated test fixtures from Ruff and suppressing FA100 (future annotations) for these files makes sense, as they represent expected output from the codegen process and don't need to follow all project conventions.
Also applies to: 281-283
tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (4)
1-6: LGTM! Imports are correct for Pydantic v2 with forward references.The use of
from __future__ import annotationsenables forward reference strings without quotes, which is appropriate for the nested model structure.
8-9: LGTM! Field alias matches GraphQL convention.The alias
"InfraDeviceUpsert"correctly maps the Pythonic snake_case field name to the GraphQL PascalCase field name.
36-38: LGTM! model_rebuild() calls are necessary for forward references.In Pydantic v2, when using forward references (even with
from __future__ import annotations), callingmodel_rebuild()after all referenced models are defined ensures proper field resolution.
1-39: No changes needed for python01 fixtures The identical before/after fixtures correctly test idempotency of the FutureAnnotationPlugin.tests/fixtures/unit/test_graphql_plugin/python01.py (1)
1-39: LGTM with caveat! Model structure is well-formed.The Pydantic models correctly represent a nested GraphQL response structure with:
- Proper use of
from __future__ import annotations- Appropriate Field aliases for GraphQL naming conventions
- Correct
model_rebuild()calls for forward reference resolutionHowever, this file appears identical to
python01_after_annotation.py. See the verification request in that file's review regarding the intended relationship between these fixtures.changelog/+gql-command.added.md (1)
1-1: LGTM! Clear and concise changelog entry.The changelog entry accurately describes the new feature and follows the expected format.
infrahub_sdk/graphql/constants.py (1)
1-1: LGTM! Well-structured type mapping constant.The mapping correctly defines Python-to-GraphQL non-null type conversions for common scalar types. Centralizing this in a constants module improves maintainability.
infrahub_sdk/ctl/cli_commands.py (2)
29-29: LGTM! Import follows established pattern.The import of
graphql_appis consistent with other CLI subcommand imports in this file.
67-67: LGTM! CLI registration follows established pattern.The registration of the
graphqlcommand group follows the same pattern as other CLI subcommands in this file. As per coding guidelines, CLI commands are properly organized using Typer ininfrahub_sdk/ctl/.Based on learnings
docs/docs/python-sdk/guides/python-typing.mdx (1)
105-128: LGTM! Clear introduction and rationale for Pydantic model generation.The section provides a strong rationale for generating Pydantic models from GraphQL queries, covering type safety, IDE support, documentation, and validation. The command examples are clear and include both current directory and specific file usage patterns.
docs/docs/infrahubctl/infrahubctl-graphql.mdx (2)
1-21: LGTM! Clear command group overview.The main command group documentation follows the standard CLI reference format with proper usage block, options, and subcommand list.
22-36: LGTM! Clear export-schema documentation.The
export-schemacommand documentation is complete and follows the standard reference format with usage, options, and defaults clearly specified.tests/unit/sdk/graphql/test_plugin.py (3)
14-36: LGTM! Fixture setup is clean and properly typed.The fixtures correctly load test resources using
get_fixtures_dir()and appropriate encoding. Return type annotations are present.
38-48: LGTM! Test correctly verifies idempotency of the plugin.The test appropriately checks that the plugin doesn't duplicate the
__future__import when already present. Theoperation_definition=Noneargument aligns with the plugin's signature (the parameter is intentionally unused in the implementation).
51-62: LGTM! Test correctly verifies annotation insertion.The test appropriately validates that the plugin inserts the
__future__import when it's missing. Together with the first test, this provides good coverage of both plugin branches.infrahub_sdk/graphql/__init__.py (1)
1-12: LGTM! Clean public API organization.The module properly exposes the GraphQL utilities through explicit imports and
__all__declaration, making the public interface clear.tests/unit/sdk/graphql/conftest.py (1)
16-113: LGTM! Well-structured test fixtures.The fixtures provide comprehensive test data covering various GraphQL scenarios (no filters, aliases, fragments, different filter types). The naming is clear and the data structures properly represent the different test cases.
infrahub_sdk/graphql/renderers.py (5)
9-9: LGTM! Good separation of concerns.Moving
VARIABLE_TYPE_MAPPINGto a constants module improves code organization.
12-66: LGTM! Comprehensive value conversion with excellent documentation.The
convert_to_graphql_as_stringfunction properly handles various Python types (None, str, bool, Enum, list, BaseModel) and converts them to GraphQL string representations. The docstring provides clear examples and the implementation correctly usesjson.dumps()for string escaping.
69-80: LGTM! Clean variable rendering implementation.The function properly maps Python types to GraphQL variable type strings using the imported
VARIABLE_TYPE_MAPPING.
83-149: LGTM! Well-documented query block renderer.The
render_query_blockfunction correctly handles:
- Special GraphQL keys (
@filters,@alias)- Nested structures with proper indentation
- Recursive rendering for complex queries
The comprehensive docstring with examples makes the function's behavior clear.
152-212: LGTM! Robust input block renderer.The
render_input_blockfunction properly handles:
- Nested dictionaries with recursive rendering
- Lists with mixed item types (dicts and primitives)
- Proper indentation at all nesting levels
The docstring clearly documents the differences from
render_query_block(no special keys like@filtersor@alias).tests/unit/sdk/graphql/test_renderer.py (1)
104-245: LGTM! Comprehensive test coverage.The test functions thoroughly validate:
- Query block rendering with and without filters/aliases/fragments
- Input block rendering with nested structures and lists
- Different indentation levels (offset=2, indentation=2)
- Line-by-line assertion of expected output
The tests provide excellent coverage of the rendering functions.
infrahub_sdk/graphql/query.py (3)
8-28: LGTM! Clean base class design.
BaseGraphQLQueryprovides a solid foundation with:
- Type-safe attributes (
query_type,indentation)- Flexible constructor accepting query, variables, and name
- Reusable
render_first_line()method that properly formats the query opening with variables
31-43: LGTM! Clean Query class implementation.The
Queryclass properly:
- Sets
query_type = "query"- Delegates rendering to
render_query_blockwith appropriate parameters- Supports optional
convert_enumparameter- Returns properly formatted GraphQL query string
46-77: LGTM! Well-structured Mutation class.The
Mutationclass correctly:
- Extends
BaseGraphQLQuerywith mutation-specific attributes (mutation,input_data)- Renders both input block (mutation arguments) and query block (return fields)
- Uses proper indentation and formatting
- Supports
convert_enumparameter throughoutinfrahub_sdk/graphql/plugin.py (2)
31-62: LGTM! Type hint normalization is correctly implemented.The recursive traversal of
Subscriptnodes correctly handles nestedListannotations (e.g.,List[List[X]]→list[list[X]]) and applies the transformation to all annotated assignments within class definitions. The integration withFutureAnnotationPlugin.insert_future_annotationensuresfrom __future__ import annotationsis present before normalizing type hints.
65-86: LGTM! BaseModel import replacement is correctly implemented.The plugin correctly locates the
from base_model import BaseModelimport generated by ariadne-codegen and replaces it withfrom pydantic import BaseModel. The error handling for missing imports is appropriate, and the AST transformation is accurate.tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py (1)
1-20: LGTM! Fixture correctly demonstrates Pydantic v2 forward reference handling.This test fixture appropriately uses
from __future__ import annotationswith string-quoted forward references andmodel_rebuild()calls to verify the FutureAnnotationPlugin behavior. The pattern aligns with Pydantic v2 best practices for resolving forward references.tests/fixtures/unit/test_graphql_plugin/python02.py (1)
1-17: LGTM! Fixture correctly demonstrates standard forward reference pattern.This test fixture uses standard string-quoted forward references without
from __future__ import annotations, with appropriatemodel_rebuild()calls. This provides the baseline for testing FutureAnnotationPlugin transformations.tests/unit/sdk/graphql/test_query.py (2)
3-3: LGTM! Import path updated to reflect module reorganization.The updated import from
infrahub_sdk.graphql.queryaligns with the PR's GraphQL module refactoring. The Query and Mutation classes maintain the same API, ensuring test compatibility.Based on coding guidelines.
16-291: LGTM! Comprehensive test coverage for Query and Mutation rendering.The test suite thoroughly covers various scenarios: no variables, empty filters, filters with variables, enum handling, and mutation rendering. Tests follow pytest best practices with clear naming and explicit assertions.
Based on coding guidelines.
infrahub_sdk/graphql/utils.py (1)
4-10: LGTM! Clean sentinel pattern for class lookup.The function correctly identifies the first class definition and returns a clear sentinel value (-1) when none exists. The implementation is straightforward and well-documented.
| class MyStrEnum(str, Enum): | ||
| VALUE1 = "value1" | ||
| VALUE2 = "value2" | ||
|
|
||
|
|
||
| class MyIntEnum(int, Enum): | ||
| VALUE1 = 12 | ||
| VALUE2 = 24 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove duplicate enum definitions.
These enum classes are already defined in tests/unit/sdk/graphql/conftest.py (lines 6-13). Remove the duplicates here and import from conftest if needed.
-class MyStrEnum(str, Enum):
- VALUE1 = "value1"
- VALUE2 = "value2"
-
-
-class MyIntEnum(int, Enum):
- VALUE1 = 12
- VALUE2 = 24Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In tests/unit/sdk/graphql/test_renderer.py around lines 8 to 15, duplicate enum
classes MyStrEnum and MyIntEnum are defined but already exist in
tests/unit/sdk/graphql/conftest.py (lines 6-13); remove these class definitions
from this file and instead import the enums from conftest (e.g., add from
.conftest import MyStrEnum, MyIntEnum at top) or rely on pytest
fixtures/namespace as appropriate so tests use the single shared definitions.
| @pytest.fixture | ||
| def query_data_no_filter(): | ||
| data = { | ||
| "device": { | ||
| "name": {"value": None}, | ||
| "description": {"value": None}, | ||
| "interfaces": {"name": {"value": None}}, | ||
| } | ||
| } | ||
|
|
||
| return data | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def query_data_alias(): | ||
| data = { | ||
| "device": { | ||
| "name": {"@alias": "new_name", "value": None}, | ||
| "description": {"value": {"@alias": "myvalue"}}, | ||
| "interfaces": {"@alias": "myinterfaces", "name": {"value": None}}, | ||
| } | ||
| } | ||
|
|
||
| return data | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def query_data_fragment(): | ||
| data = { | ||
| "device": { | ||
| "name": {"value": None}, | ||
| "...on Builtin": { | ||
| "description": {"value": None}, | ||
| "interfaces": {"name": {"value": None}}, | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| return data | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def query_data_empty_filter(): | ||
| data = { | ||
| "device": { | ||
| "@filters": {}, | ||
| "name": {"value": None}, | ||
| "description": {"value": None}, | ||
| "interfaces": {"name": {"value": None}}, | ||
| } | ||
| } | ||
|
|
||
| return data | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def query_data_filters_01(): | ||
| data = { | ||
| "device": { | ||
| "@filters": {"name__value": "$name"}, | ||
| "name": {"value": None}, | ||
| "description": {"value": None}, | ||
| "interfaces": { | ||
| "@filters": {"enabled__value": "$enabled"}, | ||
| "name": {"value": None}, | ||
| }, | ||
| } | ||
| } | ||
| return data | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def query_data_filters_02(): | ||
| data = { | ||
| "device": { | ||
| "@filters": {"name__value": "myname", "integer__value": 44, "enumstr__value": MyStrEnum.VALUE2}, | ||
| "name": {"value": None}, | ||
| "interfaces": { | ||
| "@filters": {"enabled__value": True, "enumint__value": MyIntEnum.VALUE1}, | ||
| "name": {"value": None}, | ||
| }, | ||
| } | ||
| } | ||
| return data | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove duplicate fixture definitions.
All these fixtures (query_data_no_filter, query_data_alias, query_data_fragment, query_data_empty_filter, query_data_filters_01, query_data_filters_02, input_data_01) are already defined in tests/unit/sdk/graphql/conftest.py (lines 16-113). Pytest automatically discovers and makes conftest fixtures available to test modules, so these duplicates should be removed.
-@pytest.fixture
-def query_data_no_filter():
- data = {
- "device": {
- "name": {"value": None},
- "description": {"value": None},
- "interfaces": {"name": {"value": None}},
- }
- }
-
- return data
-
-
-@pytest.fixture
-def query_data_alias():
- data = {
- "device": {
- "name": {"@alias": "new_name", "value": None},
- "description": {"value": {"@alias": "myvalue"}},
- "interfaces": {"@alias": "myinterfaces", "name": {"value": None}},
- }
- }
-
- return data
-
-
-@pytest.fixture
-def query_data_fragment():
- data = {
- "device": {
- "name": {"value": None},
- "...on Builtin": {
- "description": {"value": None},
- "interfaces": {"name": {"value": None}},
- },
- }
- }
-
- return data
-
-
-@pytest.fixture
-def query_data_empty_filter():
- data = {
- "device": {
- "@filters": {},
- "name": {"value": None},
- "description": {"value": None},
- "interfaces": {"name": {"value": None}},
- }
- }
-
- return data
-
-
-@pytest.fixture
-def query_data_filters_01():
- data = {
- "device": {
- "@filters": {"name__value": "$name"},
- "name": {"value": None},
- "description": {"value": None},
- "interfaces": {
- "@filters": {"enabled__value": "$enabled"},
- "name": {"value": None},
- },
- }
- }
- return data
-
-
-@pytest.fixture
-def query_data_filters_02():
- data = {
- "device": {
- "@filters": {"name__value": "myname", "integer__value": 44, "enumstr__value": MyStrEnum.VALUE2},
- "name": {"value": None},
- "interfaces": {
- "@filters": {"enabled__value": True, "enumint__value": MyIntEnum.VALUE1},
- "name": {"value": None},
- },
- }
- }
- return data📝 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.
| @pytest.fixture | |
| def query_data_no_filter(): | |
| data = { | |
| "device": { | |
| "name": {"value": None}, | |
| "description": {"value": None}, | |
| "interfaces": {"name": {"value": None}}, | |
| } | |
| } | |
| return data | |
| @pytest.fixture | |
| def query_data_alias(): | |
| data = { | |
| "device": { | |
| "name": {"@alias": "new_name", "value": None}, | |
| "description": {"value": {"@alias": "myvalue"}}, | |
| "interfaces": {"@alias": "myinterfaces", "name": {"value": None}}, | |
| } | |
| } | |
| return data | |
| @pytest.fixture | |
| def query_data_fragment(): | |
| data = { | |
| "device": { | |
| "name": {"value": None}, | |
| "...on Builtin": { | |
| "description": {"value": None}, | |
| "interfaces": {"name": {"value": None}}, | |
| }, | |
| } | |
| } | |
| return data | |
| @pytest.fixture | |
| def query_data_empty_filter(): | |
| data = { | |
| "device": { | |
| "@filters": {}, | |
| "name": {"value": None}, | |
| "description": {"value": None}, | |
| "interfaces": {"name": {"value": None}}, | |
| } | |
| } | |
| return data | |
| @pytest.fixture | |
| def query_data_filters_01(): | |
| data = { | |
| "device": { | |
| "@filters": {"name__value": "$name"}, | |
| "name": {"value": None}, | |
| "description": {"value": None}, | |
| "interfaces": { | |
| "@filters": {"enabled__value": "$enabled"}, | |
| "name": {"value": None}, | |
| }, | |
| } | |
| } | |
| return data | |
| @pytest.fixture | |
| def query_data_filters_02(): | |
| data = { | |
| "device": { | |
| "@filters": {"name__value": "myname", "integer__value": 44, "enumstr__value": MyStrEnum.VALUE2}, | |
| "name": {"value": None}, | |
| "interfaces": { | |
| "@filters": {"enabled__value": True, "enumint__value": MyIntEnum.VALUE1}, | |
| "name": {"value": None}, | |
| }, | |
| } | |
| } | |
| return data |
🤖 Prompt for AI Agents
In tests/unit/sdk/graphql/test_renderer.py around lines 18 to 102, there are
duplicate pytest fixture definitions (query_data_no_filter, query_data_alias,
query_data_fragment, query_data_empty_filter, query_data_filters_01,
query_data_filters_02, and input_data_01) that are already provided by
tests/unit/sdk/graphql/conftest.py; remove these fixture definitions from this
test file so the tests use the conftest-provided fixtures instead, ensuring no
local redefinitions remain and that any references in this file rely on the
conftest fixtures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
infrahub_sdk/ctl/graphql.py (2)
98-110: HTTP response validation is still missing.Line 107-108 still writes
response.textdirectly to the schema file without validating the HTTP status code. If the server returns an error response (e.g., 404, 500), the error body will be written to the schema file, corrupting it and causing downstream failures ingenerate_return_types.Apply this diff to validate the response:
client = initialize_client() response = await client._get(url=f"{client.address}/schema.graphql") + response.raise_for_status() destination.write_text(response.text) console.print(f"[green]Schema exported to {destination}")Note: Line 107 uses the private method
client._get(). If a public method becomes available, prefer that to avoid coupling to internal implementation details.
141-173: Inconsistent logging and partial generation issues remain unresolved.Two issues persist in this loop:
Inconsistent logging: Line 146 still uses
print()while lines 167 and 173 useconsole.print(), creating inconsistent output formatting.Partial generation after parsing error: Lines 163-173 catch
ParsingErrorfromadd_operationand continue processing. Lines 168-173 proceed to generate fragments and write files even when operations failed to parse, resulting in incomplete or incorrect generated types. Success messages are still printed despite the error.Apply this diff to fix both issues:
except ValueError as exc: - print(f"Error generating result types for {gql_file}: {exc}") + console.print(f"[red]Error generating result types for {gql_file}: {exc}") continue queries = filter_operations_definitions(definitions) fragments = filter_fragments_definitions(definitions) package_generator = get_package_generator( schema=graphql_schema, fragments=fragments, settings=ClientSettings( schema_path=str(schema), target_package_name=directory.name, queries_path=str(directory), include_comments=CommentsStrategy.NONE, ), plugin_manager=plugin_manager, ) + parsing_failed = False try: for query_operation in queries: package_generator.add_operation(query_operation) except ParsingError as exc: console.print(f"[red]Unable to process {gql_file.name}: {exc}") + parsing_failed = True + + if parsing_failed: + continue + module_fragment = package_generator.fragments_generator.generate() generate_result_types(directory=directory, package=package_generator, fragment=module_fragment) for file_name in package_generator._result_types_files.keys(): console.print(f"[green]Generated {file_name} in {directory}")
🧹 Nitpick comments (1)
infrahub_sdk/ctl/graphql.py (1)
77-89: Consider using public APIs instead of private attributes.This function accesses multiple private attributes and methods from
PackageGenerator(_result_types_files,_add_comments_to_code,_generated_files). While this may be necessary given the currentariadne-codegenAPI, it creates tight coupling and risks breakage if the library changes its internals.If public APIs are available or can be requested from the library maintainers, prefer those. Otherwise, document this dependency and consider wrapping this logic in a way that isolates the private-attribute access.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
docs/docs/infrahubctl/infrahubctl-graphql.mdx(1 hunks)infrahub_sdk/ctl/graphql.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/docs/infrahubctl/infrahubctl-graphql.mdx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method
Files:
infrahub_sdk/ctl/graphql.py
infrahub_sdk/ctl/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
infrahub_sdk/ctl/**/*.py: Build CLI commands with Typer
Organize and keep all CLI commands within infrahub_sdk/ctl/
Files:
infrahub_sdk/ctl/graphql.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: opsmill/infrahub-sdk-python#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T13:35:12.998Z
Learning: Applies to infrahub_sdk/ctl/**/*.py : Organize and keep all CLI commands within infrahub_sdk/ctl/
🧬 Code graph analysis (1)
infrahub_sdk/ctl/graphql.py (4)
infrahub_sdk/async_typer.py (2)
AsyncTyper(11-31)command(29-31)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-21)remove_fragment_import(24-30)
⏰ 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). (8)
- GitHub Check: unit-tests (3.12)
- GitHub Check: unit-tests (3.13)
- GitHub Check: unit-tests (3.11)
- GitHub Check: unit-tests (3.9)
- GitHub Check: unit-tests (3.10)
- GitHub Check: integration-tests-latest-infrahub
- GitHub Check: documentation
- GitHub Check: Cloudflare Pages
🔇 Additional comments (4)
infrahub_sdk/ctl/graphql.py (4)
1-36: LGTM!The imports are well-organized and all dependencies appear necessary for the CLI functionality. The plugin configuration in
ARIADNE_PLUGINSis appropriate for controlling the code generation pipeline.
38-54: LGTM!The function correctly handles both single files and directories, with appropriate validation and error handling. The recursive glob pattern ensures all
.gqlfiles are discovered.
57-74: LGTM!The function properly validates the query file and performs schema validation. Excluding
NoUnusedFragmentsRuleis appropriate when fragments may be shared across files. Error messages are clear and helpful.
91-96: LGTM!Standard Typer callback pattern for the CLI group.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
infrahub_sdk/ctl/graphql.py (2)
107-110: Unaddressed: Validate HTTP response before writing schema file.This is a duplicate of a previous review comment that has not been addressed. Lines 107-109 still fetch the schema and write
response.textdirectly to the file without checking the HTTP status code. If the server returns an error (e.g., 404, 500), the error response body will be written to the schema file, resulting in an invalid schema that will cause downstream failures ingenerate_return_types.Apply this diff to validate the response and ensure the parent directory exists:
client = initialize_client() response = await client._get(url=f"{client.address}/schema.graphql") + if response.status_code != 200: + raise ValueError(f"Failed to fetch schema: HTTP {response.status_code} - {response.text}") + destination.parent.mkdir(parents=True, exist_ok=True) destination.write_text(response.text) console.print(f"[green]Schema exported to {destination}")Note on private method access: Line 107 calls
client._get(), which is a private method. Consider documenting this dependency or requesting a public method from theInfrahubClientmaintainers.
144-174: Unaddressed: Inconsistent logging and partial generation after parsing error.This is a duplicate of a previous review comment that has not been addressed. Two issues remain:
Inconsistent logging (line 147): Uses
print()instead ofconsole.print(), creating inconsistent output formatting compared to lines 168 and 174.Partial generation after parsing error (lines 167-169): The code catches
ParsingErrorfromadd_operationand logs it, but then continues to lines 169-171 to generate fragments and write files. If some operations failed to parse, the generated types may be incomplete or incorrect, yet success messages are still printed (line 174). This creates a confusing user experience where errors are shown but the command reports success.Apply this diff to fix both issues:
except ValueError as exc: - print(f"Error generating result types for {gql_file}: {exc}") + console.print(f"[red]Error generating result types for {gql_file}: {exc}") continue queries = filter_operations_definitions(definitions) fragments = filter_fragments_definitions(definitions) package_generator = get_package_generator( schema=graphql_schema, fragments=fragments, settings=ClientSettings( schema_path=str(schema), target_package_name=directory.name, queries_path=str(directory), include_comments=CommentsStrategy.NONE, ), plugin_manager=plugin_manager, ) + parsing_failed = False try: for query_operation in queries: package_generator.add_operation(query_operation) except ParsingError as exc: console.print(f"[red]Unable to process {gql_file.name}: {exc}") + parsing_failed = True + + if parsing_failed: + continue + module_fragment = package_generator.fragments_generator.generate() generate_result_types(directory=directory, package=package_generator, fragment=module_fragment) for file_name in package_generator._result_types_files.keys(): console.print(f"[green]Generated {file_name} in {directory}")
🧹 Nitpick comments (2)
infrahub_sdk/ctl/graphql.py (2)
52-53: Simplify the file check condition.The condition
not query_path.is_dir() and query_path.is_file()is redundant. If a path is a file, it's inherently not a directory.Apply this diff:
- if not query_path.is_dir() and query_path.is_file(): + if query_path.is_file(): return [query_path]
78-90: Document ariadne-codegen private API usageThis function relies on internal
PackageGeneratorattributes (_result_types_files,_generated_files) and method (_add_comments_to_code). Add a comment explaining this dependency and open an issue/PR against ariadne-codegen to expose public APIs for:
- Iterating result-type files
- Inserting file comments (use
include_commentsconfig orget_file_commenthook)- Tracking generated files
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
infrahub_sdk/ctl/graphql.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method
Files:
infrahub_sdk/ctl/graphql.py
infrahub_sdk/ctl/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
infrahub_sdk/ctl/**/*.py: Build CLI commands with Typer
Organize and keep all CLI commands within infrahub_sdk/ctl/
Files:
infrahub_sdk/ctl/graphql.py
🧬 Code graph analysis (1)
infrahub_sdk/ctl/graphql.py (5)
infrahub_sdk/async_typer.py (2)
AsyncTyper(11-31)command(29-31)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-21)remove_fragment_import(24-30)tests/unit/sdk/graphql/test_plugin.py (1)
graphql_schema(15-17)
⏰ 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). (8)
- GitHub Check: unit-tests (3.11)
- GitHub Check: unit-tests (3.13)
- GitHub Check: unit-tests (3.12)
- GitHub Check: unit-tests (3.10)
- GitHub Check: unit-tests (3.9)
- GitHub Check: integration-tests-latest-infrahub
- GitHub Check: documentation
- GitHub Check: Cloudflare Pages
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments, but great addition!
|
|
||
| Generated Pydantic models from GraphQL queries offer several important benefits: | ||
|
|
||
| - **Type Safety**: Catch type errors at development time instead of runtime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested change:
- Catch type errors at development time instead of runtime
+ Catch type errors during development time instead of at runtime| ) | ||
| if validation_errors: | ||
| raise ValueError("\n\n".join(error.message for error in validation_errors)) | ||
| return queries_ast.definitions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While we don't support it yet within the SDK this would be problematic with fragments spread out in multiple files. Not an issue for this PR, just highlighting if we want to do something different with regards to the API of the command because of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree we'll need to revisit this part of the code to improve the support for Fragments
infrahub_sdk/ctl/graphql.py
Outdated
| """Export the GraphQL schema to a file.""" | ||
|
|
||
| client = initialize_client() | ||
| response = await client._get(url=f"{client.address}/schema.graphql") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest that we move this and avoid calling client._get from the CTL. Instead I's suggest that we add some client.schema.get_graphql_schema()
Do we need a branch parameter here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, done
| for query_operation in queries: | ||
| package_generator.add_operation(query_operation) | ||
| except ParsingError as exc: | ||
| console.print(f"[red]Unable to process {gql_file.name}: {exc}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we ignore this error and the one above aside from displaying output. Do we want the command to fail here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, update the logic to ignore failing files and continue
| class Mutation(BaseGraphQLQuery): | ||
| query_type = "mutation" | ||
|
|
||
| def __init__(self, *args: Any, mutation: str, input_data: dict, **kwargs: Any): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Realise this is just moved code but I think we should remove*args: Any and also **kwargs: Any) to make this class easier to use..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's best to do that in a separate PR to avoid adding unexpected issues
c2cf606 to
dabcbef
Compare
There was a problem hiding this 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
♻️ Duplicate comments (2)
infrahub_sdk/schema/__init__.py (1)
719-736: Same branch parameter issue as async variant.This sync version has the identical issue: the
branchparameter is set but never used in the URL construction. Apply the same fix as suggested for the async version at lines 477-494.infrahub_sdk/graphql/query.py (1)
49-52: Simplify constructor signature.The use of
*argsand**kwargsobscures the required parameters and makes the API harder to discover and use. Consider making the signature explicit to improve clarity and IDE support.Apply this diff to make the parameters explicit:
- def __init__(self, *args: Any, mutation: str, input_data: dict, **kwargs: Any): + def __init__(self, mutation: str, input_data: dict, query: dict, variables: dict | None = None, name: str | None = None): self.input_data = input_data self.mutation = mutation - super().__init__(*args, **kwargs) + super().__init__(query=query, variables=variables, name=name)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
poetry.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
.vale/styles/Infrahub/branded-terms-case-swap.yml(0 hunks)changelog/+gql-command.added.md(1 hunks)docs/docs/infrahubctl/infrahubctl-graphql.mdx(1 hunks)docs/docs/python-sdk/guides/python-typing.mdx(1 hunks)infrahub_sdk/ctl/cli_commands.py(2 hunks)infrahub_sdk/ctl/graphql.py(1 hunks)infrahub_sdk/graphql/__init__.py(1 hunks)infrahub_sdk/graphql/constants.py(1 hunks)infrahub_sdk/graphql/plugin.py(1 hunks)infrahub_sdk/graphql/query.py(1 hunks)infrahub_sdk/graphql/renderers.py(3 hunks)infrahub_sdk/graphql/utils.py(1 hunks)infrahub_sdk/schema/__init__.py(2 hunks)pyproject.toml(5 hunks)tests/fixtures/unit/test_graphql_plugin/python01.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python02.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py(1 hunks)tests/unit/sdk/graphql/conftest.py(1 hunks)tests/unit/sdk/graphql/test_plugin.py(1 hunks)tests/unit/sdk/graphql/test_query.py(1 hunks)tests/unit/sdk/graphql/test_renderer.py(1 hunks)
💤 Files with no reviewable changes (1)
- .vale/styles/Infrahub/branded-terms-case-swap.yml
✅ Files skipped from review due to trivial changes (1)
- docs/docs/infrahubctl/infrahubctl-graphql.mdx
🚧 Files skipped from review as they are similar to previous changes (11)
- tests/fixtures/unit/test_graphql_plugin/python02.py
- infrahub_sdk/graphql/constants.py
- tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py
- infrahub_sdk/graphql/renderers.py
- infrahub_sdk/graphql/init.py
- infrahub_sdk/ctl/graphql.py
- tests/unit/sdk/graphql/conftest.py
- infrahub_sdk/graphql/utils.py
- pyproject.toml
- tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py
- changelog/+gql-command.added.md
🧰 Additional context used
📓 Path-based instructions (5)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method
Files:
infrahub_sdk/ctl/cli_commands.pytests/unit/sdk/graphql/test_plugin.pytests/fixtures/unit/test_graphql_plugin/python01.pyinfrahub_sdk/graphql/query.pyinfrahub_sdk/graphql/plugin.pyinfrahub_sdk/schema/__init__.pytests/unit/sdk/graphql/test_query.pytests/unit/sdk/graphql/test_renderer.py
infrahub_sdk/ctl/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
infrahub_sdk/ctl/**/*.py: Build CLI commands with Typer
Organize and keep all CLI commands within infrahub_sdk/ctl/
Files:
infrahub_sdk/ctl/cli_commands.py
tests/unit/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Place and write unit tests under tests/unit/ (isolated component tests)
Files:
tests/unit/sdk/graphql/test_plugin.pytests/unit/sdk/graphql/test_query.pytests/unit/sdk/graphql/test_renderer.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing
Files:
tests/unit/sdk/graphql/test_plugin.pytests/fixtures/unit/test_graphql_plugin/python01.pytests/unit/sdk/graphql/test_query.pytests/unit/sdk/graphql/test_renderer.py
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CLAUDE.md)
docs/**/*.{md,mdx}: Follow the Diataxis framework and classify docs as Tutorials, How-to guides, Explanation, or Reference
Structure How-to guides with required frontmatter and sections: introduction, prerequisites, step-by-step steps, validation, related resources
Structure Topics (Explanation) docs with introduction, concepts & definitions, background & context, architecture & design, connections, further reading
Use a professional, concise, informative tone with consistent structure across documents
Use proper language tags on all code blocks
Include both async and sync examples where applicable using the Tabs component
Add validation steps to guides to confirm success and expected outputs
Use callouts for warnings, tips, and important notes
Define new terms on first use and use domain-relevant terminology consistent with Infrahub’s model/UI
Conform to markdown style rules in .markdownlint.yaml and Vale styles in .vale/styles/
Files:
docs/docs/python-sdk/guides/python-typing.mdx
🧠 Learnings (1)
📚 Learning: 2025-08-24T13:35:12.998Z
Learnt from: CR
PR: opsmill/infrahub-sdk-python#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T13:35:12.998Z
Learning: Applies to infrahub_sdk/ctl/**/*.py : Build CLI commands with Typer
Applied to files:
infrahub_sdk/ctl/cli_commands.py
🧬 Code graph analysis (6)
tests/unit/sdk/graphql/test_plugin.py (1)
infrahub_sdk/graphql/plugin.py (4)
FutureAnnotationPlugin(12-29)generate_result_types_module(24-29)generate_result_types_module(57-63)generate_result_types_module(80-85)
tests/fixtures/unit/test_graphql_plugin/python01.py (3)
tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (6)
CreateDevice(8-9)CreateDeviceInfraDeviceUpsert(12-14)CreateDeviceInfraDeviceUpsertObject(17-21)CreateDeviceInfraDeviceUpsertObjectName(24-25)CreateDeviceInfraDeviceUpsertObjectDescription(28-29)CreateDeviceInfraDeviceUpsertObjectStatus(32-33)tests/fixtures/unit/test_graphql_plugin/python02.py (2)
CreateDevice(6-7)CreateDeviceInfraDeviceUpsert(10-12)tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py (2)
CreateDevice(9-10)CreateDeviceInfraDeviceUpsert(13-15)
infrahub_sdk/graphql/query.py (1)
infrahub_sdk/graphql/renderers.py (3)
render_input_block(152-212)render_query_block(83-149)render_variables_to_string(69-80)
infrahub_sdk/schema/__init__.py (2)
tests/unit/sdk/conftest.py (1)
client(32-33)infrahub_sdk/client.py (2)
_get(1037-1055)_get(2848-2866)
tests/unit/sdk/graphql/test_query.py (1)
infrahub_sdk/graphql/query.py (2)
Mutation(46-77)Query(31-43)
tests/unit/sdk/graphql/test_renderer.py (2)
infrahub_sdk/graphql/renderers.py (2)
render_input_block(152-212)render_query_block(83-149)tests/unit/sdk/graphql/conftest.py (4)
query_data_no_filter(17-26)query_data_alias(30-39)query_data_fragment(43-54)input_data_01(103-113)
⏰ 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). (7)
- GitHub Check: unit-tests (3.13)
- GitHub Check: unit-tests (3.12)
- GitHub Check: integration-tests-latest-infrahub
- GitHub Check: unit-tests (3.11)
- GitHub Check: unit-tests (3.10)
- GitHub Check: unit-tests (3.9)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (16)
infrahub_sdk/ctl/cli_commands.py (1)
29-29: LGTM! GraphQL CLI group integration follows established patterns.The import and registration of the new GraphQL command group are correctly implemented and consistent with existing sub-app integrations (branch, schema, repository, etc.).
Also applies to: 67-67
docs/docs/python-sdk/guides/python-typing.mdx (2)
103-104: LGTM!The typo has been corrected from "procotols.py" to "protocols.py".
105-151: Well-structured documentation for the new feature.The new section on generating Pydantic models from GraphQL queries is clear, well-organized, and follows a logical progression from rationale to usage. The workflow steps provide practical guidance for users.
tests/fixtures/unit/test_graphql_plugin/python01.py (2)
1-34: LGTM!The Pydantic model structure is correct. The use of
from __future__ import annotationsallows forward references to work properly, and the models follow Pydantic v2 patterns with proper type hints and field aliases.
36-38: LGTM!The
model_rebuild()calls are necessary in Pydantic v2 to resolve forward references after all models are defined. The selection of models to rebuild is appropriate—focusing on those with forward reference dependencies.tests/unit/sdk/graphql/test_query.py (1)
3-3: LGTM!The import path update correctly reflects the module reorganization where
QueryandMutationclasses have been moved toinfrahub_sdk.graphql.query.tests/unit/sdk/graphql/test_renderer.py (2)
4-90: LGTM!The
render_query_blocktests are comprehensive, covering basic rendering, alias handling, and fragment blocks. The use of fixtures from conftest and line-by-line assertions provide good test coverage.
93-145: LGTM!The
render_input_blocktests thoroughly verify the rendering of nested structures, lists, and various data types with different indentation settings.tests/unit/sdk/graphql/test_plugin.py (3)
14-35: LGTM!The fixtures properly load test resources using the
get_fixtures_dir()utility and explicit UTF-8 encoding, ensuring consistent file reading across platforms.
38-48: LGTM!The test correctly verifies that the plugin is idempotent—it doesn't modify a module that already has the
__future__annotations import. The AST comparison approach is appropriate.
51-62: LGTM!The test properly verifies that the plugin inserts the
__future__annotations import when it's missing, comparing against an expected output fixture. This provides good coverage of the plugin's primary functionality.infrahub_sdk/graphql/query.py (2)
8-28: LGTM!The
BaseGraphQLQueryclass provides a clean foundation with clear responsibilities: storing query structure, handling variable serialization, and rendering the opening line.
31-43: LGTM!The
Queryclass is clean and focused, properly delegating block rendering to the renderers module while maintaining consistent formatting.infrahub_sdk/graphql/plugin.py (3)
12-29: LGTM!The
FutureAnnotationPlugincorrectly checks for existing__future__annotations import usingany()to safely iterate over all imported names, preventing potentialIndexError. The plugin is idempotent and properly inserts the import only when needed.
32-63: LGTM!The
StandardTypeHintPluginproperly converts legacyListtype hints to modernlist(PEP 585 style), with recursive handling for nested subscripts. The plugin correctly ensures the__future__annotations import is present first.
66-85: LGTM!The
PydanticBaseModelPlugincorrectly raisesValueErrordirectly infind_base_model_indexwhen the BaseModel import isn't found, as suggested in previous feedback. The error handling is appropriate for indicating misconfigured generated code.
b1d8a7b to
0ca3ee4
Compare
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/unit/sdk/graphql/test_query.py (1)
248-290: Avoid unused variables in mutation headerThe test declares $description and $number but they’re unused in the operation body. Most GraphQL validators error on unused variables (NoUnusedVariables). Consider limiting variables to those actually referenced to keep examples executable.
♻️ Duplicate comments (1)
infrahub_sdk/graphql/query.py (1)
49-53: Avoid*args/**kwargsin Mutation.init (ack duplicate)Explicit params improve readability and static checking. Consider
def __init__(self, query: dict, *, mutation: str, input_data: dict, variables: dict | None = None, name: str | None = None).
🧹 Nitpick comments (10)
pyproject.toml (4)
73-87: Extras wiring looks good; ensure dev/tests install ariadne-codegen if unit tests import it.If unit tests import ariadne-codegen (e.g., plugin/renderers tests), include it in tests extras or dev group to avoid CI misses.
Option A — add to tests extra:
# outside the selected lines [tool.poetry.extras] tests = ["Jinja2", "pytest", "pyyaml", "rich", "ariadne-codegen"]Option B — add to dev group:
# outside the selected lines [tool.poetry.group.dev.dependencies] ariadne-codegen = ">=0.15.3,<0.16"Also, update docs to install CLI with codegen:
- pip install "infrahub-sdk[ctl]"
135-135: Ruff config: duplicated controls for the same path; choose one.You both exclude the path and add per-file-ignores for it; the exclude makes the per-file-ignores ineffective. Prefer keeping lint coverage with targeted ignores.
Preferred fix (keep per-file-ignores, remove exclude):
[tool.ruff] exclude = [ ".git", ".tox", ".venv", "env", "_build", "build", "dist", "examples", - "tests/fixtures/unit/test_graphql_plugin", ]Alternative (keep exclude, drop per-file-ignores):
-"tests/fixtures/unit/test_graphql_plugin/*.py" = [ - "FA100", # Add `from __future__ import annotations` to simplify `typing.Optional` -]Also applies to: 281-283
49-49: Align graphql-core minimum with ariadne-codegen’s needs.If ariadne-codegen requires graphql-core ≥3.2.x, our current spec (>=3.1,<3.3) may resolve to an incompatible 3.1.x.
If confirmed, update (outside these lines):
-graphql-core = ">=3.1,<3.3" +graphql-core = ">=3.2,<3.3"
49-49: Pin ariadne-codegen to a compatible version range
ariadne-codegen 0.15.3 supports Python 3.13, Pydantic v2, and graphql-core 3.x—avoid brittle exact pins:-ariadne-codegen = { version = "0.15.3", optional = true } +ariadne-codegen = { version = ">=0.15.3,<0.16.0", optional = true }tests/unit/sdk/graphql/conftest.py (1)
6-13: Deduplicate enums across testsMyStrEnum/MyIntEnum are also defined in tests/unit/sdk/graphql/test_query.py. Prefer importing these from conftest to avoid divergence.
tests/unit/sdk/graphql/test_query.py (1)
16-86: Query rendering assertions look solidCovers no-vars and filter/vars formatting well; outputs match renderer semantics.
Consider adding tests for alias and inline fragment rendering using the provided fixtures to broaden coverage.
tests/unit/sdk/graphql/test_plugin.py (1)
38-62: Add coverage for other pluginsOnly FutureAnnotationPlugin is tested. Please add unit tests for:
- StandardTypeHintPlugin (e.g., List[...] → list[...], nested subscripts)
- PydanticBaseModelPlugin (replacing
from base_model import BaseModelwithfrom pydantic import BaseModel)infrahub_sdk/graphql/renderers.py (1)
191-209: Optional: make trailing commas configurable for listsrender_input_block always appends a trailing comma to list items. Some GraphQL servers/parsers may be stricter. Consider a flag to disable trailing commas while keeping current default.
Example change:
-def render_input_block(data: dict, offset: int = 4, indentation: int = 4, convert_enum: bool = False) -> list[str]: +def render_input_block( + data: dict, + offset: int = 4, + indentation: int = 4, + convert_enum: bool = False, + allow_trailing_commas: bool = True, +) -> list[str]: @@ - elif isinstance(value, list): + elif isinstance(value, list): lines.append(f"{offset_str}{key}: " + "[") - for item in value: + for idx, item in enumerate(value): + is_last = idx == len(value) - 1 if isinstance(item, dict): lines.append(f"{offset_str}{' ' * indentation}" + "{") @@ - lines.append(f"{offset_str}{' ' * indentation}" + "},") + lines.append(f"{offset_str}{' ' * indentation}" + ("}," if allow_trailing_commas or not is_last else "}")) else: - lines.append( - f"{offset_str}{' ' * indentation}{convert_to_graphql_as_string(value=item, convert_enum=convert_enum)}," - ) + val = convert_to_graphql_as_string(value=item, convert_enum=convert_enum) + lines.append(f"{offset_str}{' ' * indentation}{val}" + ("," if allow_trailing_commas or not is_last else ""))Please confirm your target GraphQL servers accept trailing commas; if not, the above toggle will help.
infrahub_sdk/graphql/plugin.py (2)
33-55: BroadenList[...]normalization to covertyping.List[...]Current check only handles
List[...]when used via direct import. Also handletyping.List[...](ast.Attribute).- if isinstance(subscript.value, ast.Name) and subscript.value.id == "List": - subscript.value.id = "list" + if isinstance(subscript.value, ast.Name) and subscript.value.id == "List": + subscript.value.id = "list" + elif isinstance(subscript.value, ast.Attribute) and subscript.value.attr == "List": + # Convert `typing.List[T]` -> `list[T]` + subscript.value = ast.Name(id="list")
66-85: Make BaseModel import replacement idempotent and non-fatalFailing when
from base_model import BaseModelisn’t present can abort generation unnecessarily. No-op when not found.@classmethod def replace_base_model_import(cls, module: ast.Module) -> ast.Module: - base_model_index = cls.find_base_model_index(module) - module.body[base_model_index] = ast.ImportFrom(module="pydantic", names=[ast.alias(name="BaseModel")], level=0) + try: + base_model_index = cls.find_base_model_index(module) + except ValueError: + return module + module.body[base_model_index] = ast.ImportFrom( + module="pydantic", names=[ast.alias(name="BaseModel")], level=0 + ) return module
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
poetry.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
.vale/styles/Infrahub/branded-terms-case-swap.yml(0 hunks)changelog/+gql-command.added.md(1 hunks)docs/docs/infrahubctl/infrahubctl-graphql.mdx(1 hunks)docs/docs/python-sdk/guides/python-typing.mdx(1 hunks)infrahub_sdk/ctl/cli_commands.py(2 hunks)infrahub_sdk/ctl/graphql.py(1 hunks)infrahub_sdk/graphql/__init__.py(1 hunks)infrahub_sdk/graphql/constants.py(1 hunks)infrahub_sdk/graphql/plugin.py(1 hunks)infrahub_sdk/graphql/query.py(1 hunks)infrahub_sdk/graphql/renderers.py(3 hunks)infrahub_sdk/graphql/utils.py(1 hunks)infrahub_sdk/schema/__init__.py(2 hunks)pyproject.toml(5 hunks)tests/fixtures/unit/test_graphql_plugin/python01.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python02.py(1 hunks)tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py(1 hunks)tests/unit/sdk/graphql/conftest.py(1 hunks)tests/unit/sdk/graphql/test_plugin.py(1 hunks)tests/unit/sdk/graphql/test_query.py(1 hunks)tests/unit/sdk/graphql/test_renderer.py(1 hunks)
💤 Files with no reviewable changes (1)
- .vale/styles/Infrahub/branded-terms-case-swap.yml
✅ Files skipped from review due to trivial changes (1)
- docs/docs/infrahubctl/infrahubctl-graphql.mdx
🚧 Files skipped from review as they are similar to previous changes (10)
- infrahub_sdk/graphql/constants.py
- infrahub_sdk/ctl/cli_commands.py
- infrahub_sdk/graphql/init.py
- infrahub_sdk/graphql/utils.py
- tests/fixtures/unit/test_graphql_plugin/python01.py
- tests/unit/sdk/graphql/test_renderer.py
- changelog/+gql-command.added.md
- tests/fixtures/unit/test_graphql_plugin/python02.py
- docs/docs/python-sdk/guides/python-typing.mdx
- infrahub_sdk/ctl/graphql.py
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method
Files:
tests/unit/sdk/graphql/test_plugin.pytests/unit/sdk/graphql/conftest.pyinfrahub_sdk/schema/__init__.pyinfrahub_sdk/graphql/renderers.pytests/fixtures/unit/test_graphql_plugin/python02_after_annotation.pyinfrahub_sdk/graphql/plugin.pytests/fixtures/unit/test_graphql_plugin/python01_after_annotation.pyinfrahub_sdk/graphql/query.pytests/unit/sdk/graphql/test_query.py
tests/unit/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Place and write unit tests under tests/unit/ (isolated component tests)
Files:
tests/unit/sdk/graphql/test_plugin.pytests/unit/sdk/graphql/conftest.pytests/unit/sdk/graphql/test_query.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Use the custom pytest plugin (infrahub_sdk.pytest_plugin) fixtures for clients, configuration, and Infrahub-specific testing
Files:
tests/unit/sdk/graphql/test_plugin.pytests/unit/sdk/graphql/conftest.pytests/fixtures/unit/test_graphql_plugin/python02_after_annotation.pytests/fixtures/unit/test_graphql_plugin/python01_after_annotation.pytests/unit/sdk/graphql/test_query.py
🧬 Code graph analysis (7)
tests/unit/sdk/graphql/test_plugin.py (1)
infrahub_sdk/graphql/plugin.py (4)
FutureAnnotationPlugin(12-29)generate_result_types_module(24-29)generate_result_types_module(57-63)generate_result_types_module(80-85)
tests/unit/sdk/graphql/conftest.py (2)
infrahub_sdk/protocols_base.py (1)
Enum(111-112)tests/unit/sdk/graphql/test_query.py (2)
MyStrEnum(6-8)MyIntEnum(11-13)
infrahub_sdk/schema/__init__.py (1)
infrahub_sdk/client.py (2)
_get(1037-1055)_get(2848-2866)
tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py (1)
tests/fixtures/unit/test_graphql_plugin/python02.py (2)
CreateDevice(6-7)CreateDeviceInfraDeviceUpsert(10-12)
tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (1)
tests/fixtures/unit/test_graphql_plugin/python01.py (6)
CreateDevice(8-9)CreateDeviceInfraDeviceUpsert(12-14)CreateDeviceInfraDeviceUpsertObject(17-21)CreateDeviceInfraDeviceUpsertObjectName(24-25)CreateDeviceInfraDeviceUpsertObjectDescription(28-29)CreateDeviceInfraDeviceUpsertObjectStatus(32-33)
infrahub_sdk/graphql/query.py (1)
infrahub_sdk/graphql/renderers.py (3)
render_input_block(152-212)render_query_block(83-149)render_variables_to_string(69-80)
tests/unit/sdk/graphql/test_query.py (1)
infrahub_sdk/graphql/query.py (2)
Mutation(46-77)Query(31-43)
⏰ 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). (6)
- GitHub Check: unit-tests (3.9)
- GitHub Check: unit-tests (3.10)
- GitHub Check: unit-tests (3.12)
- GitHub Check: unit-tests (3.13)
- GitHub Check: integration-tests-latest-infrahub
- GitHub Check: unit-tests (3.11)
🔇 Additional comments (4)
infrahub_sdk/schema/__init__.py (1)
477-494: LGTM! Async variant correctly implements branch-aware schema fetching.The async implementation properly includes the branch parameter in the URL (line 487) and validates the response. The previous issue flagged in past reviews has been successfully addressed.
tests/fixtures/unit/test_graphql_plugin/python01_after_annotation.py (1)
1-38: LGTM: correct forward-ref and model structureModels, aliases, and model_rebuild calls look correct for the fixture.
tests/fixtures/unit/test_graphql_plugin/python02_after_annotation.py (1)
1-19: LGTM: annotated variant matches expectationsForward annotations and rebuild calls are appropriate for the test.
infrahub_sdk/graphql/query.py (1)
17-29: First-line rendering is clear and minimalName and variables are composed correctly; spacing matches tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
infrahub_sdk/schema/__init__.py(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
When implementing Infrahub checks, subclass InfrahubCheck and override validate(data); do not implement or rely on a check() method
Files:
infrahub_sdk/schema/__init__.py
🧬 Code graph analysis (1)
infrahub_sdk/schema/__init__.py (2)
tests/unit/sdk/conftest.py (1)
client(32-33)infrahub_sdk/client.py (2)
_get(1037-1055)_get(2848-2866)
| async def get_graphql_schema(self, branch: str | None = None) -> str: | ||
| """Get the GraphQL schema as a string. | ||
| Args: | ||
| branch: The branch to get the schema for. Defaults to default_branch. | ||
| Returns: | ||
| The GraphQL schema as a string. | ||
| """ | ||
| branch = branch or self.client.default_branch | ||
| url = f"{self.client.address}/schema.graphql?branch={branch}" | ||
|
|
||
| response = await self.client._get(url=url) | ||
|
|
||
| if response.status_code != 200: | ||
| raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}") | ||
|
|
||
| return response.text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL-encode the branch parameter and align error handling with existing patterns.
The branch parameter is directly interpolated into the URL without encoding. Branch names containing special characters (spaces, ?, &, etc.) will produce malformed URLs and cause request failures.
Additionally, the error handling differs from the established pattern in _parse_schema_response (lines 197-213). When the server returns a 400 status (invalid branch), the code should raise BranchNotFoundError for consistency.
Apply this diff to fix both issues:
+from urllib.parse import urlencode
+
async def get_graphql_schema(self, branch: str | None = None) -> str:
"""Get the GraphQL schema as a string.
Args:
branch: The branch to get the schema for. Defaults to default_branch.
Returns:
The GraphQL schema as a string.
"""
branch = branch or self.client.default_branch
- url = f"{self.client.address}/schema.graphql?branch={branch}"
+ query_params = urlencode([("branch", branch)])
+ url = f"{self.client.address}/schema.graphql?{query_params}"
response = await self.client._get(url=url)
+ if response.status_code == 400:
+ raise BranchNotFoundError(
+ identifier=branch,
+ message=f"The requested branch was not found on the server [{branch}]"
+ )
+
if response.status_code != 200:
raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}")
return response.textNote: urlencode is already imported at line 10.
🤖 Prompt for AI Agents
In infrahub_sdk/schema/__init__.py around lines 477 to 494, the branch is
interpolated into the URL without URL-encoding and the error handling should
match the existing _parse_schema_response behavior; use urlencode to encode the
branch query parameter (e.g., build the query string with urlencode({"branch":
branch})) when constructing the url, and change the response error handling to
raise BranchNotFoundError when response.status_code == 400 and otherwise raise a
ValueError including status and response.text to keep parity with the
established pattern.
| def get_graphql_schema(self, branch: str | None = None) -> str: | ||
| """Get the GraphQL schema as a string. | ||
| Args: | ||
| branch: The branch to get the schema for. Defaults to default_branch. | ||
| Returns: | ||
| The GraphQL schema as a string. | ||
| """ | ||
| branch = branch or self.client.default_branch | ||
| url = f"{self.client.address}/schema.graphql?branch={branch}" | ||
|
|
||
| response = self.client._get(url=url) | ||
|
|
||
| if response.status_code != 200: | ||
| raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}") | ||
|
|
||
| return response.text |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL-encode the branch parameter and align error handling (sync variant).
Same issues as the async variant: the branch parameter needs URL encoding, and error handling should match the established pattern.
Apply this diff:
def get_graphql_schema(self, branch: str | None = None) -> str:
"""Get the GraphQL schema as a string.
Args:
branch: The branch to get the schema for. Defaults to default_branch.
Returns:
The GraphQL schema as a string.
"""
branch = branch or self.client.default_branch
- url = f"{self.client.address}/schema.graphql?branch={branch}"
+ query_params = urlencode([("branch", branch)])
+ url = f"{self.client.address}/schema.graphql?{query_params}"
response = self.client._get(url=url)
+ if response.status_code == 400:
+ raise BranchNotFoundError(
+ identifier=branch,
+ message=f"The requested branch was not found on the server [{branch}]"
+ )
+
if response.status_code != 200:
raise ValueError(f"Failed to fetch GraphQL schema: HTTP {response.status_code} - {response.text}")
return response.textCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In infrahub_sdk/schema/__init__.py around lines 719-736, URL-encode the branch
parameter when building the schema URL and replace the ad-hoc status_code check
with the client's established sync error handler to match the async variant;
specifically, encode branch with urllib.parse.quote_plus (or similar) when
interpolating into the URL and call the client's existing response-error routine
(e.g., self.client._raise_for_status(response) or the equivalent used elsewhere)
instead of manually raising ValueError so error handling/formatting stays
consistent.
Fixes #545
This PR adds a new command to generate Pydantic model that matches the format of a given GraphQL query
For each graphql query, a python file with the same name will be created with the corresponding Pydantic models.
In order to generate the return types, the graphql schema needs to exist locally so this PR also adds a command to fetch the graphql schema from the infrahub instance.
Implementation
Internally, the command is leveraging the library
ariadne-codegento generate the Pydantic models.ariadne-codegenprovide a plugin system that makes it very easy to control the format of the generated file, currently we are doing some small adjustment but the system is very powerful.The main drawback if using ``ariadne-codegen` is that by default the system has been designed to generate not just the return types but the full client so a wrapper was required to only generate the types.
Summary by CodeRabbit
New Features
Documentation
Refactor
Tests
Chores