diff --git a/docs/docs/infrahubctl/infrahubctl-graphql.mdx b/docs/docs/infrahubctl/infrahubctl-graphql.mdx index e97d5216..143fc027 100644 --- a/docs/docs/infrahubctl/infrahubctl-graphql.mdx +++ b/docs/docs/infrahubctl/infrahubctl-graphql.mdx @@ -16,13 +16,21 @@ $ infrahubctl graphql [OPTIONS] COMMAND [ARGS]... **Commands**: -* `query-report`: Run a GraphQL query through... +* `query-report`: Report how Infrahub will interpret a... * `export-schema`: Export the GraphQL schema to a file. * `generate-return-types`: Create Pydantic Models for GraphQL query... ## `infrahubctl graphql query-report` -Run a GraphQL query through InfrahubGraphQLQueryReport and report its analysis. +Report how Infrahub will interpret a GraphQL query. + +Reports whether the query targets unique nodes, meaning every query it contains returns a +single object. Artifact and generator definitions use this to decide how much to regenerate +when data changes: when it is true, only the artifacts or generator instances of the objects +that changed are regenerated, and when it is false, all of them are. + +A query returns a single object when it filters on ids or hfid, or on every part of one +uniqueness constraint of the model, and the values it filters on are always provided. **Usage**: @@ -32,12 +40,12 @@ $ infrahubctl graphql query-report [OPTIONS] NAME **Arguments**: -* `NAME`: Name of the GraphQL query to analyze. [required] +* `NAME`: Name of the GraphQL query to analyze, as declared under queries in .infrahub.yml. [required] **Options**: * `--online`: Fetch the query from the Infrahub server (CoreGraphQLQuery by name) instead of reading it from the local .infrahub.yml file. -* `--branch TEXT`: Branch on which to run the report. +* `--branch TEXT`: Branch on which to run the report. Uniqueness constraints come from the schema, so the result can differ between branches. * `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml] * `--help`: Show this message and exit. diff --git a/infrahub_sdk/ctl/graphql.py b/infrahub_sdk/ctl/graphql.py index 37d55137..c46a893e 100644 --- a/infrahub_sdk/ctl/graphql.py +++ b/infrahub_sdk/ctl/graphql.py @@ -112,7 +112,9 @@ def callback() -> None: @app.command(name="query-report") @catch_exception(console=console) async def query_report( - name: str = typer.Argument(..., help="Name of the GraphQL query to analyze."), + name: str = typer.Argument( + ..., help="Name of the GraphQL query to analyze, as declared under queries in .infrahub.yml." + ), online: bool = typer.Option( False, "--online", @@ -121,10 +123,25 @@ async def query_report( "instead of reading it from the local .infrahub.yml file." ), ), - branch: str | None = typer.Option(None, help="Branch on which to run the report."), + branch: str | None = typer.Option( + None, + help=( + "Branch on which to run the report. Uniqueness constraints come from the schema, " + "so the result can differ between branches." + ), + ), _: str = CONFIG_PARAM, ) -> None: - """Run a GraphQL query through InfrahubGraphQLQueryReport and report its analysis.""" + """Report how Infrahub will interpret a GraphQL query. + + Reports whether the query targets unique nodes, meaning every query it contains returns a + single object. Artifact and generator definitions use this to decide how much to regenerate + when data changes: when it is true, only the artifacts or generator instances of the objects + that changed are regenerated, and when it is false, all of them are. + + A query returns a single object when it filters on ids or hfid, or on every part of one + uniqueness constraint of the model, and the values it filters on are always provided. + """ client = initialize_client(branch=branch) if online: diff --git a/pyproject.toml b/pyproject.toml index b8941b9a..f6fc3987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,7 +163,7 @@ invalid-assignment = "ignore" # 1 violation in importer/json.py include = ["infrahub_sdk/node/node.py"] [tool.ty.overrides.rules] -invalid-argument-type = "ignore" # 9 violations - lines 776, 855, 859, 862 +invalid-argument-type = "ignore" # 8 violations [[tool.ty.overrides]] @@ -183,7 +183,6 @@ unused-ignore-comment = "ignore" # Clashes with mypy's type ignore comments include = ["tests/fixtures/**"] [tool.ty.overrides.rules] -invalid-argument-type = "ignore" # Test fixtures - dynamic mock data possibly-missing-attribute = "ignore" # Test fixtures use dynamic attributes # Test-specific overrides - tests have more lenient type checking @@ -231,9 +230,8 @@ include = [ ] [tool.ty.overrides.rules] -invalid-argument-type = "ignore" # 29 violations +invalid-argument-type = "ignore" # 25 violations invalid-assignment = "ignore" -no-matching-overload = "ignore" possibly-missing-attribute = "ignore" [[tool.ty.overrides]] @@ -276,7 +274,7 @@ disable_error_code = ["call-overload"] [[tool.mypy.overrides]] module = "infrahub_sdk.utils" -disable_error_code = ["arg-type", "attr-defined", "return-value", "union-attr"] +disable_error_code = ["attr-defined", "return-value", "union-attr"] [[tool.mypy.overrides]] # ``main.py`` intentionally narrows the ``attributes``/``relationships``/``choices`` fields inherited @@ -290,7 +288,7 @@ disable_error_code = ["assignment"] # The generated read models expose ``kind`` via ``@computed_field`` stacked on ``@property``. mypy # does not support decorators on top of ``@property`` and flags it, but pydantic requires this order. module = "infrahub_sdk.schema.generated.read" -disable_error_code = ["misc"] +disable_error_code = ["prop-decorator"] [tool.ruff] line-length = 120 diff --git a/uv.lock b/uv.lock index 201b6daa..d5ba79e9 100644 --- a/uv.lock +++ b/uv.lock @@ -582,24 +582,24 @@ wheels = [ [[package]] name = "h2" -version = "4.3.0" +version = "4.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "hpack" }, { name = "hyperframe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/85/7c366e69d84c17bb778fe41419e1fbcce3033d5b7ce29bbffff0a98b859f/h2-4.4.1.tar.gz", hash = "sha256:4e866ffb1a869ae14dd9b5e6beb5c24a13da0495ad72b65925ded182521c1516", size = 2157281, upload-time = "2026-08-03T11:45:09.509Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/e85faf23bd72a92d1921e37d674ca56eb298a3c8be31fdecef0ff2b3aaac/h2-4.4.1-py3-none-any.whl", hash = "sha256:0e25f1462b23c9cb82d9eb02e28bc706dac2a68cb457c6a0d74d63c8a2a5d0e6", size = 62636, upload-time = "2026-08-03T11:44:59.164Z" }, ] [[package]] name = "hpack" -version = "4.1.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/5b/fcabf6028144a8723726318b07a32c2f3314acdff6265743cf08a344b18e/hpack-4.2.0.tar.gz", hash = "sha256:0895cfa3b5531fc65fe439c05eb65144f123bf7a394fcaa56aa423548d8e45c0", size = 51300, upload-time = "2026-06-23T18:34:46.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, + { url = "https://files.pythonhosted.org/packages/71/b4/4a9fcfb2aef6ba44d9073ecd301443aa00b3dac95de5619f2a7de7ec8a91/hpack-4.2.0-py3-none-any.whl", hash = "sha256:858ac0b02280fa582b5080d68db0899c62a80375e0e5413a74970c5e518b6986", size = 34246, upload-time = "2026-06-23T18:34:45.472Z" }, ] [[package]]