Source code for temple.ls

"""
temple.ls
~~~~~~~~~

Lists all temple templates and projects spun up with those templates
"""
import collections

import requests

import temple.check
import temple.constants
import temple.utils


def _parse_link_header(headers):
    """Parses Github's link header for pagination.

    TODO eventually use a github client for this
    """
    links = {}
    if 'link' in headers:
        link_headers = headers['link'].split(', ')
        for link_header in link_headers:
            (url, rel) = link_header.split('; ')
            url = url[1:-1]
            rel = rel[5:-1]
            links[rel] = url
    return links


def _code_search(query, github_user=None):
    """Performs a Github API code search

    Args:
        query (str): The query sent to Github's code search
        github_user (str, optional): The Github user being searched in the query string

    Returns:
        dict: A dictionary of repository information keyed on the git SSH url

    Raises:
        `InvalidGithubUserError`: When ``github_user`` is invalid
    """
    github_client = temple.utils.GithubClient()
    headers = {'Accept': 'application/vnd.github.v3.text-match+json'}

    resp = github_client.get('/search/code',
                             params={'q': query, 'per_page': 100},
                             headers=headers)

    if resp.status_code == requests.codes.unprocessable_entity and github_user:
        raise temple.exceptions.InvalidGithubUserError(
            'Invalid Github user or org - "{}"'.format(github_user))
    resp.raise_for_status()

    resp_data = resp.json()

    repositories = collections.defaultdict(dict)
    while True:
        repositories.update({
            'git@github.com:{}.git'.format(repo['repository']['full_name']): repo['repository']
            for repo in resp_data['items']
        })

        next_url = _parse_link_header(resp.headers).get('next')
        if next_url:
            resp = requests.get(next_url, headers=headers)
            resp.raise_for_status()
            resp_data = resp.json()
        else:
            break

    return repositories


[docs]@temple.utils.set_cmd_env_var('ls') def ls(github_user, template=None): """Lists all temple templates and packages associated with those templates If ``template`` is None, returns the available templates for the configured Github org. If ``template`` is a Github path to a template, returns all projects spun up with that template. ``ls`` uses the github search API to find results. Note that the `temple.constants.TEMPLE_ENV_VAR` is set to 'ls' for the duration of this function. Args: github_user (str): The github user or org being searched. template (str, optional): The template git repo path. If provided, lists all projects that have been created with the provided template. Note that the template path is the SSH path (e.g. git@github.com:CloverHealth/temple.git) Returns: dict: A dictionary of repository information keyed on the SSH Github url Raises: `InvalidGithubUserError`: When ``github_user`` is invalid """ temple.check.has_env_vars(temple.constants.GITHUB_API_TOKEN_ENV_VAR) if template: temple.check.is_git_ssh_path(template) template_repo_name = temple.check.get_name_from_ssh_path(template) search_q = 'user:{} filename:{} {}'.format( github_user, temple.constants.TEMPLE_CONFIG_FILE, template_repo_name) else: search_q = 'user:{} cookiecutter.json in:path'.format(github_user) results = _code_search(search_q, github_user) return collections.OrderedDict(sorted(results.items()))