SkillAgentSearch skills...

Agithub

Agnostic Github client API -- An EDSL for connecting to REST servers

Install / Use

/learn @mozilla/Agithub
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

The Agnostic GitHub API

It doesn't know, and you don't care!

agithub is a REST API client with transparent syntax which facilitates rapid prototyping — on any REST API!

Originally tailored to the GitHub REST API, AGitHub has grown up to support many other REST APIs:

  • DigitalOcean
  • Facebook
  • GitHub
  • OpenWeatherMap
  • SalesForce

Additionally, you can add full support for another REST API with very little new code! To see how, check out the Facebook client, which has about 30 lines of code.

This works because AGithub knows everything it needs to about protocol (REST, HTTP, TCP), but assumes nothing about your upstream API.

Use

The most striking quality of AGitHub is how closely its syntax emulates HTTP. In fact, you might find it even more convenient than HTTP, and almost as general (as far as REST APIs go, anyway). The examples below tend to use the GitHub API as a reference point, but it is no less easy to use agithub with, say, the Facebook Graph.

Create a client

from agithub.GitHub import GitHub
client = GitHub()

GET

Here's how to do a GET request, with properly-encoded url parameters:

client.issues.get(filter='subscribed')

That is equivalent to the following:

GET /issues/?filter=subscribed

POST

Here's how to send a request body along with your request:

some_object = {'foo': 'bar'}
client.video.upload.post(body=some_object, tags="social devcon")

This will send the following request, with some_object serialized as the request body:<sup>*</sup>

POST /video/upload?tags=social+devcon

{"foo": "bar"}

The body parameter is reserved and is used to define the request body to be POSTed. tags is an example query parameter, showing that you can pass both an object to send as the request body as well as query parameters.

<sup>*</sup> For now, the request body is limited to JSON data; but we plan to add support for other types as well

Parameters

headers

Pass custom http headers in your ruquest with the reserved parameter headers.

from agithub.GitHub import GitHub
g = GitHub()
headers = {'Accept': 'application/vnd.github.symmetra-preview+json'}
status, data = g.search.labels.get(headers=headers, repository_id=401025, q='¯\_(ツ)_/¯')
print(data['items'][0])
{u'default': False, u'name': u'\xaf\\_(\u30c4)_/\xaf', u'url': u'https://api.github.com/repos/github/hub/labels/%C2%AF%5C_(%E3%83%84)_/%C2%AF', u'color': u'008672', u'node_id': u'MDU6TGFiZWwxMTcwNjYzNTM=', u'score': 43.937515, u'id': 117066353, u'description': u''}

body

If you're using POST, PUT, or PATCH (post(), put(), or patch()), then you should include the body as the body= argument. The body is serialized to JSON before sending it out on the wire.

from agithub.GitHub import GitHub
g = GitHub()
# This Content-Type header is only required in this example due to a GitHub 
# requirement for this specific markdown.raw API endpoint
headers={'Content-Type': 'text/plain'}  
body = '# This should be my header'
status, data = g.markdown.raw.post(body=body, headers=headers)
print(data)
<h1>
<a id="user-content-this-should-be-my-header" class="anchor" href="#this-should-be-my-header" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>This should be my header</h1>

Example App

  1. First, instantiate a GitHub object.

    from agithub.GitHub import GitHub
    g = GitHub()
    
  2. When you make a request, the status and response body are passed back as a tuple.

    status, data = g.users.octocat.get()
    print(data['name'])
    print(status)
    
    The Octocat
    200
    
  3. If you forget the request method, agithub will complain that you haven't provided enough information to complete the request.

    g.users
    
    <class 'agithub.github.IncompleteRequest'>: /users
    
  4. Sometimes, it is inconvenient (or impossible) to refer to a URL as a chain of attributes, so indexing syntax is provided as well. It behaves exactly the same. In these examples we use indexing syntax because you can't have a python function name

    • starting with a digit : 1
    • containing a dash (-) character : Spoon-Knife
    g.repos.github.hub.issues[1].get()
    g.repos.octocat['Spoon-Knife'].branches.get()
    
    (200, { 'id': '#blah', ... })
    (200, [ list, of, branches ])
    
    
  5. You can also pass query parameter to the API as function parameters to the method function (e.g. get).

    status, data = g.repos.octocat['Spoon-Knife'].issues.get(
        state='all', creator='octocat')
    print(data[0].keys())
    print(status)
    
    [u'labels', u'number', … , u'assignees']
    200
    

    Notice the syntax here: <API-object>.<URL-path>.<request-method>(<query-parameters>)

    • API-object : g
    • URL-path : repos.octocat['Spoon-Knife'].issues
    • request-method : get
    • query-parameters : state='all', creator='octocat'
  6. As a weird quirk of the implementation, you may build a partial call to the upstream API, and use it later.

    def following(self, user):
        return self.user.following[user].get
    
    myCall = following(g, 'octocat')
    if 204 == myCall()[0]:
        print 'You are following octocat'
    
    You are following octocat
    

    You may find this useful — or not.

  7. Finally, agithub knows nothing at all about the GitHub API, and it won't second-guess you.

    g.funny.I.donna.remember.that.one.head()
    
    (404, {'message': 'Not Found'})
    

    The error message you get is directly from GitHub's API. This gives you all of the information you need to survey the situation.

  8. If you need more information, the response headers of the previous request are available via the getheaders() method.

    g.getheaders()
    
    [('status', '404 Not Found'),
     ('x-ratelimit-remaining', '54'),
     …
     ('server', 'GitHub.com')]
    

    Note that the headers are standardized to all lower case. So though, in this example, GitHub returns a header of X-RateLimit-Remaining the header is returned from getheaders as x-ratelimit-remaining

Error handling

Errors are handled in the most transparent way possible: they are passed on to you for further scrutiny. There are two kinds of errors that can crop up:

  1. Networking Exceptions (from the http library). Catch these with try .. catch blocks, as you otherwise would.

  2. GitHub API errors. These mean you're doing something wrong with the API, and they are always evident in the response's status. The API considerately returns a helpful error message in the JSON body.

Specific REST APIs

agithub includes a handful of implementations for specific REST APIs. The example above uses the GitHub API but only for demonstration purposes. It doesn't include any GitHub specific functionality (for example, authentication).

Here is a summary of additional functionality available for each distinct REST API with support included in agithub. Keep in mind, agithub is designed to be extended to any REST API and these are just an initial collection of APIs.

GitHub : agithub/GitHub.py

GitHub Authentication

To initiate an authenticated GitHub object, pass it your username and password or a token.

from agithub.GitHub import GitHub
g = GitHub('user', 'pass')
from agithub.GitHub import GitHub
g = GitHub(token='token')

GitHub Pagination

When calling the GitHub API with a query that returns many results, GitHub will paginate the response, requiring you to request each page of results with separate API calls. If you'd like to automatically fetch all pages, you can enable pagination in the GitHub object by setting paginate to True.

from agithub.GitHub import GitHub
g = GitHub(paginate=True)
status, data = g.repos.octocat['Spoon-Knife'].issues.get()

status, data = g.users.octocat.repos.get(per_page=1)
print(len(data))
8

(added in v2.2.0)

GitHub Rate Limiting

By default, if GitHub returns a response indicating that a request was refused due to rate limiting, agithub will wait until the point in time when the rate limit is lifted and attempt the call again.

If you'd like to disable this behavior and instead just return the error response from GitHub set sleep_on_ratelimit to False.

from agithub.GitHub import GitHub
g = GitHub(sleep_on_ratelimit=False)
status, data = g.repos.octocat['Spoon-Knife'].issues.get()
print(status)
print(data['message'])
403
API rate limit exceeded for 203.0.113.2. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)

(added in v2.2.0)

GitHub Logging

To see log messages related to GitHub specific features like pagination and rate limiting, you can use a root logger from the Python logging module.

import logging
logging.basicConfig()
logger = logging.getLogger()  # The root logger
logger.setLevel(logging.DEBUG)
from agithub.GitHub import GitHub
g = GitHub(paginate=True)
status, data = g.repos.octocat['Spoon-Knife'].issues.get()
DEBUG:agithub.GitHub:No GitHub ratelimit remaining. Sleeping for 676 seconds until 14:22:43 before trying API call again.
DEBUG:agithub.GitHub:Fetching an additional paginated GitHub response page at https://api.github.com/repositories/1300192/issues?page=2
DEBUG:agithub.GitHub:Fetching an additional 

Related Skills

View on GitHub
GitHub Stars429
CategoryDevelopment
Updated29d ago
Forks64

Languages

Python

Security Score

95/100

Audited on Mar 7, 2026

No findings