yanyongyu / githubkit

The modern, all-batteries-included GitHub SDK for Python, including rest api, graphql, webhooks, like octokit!
MIT License
173 stars 25 forks source link

Differences between regular GH and GHES API endpoints #133

Open fau-st opened 1 day ago

fau-st commented 1 day ago

Hello,

I'm successfully using this library to interact with a GitHub Enterprise Server through the REST API. As you may know, for GHES, the REST API endpoint is located at https://[hostname]/api/v3/ instead of https://api.github.com/. So far so good as I can use the base_url parameter when building a GitHub object.

However, the troubles appear when I try to make a request using the graphql endpoint. Indeed, GHES uses this endpoint for graphql : https://[hostname]/api/graphql/ (the v3/ suffix is no longer present) and I can no longer use base_url because it's different whether I use the REST or graphql API.

Sample code :

from githubkit import (
    AppAuthStrategy,
    GitHub,
)

INSTALLATION_ID: int = ...
auth_strategy: AppAuthStrategy = ...

gh = GitHub(
    auth_strategy.as_installation(INSTALLATION_ID),
    base_url='https://ghes-hostname/api/v3',
)

gh.graphql('query { viewer { login } }')
# >>> githubkit.exception.RequestFailed: Response(404 Not Found, data_model=typing.Any)

Here, githubkit successfully retrieves a token through the REST API to use for the graphql query but then this query is made against https://ghes-hostname/api/v3/graphql and a 404 response is returned.

I'd like to know your opinion on this as supporting GHES might not be your priority. Let me know if you need more information.

yanyongyu commented 1 day ago

According to octokit, a workaround for GHES is needed. https://github.com/octokit/graphql.js/blob/dae781b027c19bcd458577cd9ac6ca888b2fdfeb/src/graphql.ts#L67-L72

fau-st commented 2 hours ago

Based on your findings, I wrote a quick workaround based on HTTPS's event hooks.

async def _fix_ghes_graphql_endpoint(request: httpx.Request) -> None:
    GHES_GRAPHQL_SUFFIX_TOFIX = b"/api/v3/graphql"
    if request.url.raw_path.endswith(GHES_GRAPHQL_SUFFIX_TOFIX):
        request.url = request.url.copy_with(
            raw_path=request.url.raw_path[: -len(GHES_GRAPHQL_SUFFIX_TOFIX)] + b"/api/graphql"
        )

It can then be used during Client's creation and transparently fix URLs:

return httpx.Client(
    **self._get_client_defaults(),
    transport=transport,
    event_hooks={'request': [_fix_ghes_graphql_endpoint]}
)

What's your opinion on this? I'm not totally satisfied because hooks make it harder to follow the flow of the request but here it seems a good fit.

FYI: I'll be away for the next two weeks or so, I can try to create a PR when I'm back.

yanyongyu commented 2 hours ago

Maybe a direct patch to endpoint url in graphql request function looks clearer. i will create a pr when i am free.