Part3: Code Generation ====================== One of the best benefits to using a schema first approach is that it makes it incredibly easy to parse and generate code from this well defined schema. Cannula offers a simple way to generate data classes, models, and resolver definitions to assist with type hints and making sure your code is in sync with the schema. First we need to add some details to the `pyproject.toml` for your application: .. literalinclude:: ../examples/tutorial/dashboard/part3/pyproject.toml You'll notice we are including some custom Scalars in this config, we can use these in our schema file to alter the input/output of certain fields. Cannula provides some basic ones that are useful but you can add your own via this same config. Here is our schema that is using the `UUID` scalar. The `UUID` Scalar will convert a string into a UUID for output and convert a string to a `UUID` on input. Next in order to create SQLAlchemy models from the schema file we'll need to add metadata directives to the types in our schema. There are two main directives at the moment: * **db_sql** * **field_meta** These directives will indicate to cannula how to generate type classes and relations. For `db_sql` this indicates we wish to create a `SQLAlchemy` model, which by default will use the pluralized lowercase of the type name as the db table. For `field_meta` there are many more options as fields can represent data or related items, there are also arguments and resolver functions. Here is our simplied schema to demostrate some of the basic options: .. literalinclude:: ../examples/tutorial/dashboard/part3/schema.graphql :language: graphql Now we just need to run the codegen command in this folder to generate the base types: .. code-block:: bash $ cannula codegen This will create the `gql` folder that we set in the `pyproject.toml` and add the following files. types.py -------- .. literalinclude:: ../examples/tutorial/dashboard/part3/gql/types.py For each `type` in our schema cannula generates a dataclass with all the simple fields as class vars. The related fields that reference another `type` or have arguments are rendered as async resolver functions. These functions call the corresponding datasource on the context object to retrieve the results. Then it creates Protocols for the operation resolvers with the correct signature. For example the `peopleQuery` will return a list of `User` this will ensure that our `RootValue` will have the correct signature and return values and our editor will highlight these errors. sql.py ------ .. literalinclude:: ../examples/tutorial/dashboard/part3/gql/sql.py The sql file contains all the database table definitions that we have defined. For a full reference please refer to the :ref:`codegen` context.py ---------- .. literalinclude:: ../examples/tutorial/dashboard/part3/gql/context.py The context is added to all the resolvers and is a way to share datasources between all the functions that are resolving data for a given query. Here have the class `UserDatasource` that maps the types to the `User` type and the `DBUser` table. The `Context` object exposes these and adds any initialization the datasources need. Wire everything up ------------------ With most of the code generated for us all we have to do is connect these pieces to our graph and FastAPI. First we'll add a bit of code to create the new tables and add some test data: .. code-block:: python # in dashboard.core.seed_data async def add_part3_users(session) -> list[uuid.UUID]: from dashboard.part3.gql.context import UserDatasource users = UserDatasource(session) user_id = uuid.uuid4() await users.add(id=user_id, name="test", email="sam@ex.com") another_id = uuid.uuid4() await users.add(id=another_id, name="another", email="sammie@ex.com") return [user_id, another_id] We will call this in our tests to seed the database. Then we have to provide a couple resolvers that were not autogenerated for us and create a CannulaAPI instance: .. literalinclude:: ../examples/tutorial/dashboard/part3/graph.py Finally we need to connect our application to an endpoint so we can access this. We will use cannula contrib dependency for FastAPI that will handle converting the request body into a graphql request (query, variables, operationName). This dependency returns a callable which we can use to inject our custom context. .. literalinclude:: ../examples/tutorial/dashboard/part3/routes.py We can use the apollo sandbox to test this out, first run the following: .. code-block:: bash $ make initdb $ make addusers $ make run This will start up the application locally with a few test users next go to the Apollo sandbox: https://studio.apollographql.com/sandbox/explorer/ Change the connection url to `http://localhost:8000/part3/graph` Once it loads you should see your schema and you can try the following query: .. code-block:: graphql query ExampleQuery { people { email id } } This should return something like this: .. code-block:: javascript { "data": { "people": [ { "email": "user@email.com", "id": "683f89e1-b9e2-4af8-bb7e-7b2bccfe54a3", } ] }, "errors": null, "extensions": null } Great this is looking better, now we need to explore :doc:`part4`