mirumee / ariadne-codegen

Generate fully typed Python client for any GraphQL API from schema, queries and mutations
BSD 3-Clause "New" or "Revised" License
269 stars 35 forks source link

`ClientForwardRefsPlugin` Error on generation #313

Open duodecanol opened 1 month ago

duodecanol commented 1 month ago

Summary

Error log

Selected strategy: Strategy.CLIENT
Using schema from '....................'.
Reading queries from ''.
Using '********' as package name.
Generating package into '********'.
Using '********' as client name.
Using 'AsyncBaseClient' as base client class.
Coping base client class from '********'.
Generating enums into 'enums.py'.
Generating inputs into 'input_types.py'.
Generating fragments into 'fragments.py'.
Comments type: stable
Converting fields and arguments name to snake case.
Generating async client.
No files to copy.
Plugins to use: ariadne_codegen.contrib.no_reimports.NoReimportsPlugin,ariadne_codegen.contrib.client_forward_refs.ClientForwardRefsPlugin
================================================================================
<ast.Name object at 0x7d2e44ae0ed0> ('lineno', 'col_offset', 'end_lineno', 'end_col_offset') ('id', 'ctx')
self
Traceback (most recent call last):
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/bin/ariadne-codegen", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/main.py", line 37, in main
    client(config_dict)
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/main.py", line 81, in client
    generated_files = package_generator.generate()
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/client_generators/package.py", line 171, in generate
    self._generate_client()
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/client_generators/package.py", line 257, in _generate_client
    client_module = self.client_generator.generate()
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/client_generators/client.py", line 148, in generate
    module = self.plugin_manager.generate_client_module(module)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/plugins/manager.py", line 60, in generate_client_module
    return self._apply_plugins_on_object("generate_client_module", module)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/plugins/manager.py", line 40, in _apply_plugins_on_object
    modified_obj = method(modified_obj, *args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/contrib/client_forward_refs.py", line 91, in generate_client_module
    self._insert_import_statement_in_method(method_def)
  File "/workspaces/python-devpod-try/xxDev/graphqltest/.venv/lib/python3.12/site-packages/ariadne_codegen/contrib/client_forward_refs.py", line 188, in _insert_import_statement_in_method
    module=self.imported_classes[import_class_id],
           ~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
KeyError: 'self'

Suggestion

https://github.com/mirumee/ariadne-codegen/blob/11bfe35bd62b2489927e0e93c6891bccc29c7f37/ariadne_codegen/contrib/client_forward_refs.py#L176-L188

        import_class_id = import_class.id

+        if import_class_id == "self":
+            return

        # We add the class to our set of imported in methods - these classes
        # don't need to be imported at all in the global scope.
        self.imported_in_method.add(import_class.id)
        method_def.body.insert(
            0,
            ast.ImportFrom(
                module=self.imported_classes[import_class_id],
                names=[import_class],
            ),
        )
duodecanol commented 1 month ago

In the above context:

print(self.imported_classes)
>>> {'AsyncBaseClient': '.async_base_client', 'UNSET': '.base_model', 'UnsetType': '.base_model', 'Upload': '.base_model', 'GraphQLField': '.base_operation'}
bombsimon commented 1 month ago

The code you're linking was changed in #306, do you have the same issue on latest main? If so, do you have a reproducible example or do you also get this on any of the two tests?

duodecanol commented 1 month ago

The code you're linking was changed in #306, do you have the same issue on latest main? If so, do you have a reproducible example or do you also get this on any of the two tests?

The pyroject.toml below would be suffice to demonstrate the case. Steps:

[project]
name = "ard-test"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
    "ariadne-codegen==0.14.0",
]
[tool.ariadne-codegen]

remote_schema_url = "https://countries.trevorblades.com"

enable_custom_operations = true

plugins = [
    "ariadne_codegen.contrib.client_forward_refs.ClientForwardRefsPlugin",
]
bombsimon commented 1 month ago

Thanks. The example uses latest release (0.14.0) but I was curious about if it failed on main. I used your example and can confirm it does. However, I'm wonder if this is partly related to enable_custom_operations = true since there are no other operations, there's also no other imports so the use of the plugin isn't clear to me. However the generation shouldn't fail.

I was going to run the tests for client forward refs on Python 3.12 but noticed that they never got added to the test fixture. I opened #314 to enable the tests

duodecanol commented 1 month ago

Thanks. The example uses latest release (0.14.0) but I was curious about if it failed on main. I used your example and can confirm it does. However, I'm wonder if this is partly related to enable_custom_operations = true since there are no other operations, there's also no other imports so the use of the plugin isn't clear to me. However the generation shouldn't fail.

I was going to run the tests for client forward refs on Python 3.12 but noticed that they never got added to the test fixture. I opened #314 to enable the tests

I was conducting a feasibility test of this package and am new to GraphQL. My use of the plugin was driven by curiosity to understand how plugins affect the code. The issue I’ve raised is to highlight a case of execution failure, but I am not experiencing issues with using this package otherwise—it appears to be excellent.

Attached are the diff results comparing two scenarios: one without any plugins and one with the ClientForwardRefsPlugin.

To achieve this, I added some error-bypassing code to the library package, which I previously mentioned.

diff --git a/graphql_client/client.py b/graphql_client/client.py
index dc5c970..8ff011b 100644
--- a/graphql_client/client.py
+++ b/graphql_client/client.py
@@ -16,7 +16,10 @@ from graphql import (
 )

 from .async_base_client import AsyncBaseClient
-from .base_operation import GraphQLField
+from .typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from ..base_operation import GraphQLField

 def gql(q: str) -> str:
@@ -25,7 +28,10 @@ def gql(q: str) -> str:

 class Client(AsyncBaseClient):
     async def execute_custom_operation(
-        self, *fields: GraphQLField, operation_type: OperationType, operation_name: str
+        self,
+        *fields: "GraphQLField",
+        operation_type: OperationType,
+        operation_name: str
     ) -> Dict[str, Any]:
         selections = self._build_selection_set(fields)
         combined_variables = self._combine_variables(fields)
@@ -43,7 +49,7 @@ class Client(AsyncBaseClient):
         return self.get_data(response)

     def _combine_variables(
-        self, fields: Tuple[GraphQLField, ...]
+        self, fields: Tuple["GraphQLField", ...]
     ) -> Dict[str, Dict[str, Any]]:
         variables_types_combined = {}
         processed_variables_combined = {}
@@ -90,11 +96,13 @@ class Client(AsyncBaseClient):
         )

     def _build_selection_set(
-        self, fields: Tuple[GraphQLField, ...]
+        self, fields: Tuple["GraphQLField", ...]
     ) -> List[SelectionNode]:
         return [field.to_ast(idx) for idx, field in enumerate(fields)]

-    async def query(self, *fields: GraphQLField, operation_name: str) -> Dict[str, Any]:
+    async def query(
+        self, *fields: "GraphQLField", operation_name: str
+    ) -> Dict[str, Any]:
         return await self.execute_custom_operation(
             *fields, operation_type=OperationType.QUERY, operation_name=operation_name
         )