やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

GitHubアップローダの改修方針について考えてみた

GitHubアップローダの改修シリーズ。

前回まで

http://ytyaru.hatenablog.com/entry/2017/09/30/000000
http://ytyaru.hatenablog.com/entry/2017/10/01/000000

  • 重複コードの解消
  • GitHubAPI部分の別ファイル実装

http://ytyaru.hatenablog.com/entry/2017/10/01/000000

  • Data.pyを共通化

今回

  • GitHubAPIとアプリ機能を別ファイルに実装したい

以下のような感じにざっくり分ける。テキトーなのであとで変更するかもしれない。

  • GitHubUploader
    • init
      • cui
        • account
        • other_repo
        • license
        • language
      • database
    • uploader
    • github
      • pagenation
      • headers
      • response

使っているAPI網羅

実装

def __GetLanguages(self):
    url = 'https://api.github.com/repos/{0}/{1}/languages'.format(self.data.get_username(), self.data.get_repo_name())
    r = requests.get(url)
    if 300 <= r.status_code:
        print(r.status_code)
        print(r.text)
        print(url)
        raise Exception("HTTP Error {0}".format(r.status_code))
        return None
    else:
        print(r.text)
        return json.loads(r.text)
def __CreateRemoteRepository(self):
    url = 'https://api.github.com/user/repos'
    post_data = json.dumps({"name": self.data.get_repo_name(), "description": self.data.get_repo_description(), "homepage": self.data.get_repo_homepage()})
    headers={
        "Time-Zone": "Asia/Tokyo",
        "Authorization": "token {0}".format(self.data.get_access_token(['repo']))
    }
    r = requests.post(url, data=post_data, headers=headers)
    print(r.text)
    time.sleep(2)
    return json.loads(r.text)
def __DeleteRemoteRepository(self):
    url = 'https://api.github.com/repos/{0}/{1}'.format(self.data.get_username(), self.data.get_repo_name())
    headers={
        "Time-Zone": "Asia/Tokyo",
        "Authorization": "token {0}".format(self.data.get_access_token(['delete_repo']))
    }
    r = requests.delete(url, headers=headers)
    if 204 != r.status_code:
        raise Exception('HTTPエラー: {0}'.format(status_code))
    time.sleep(2)
def __EditRemoteRepository(self, name, description, homepage):
    # リポジトリ名は必須
    url = 'https://api.github.com/repos/{0}/{1}'.format(self.data.get_username(), self.data.get_repo_name())
    headers={
        "Time-Zone": "Asia/Tokyo",
        "Authorization": "token {0}".format(self.data.get_access_token())
    }
    data = {}
    data['name'] = name
    if not(None is description or '' == description):
        data['description'] = description
    if not(None is homepage or '' == homepage):
        data['homepage'] = homepage

    r = requests.patch(url, headers=headers, data=json.dumps(data))
    if 200 != r.status_code:
        raise Exception('HTTPエラー: {0}'.format(r.status_code))
    time.sleep(2)
    return json.loads(r.text)
def __RequestLicenses(self):
    licenses = []
    url = 'https://api.github.com/licenses'
    r = requests.get(url, headers=self.__GetHttpHeaders())
    licenses += self.__ReturnResponse(r, success_code=200)
    next = self.page.get_next(r)
    while (None is not next):
        r = requests.get(next, headers=self.__GetHttpHeaders())
        licenses += self.__ReturnResponse(r, success_code=200)
        next = self.page.get_next(r)
    return licenses

def __RequestLicense(self, key):
    url = 'https://api.github.com/licenses/' + key
    r = requests.get(url, headers=self.__GetHttpHeaders())
    return self.__ReturnResponse(r, success_code=200)

def __GetHttpHeaders(self):
    return {
        "Accept": "application/vnd.github.drax-preview+json",
        "Time-Zone": "Asia/Tokyo",
        "Authorization": "token {0}".format(self.data.get_access_token())
    }

def __ReturnResponse(self, r, success_code=None, sleep_time=2, is_show=True):
    if is_show:
        print("HTTP Status Code: {0}".format(r.status_code))
        print(r.text)
    time.sleep(sleep_time)
    if None is not success_code:
        if (success_code != r.status_code):
            raise Exception('HTTP Error: {0}'.format(r.status_code))
            return None
    return json.loads(r.text)

def __BoolToInt(self, bool_value):
    if True == bool_value:
        return 1
    else:
        return 0

def __ArrayToString(self, array):
    ret = ""
    for v in array:
        ret = v + ','
    return ret[:-1]

HTTPの処理

  • Request
    • pagenation
    • HttpMethod
      • get, post, patch, delete
    • URL
    • parameter
    • headers
      • “Accept”: “application/vnd.github.drax-preview+json”,
      • “Time-Zone”: “Asia/Tokyo”,
      • “Authorization”: “token {0}”.format(self.data.get_access_token())
    • 正常時HTTPコード
      • それ以外は例外発生
      • 例外時の対処
        • 標準出力
        • ファイル出力
    • 形式変換
      • カンマ区切り文字列←→配列
      • 0,1←→True,False
    • sleep
      • サーバの負荷対策
    • count
      • API実行回数と日時
  • Response
    • 型変換
      • TEXT: r.text
      • JSON: json.loads(r.text)
      • バイナリ: r.content
    • 形式変換
      • カンマ区切り文字列←→配列
      • 0,1←→True,False

階層わけ

  1. HTTP通信
  2. GitHubAPI仕様
  3. SQLiteインタフェース(形式)
  4. Pythonインタフェース(関数、try-catch)
  5. ログ
  6. アプリケーション機能

分類

  1. HTTP通信
    • pagenation
    • 型変換
      • TEXT: r.text
      • JSON: json.loads(r.text)
      • バイナリ: r.content
  2. GitHubAPI仕様(v3)
    • get, post, patch, delete
    • URL
    • parameter
    • headers
      • “Accept”: “application/vnd.github.drax-preview+json”,
      • “Time-Zone”: “Asia/Tokyo”,
      • “Authorization”: “token {0}”.format(self.data.get_access_token())
    • sleep
      • リクエスト上限(サーバの負荷対策)
    • count
      • API実行回数と日時
  3. SQLiteインタフェース(形式)
    • 形式変換
      • カンマ区切り文字列←→配列
      • 0,1←→True,False
  4. Pythonインタフェース(関数、try-catch)
    • API仕様にあわせたPythonインタフェース
    • 正常時HTTPコード
      • それ以外は例外発生
  5. ログ
     * 例外時の対処
         * 標準出力
         * ファイル出力
    
  6. アプリケーション機能
    • ライセンス
      • ファイルからライセンスのキー名取得してAPI一括問い合わせ
    • リポジトリ
    • AccessToken
      • 全scopeのTokenを一括作成
      • このアプリで使うscopeのTokenを一括作成

APIエンドポイント

https://api.github.com/をブラウザのロケールバーから実行した結果。

{
"current_user_url": "https://api.github.com/user", 
"current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}", 
"authorizations_url": "https://api.github.com/authorizations", 
"code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}", 
"commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}", 
"emails_url": "https://api.github.com/user/emails", 
"emojis_url": "https://api.github.com/emojis", 
"events_url": "https://api.github.com/events", 
"feeds_url": "https://api.github.com/feeds", 
"followers_url": "https://api.github.com/user/followers", 
"following_url": "https://api.github.com/user/following{/target}", 
"gists_url": "https://api.github.com/gists{/gist_id}", 
"hub_url": "https://api.github.com/hub", 
"issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}", 
"issues_url": "https://api.github.com/issues", 
"keys_url": "https://api.github.com/user/keys", 
"notifications_url": "https://api.github.com/notifications", 
"organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}", 
"organization_url": "https://api.github.com/orgs/{org}", 
"public_gists_url": "https://api.github.com/gists/public", 
"rate_limit_url": "https://api.github.com/rate_limit", 
"repository_url": "https://api.github.com/repos/{owner}/{repo}", 
"repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}", 
"current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}", 
"starred_url": "https://api.github.com/user/starred{/owner}{/repo}", 
"starred_gists_url": "https://api.github.com/gists/starred", 
"team_url": "https://api.github.com/teams", 
"user_url": "https://api.github.com/users/{user}", 
"user_organizations_url": "https://api.github.com/user/orgs", 
"user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}", 
"user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}" }

GitHub API v3 | GitHub Developer Guide

上記の仕様書にはall(全部)とあったが、全部ではない。たとえばhttps://api.github.com/licensesがない。

ライセンスAPIといい、なぜ「all(全部)」という嘘をつくのか。それともHTTPヘッダのAcceptにAPIのバージョンを指定すると変化するのだろうか。

API制約

APIごとに異なるもの。APIごとの差異をうまく隠蔽するにはどうしたらいいか。具体的に見えない。とくにParameter。

HTTPリクエスト引数

parameterの設定。これが面倒そう。共通化できるのか。どういうインタフェースになるのか。具体的に見えない。

  • 名前
    • object
    • array
    • string
    • integer
    • boolean
  • 制約
    • 必須
    • check(いずれかの値or複合or特定の組合せのみ)
    • default
      • キーなし
      • None
      • 指定値

所感

まとめられるだろうか。