Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions 05_graphql.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0ff34c42",
"metadata": {},
"source": [
"# graphql\n",
"\n",
"> GitHub GraphQL API support for ghapi"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe7862d1",
"metadata": {},
"outputs": [],
"source": [
"#| default_exp graphql"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "373423dc",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"import os\n",
"from gql import gql, Client\n",
"from gql.transport.requests import RequestsHTTPTransport"
]
},
{
"cell_type": "markdown",
"id": "a8ea827d",
"metadata": {},
"source": [
"## GhGql Client\n",
"\n",
"The `GhGql` class provides a lazy-loaded GraphQL client. The schema is fetched on first use, not at import time."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "553fc866",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"#| export\n",
"class GhGql:\n",
" \"GitHub GraphQL client with lazy-loaded schema\"\n",
" def __init__(self, token=None):\n",
" self.token = token or os.getenv('GITHUB_TOKEN')\n",
" self._client = None\n",
" \n",
" @property\n",
" def client(self):\n",
" \"Lazy-load the GraphQL client and schema\"\n",
" if self._client is None:\n",
" if not self.token: raise ValueError(\"GITHUB_TOKEN not set\")\n",
" transport = RequestsHTTPTransport(\n",
" url='https://api.github.com/graphql',\n",
" headers={'Authorization': f'bearer {self.token}'}\n",
" )\n",
" self._client = Client(transport=transport, fetch_schema_from_transport=True)\n",
" self._client.execute(gql('{ __typename }')) # Trigger schema fetch\n",
" return self._client\n",
" \n",
" @property\n",
" def schema(self): return self.client.schema\n",
" \n",
" def __call__(self, query, variables=None):\n",
" \"Execute a GraphQL query\"\n",
" return self.client.execute(gql(query), variable_values=variables)\n",
" \n",
" def list_queries(self):\n",
" \"List all available top-level GraphQL query types\"\n",
" return list(self.schema.query_type.fields.keys())\n",
" \n",
" def query_args(self, name):\n",
" \"Show arguments for a query type\"\n",
" return self.schema.query_type.fields[name].args\n",
" \n",
" def type_fields(self, name):\n",
" \"List fields on a GraphQL type (use PascalCase, e.g. 'Repository')\"\n",
" return list(self.schema.type_map[name].fields.keys())"
]
},
{
"cell_type": "markdown",
"id": "e7468f81",
"metadata": {},
"source": [
"## Module-level convenience functions\n",
"\n",
"These use a default client instance, similar to how powertools works."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b1464c99",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"#| export\n",
"_default_client = None\n",
"\n",
"def _get_client():\n",
" \"Get or create the default client\"\n",
" global _default_client\n",
" if _default_client is None: _default_client = GhGql()\n",
" return _default_client\n",
"\n",
"def list_queries():\n",
" \"List all available top-level GraphQL query types\"\n",
" return _get_client().list_queries()\n",
"\n",
"def query_args(name:str):\n",
" \"Show arguments for a query type\"\n",
" return _get_client().query_args(name)\n",
"\n",
"def type_fields(name:str):\n",
" \"List fields on a GraphQL type (use PascalCase, e.g. 'Repository')\"\n",
" return _get_client().type_fields(name)\n",
"\n",
"def gh_query(query:str, variables:dict[str,any]=None):\n",
" \"Execute a GraphQL query\"\n",
" return _get_client()(query, variables)"
]
},
{
"cell_type": "markdown",
"id": "45ca0d7f",
"metadata": {},
"source": [
"## Examples"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17b59d28",
"metadata": {},
"outputs": [],
"source": [
"#| eval: false\n",
"# List available query types\n",
"list_queries()[:10]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b59fec62",
"metadata": {},
"outputs": [],
"source": [
"#| eval: false\n",
"# See what args a query takes\n",
"query_args('repository')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f073780a",
"metadata": {},
"outputs": [],
"source": [
"#| eval: false\n",
"# See fields on a type\n",
"type_fields('Repository')[:10]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "706870a7",
"metadata": {},
"outputs": [],
"source": [
"#| eval: false\n",
"# Execute a query\n",
"gh_query('''\n",
"query($owner: String!, $name: String!) {\n",
" repository(owner: $owner, name: $name) {\n",
" description\n",
" stargazerCount\n",
" }\n",
"}\n",
"''', {'owner': 'AnswerDotAI', 'name': 'ghapi'})"
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}
13 changes: 13 additions & 0 deletions ghapi/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@
'ghapi.event._want_evt': ('event.html#_want_evt', 'ghapi/event.py'),
'ghapi.event.load_sample_events': ('event.html#load_sample_events', 'ghapi/event.py'),
'ghapi.event.save_sample_events': ('event.html#save_sample_events', 'ghapi/event.py')},
'ghapi.graphql': { 'ghapi.graphql.GhGql': ('graphql.html#ghgql', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.__call__': ('graphql.html#ghgql.__call__', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.__init__': ('graphql.html#ghgql.__init__', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.client': ('graphql.html#ghgql.client', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.list_queries': ('graphql.html#ghgql.list_queries', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.query_args': ('graphql.html#ghgql.query_args', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.schema': ('graphql.html#ghgql.schema', 'ghapi/graphql.py'),
'ghapi.graphql.GhGql.type_fields': ('graphql.html#ghgql.type_fields', 'ghapi/graphql.py'),
'ghapi.graphql._get_client': ('graphql.html#_get_client', 'ghapi/graphql.py'),
'ghapi.graphql.gh_query': ('graphql.html#gh_query', 'ghapi/graphql.py'),
'ghapi.graphql.list_queries': ('graphql.html#list_queries', 'ghapi/graphql.py'),
'ghapi.graphql.query_args': ('graphql.html#query_args', 'ghapi/graphql.py'),
'ghapi.graphql.type_fields': ('graphql.html#type_fields', 'ghapi/graphql.py')},
'ghapi.metadata': {},
'ghapi.page': { 'ghapi.page.GhApi.last_page': ('page.html#ghapi.last_page', 'ghapi/page.py'),
'ghapi.page._Scanner': ('page.html#_scanner', 'ghapi/page.py'),
Expand Down
75 changes: 75 additions & 0 deletions ghapi/graphql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""GitHub GraphQL API support for ghapi"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../05_graphql.ipynb.

# %% auto 0
__all__ = ['GhGql', 'list_queries', 'query_args', 'type_fields', 'gh_query']

# %% ../05_graphql.ipynb 2
import os
from gql import gql, Client
from gql.transport.requests import RequestsHTTPTransport

# %% ../05_graphql.ipynb 4
class GhGql:
"GitHub GraphQL client with lazy-loaded schema"
def __init__(self, token=None):
self.token = token or os.getenv('GITHUB_TOKEN')
self._client = None

@property
def client(self):
"Lazy-load the GraphQL client and schema"
if self._client is None:
if not self.token: raise ValueError("GITHUB_TOKEN not set")
transport = RequestsHTTPTransport(
url='https://api.github.com/graphql',
headers={'Authorization': f'bearer {self.token}'}
)
self._client = Client(transport=transport, fetch_schema_from_transport=True)
self._client.execute(gql('{ __typename }')) # Trigger schema fetch
return self._client

@property
def schema(self): return self.client.schema

def __call__(self, query, variables=None):
"Execute a GraphQL query"
return self.client.execute(gql(query), variable_values=variables)

def list_queries(self):
"List all available top-level GraphQL query types"
return list(self.schema.query_type.fields.keys())

def query_args(self, name):
"Show arguments for a query type"
return self.schema.query_type.fields[name].args

def type_fields(self, name):
"List fields on a GraphQL type (use PascalCase, e.g. 'Repository')"
return list(self.schema.type_map[name].fields.keys())

# %% ../05_graphql.ipynb 6
_default_client = None

def _get_client():
"Get or create the default client"
global _default_client
if _default_client is None: _default_client = GhGql()
return _default_client

def list_queries():
"List all available top-level GraphQL query types"
return _get_client().list_queries()

def query_args(name:str):
"Show arguments for a query type"
return _get_client().query_args(name)

def type_fields(name:str):
"List fields on a GraphQL type (use PascalCase, e.g. 'Repository')"
return _get_client().type_fields(name)

def gh_query(query:str, variables:dict[str,any]=None):
"Execute a GraphQL query"
return _get_client()(query, variables)