やってみる

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

GitHubアップローダのアカウント登録のinsertで一部パターンを動作確認して修正した

とりあえず今回動作確認したケースではユーザ登録できるはず。

成果物

GitHubGitHub.Uploader.UnittestAfter.OperationCheck.201705051253

開発環境

前回まで

動作確認する概要

/database/res/db/配下に以下のDBだけを残して削除。

ユーザ登録する。以下のようにコマンドを実行して。

python3 GitHubUserRegister.py insert -u some_user_0 -p some_pass -s github.com.some_user_0
python3 GitHubUserRegister.py insert -u user1_2fa -p some_pass -t dummy_2fa_secret -s github.com.user1_2fa
python3 GitHubUserRegister.py insert -u ytyaru -p some_pass -s github.com.ytyaru
python3 GitHubUserRegister.py insert -u user3_dummy -p some_pass -s github.com.user3_dummy
python3 GitHubUserRegister.py insert -u user4_test -p some_pass -s github.com.user4_test
python3 GitHubUserRegister.py insert -u pylangstudy -p some_pass

上から順にコマンド1, コマンド2, …と仮称する。

起動引数パターン ユースケース
-u -p -s ~/.ssh/config設定済み(既に使っていたアカウントをDBに登録する)
-u -p -t -s 上記の2FA有効アカウント版。
-u -p SSH設定を何もしていない。GitHubアカウント作成後メールをベリファイしただけの新しいアカウントを登録する(SSH鍵生成から鍵のGitHub登録も行う)。

コマンド1実行

python3 GitHubUserRegister.py insert -u some_user_0 -p some_pass -s github.com.some_user_0

1回目(エラー)

AttributeError: '__Headers' object has no attribute 'Link'

ContentTypeクラスの刷新でLinkクラスを削除し、Paginatorクラス( ./web/http/Paginator.py)に変更したため存在しない。呼出側はまだ修正していなかったのでエラーになった。

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/web/service/github/api/v3/users/Emails.py", line 29, in Gets
    url = self.__response.Headers.Link.Next(r)

他にもPaginationするAPIの実装で利用されている可能性があるので以下コマンドでgrep検索した。

find . -name "*.py" -print0 | xargs -0 grep ".Headers.Link."
./web/service/github/api/v3/users/Emails.py:            url = self.__response.Headers.Link.Next(r)
./web/service/github/api/v3/users/SshKeys.py:            url = self.__response.Headers.Link.Next(r)
./web/service/github/api/v3/repositories/Repositories.py:            url = self.__response.Headers.Link.Next(r)
./web/service/github/api/v3/miscellaneous/Licenses.py:            url = self.__response.Headers.Link.Next(r)

以下のようにwhile文ごと変更。import文も。

before

while None is not url:
    ...
    url = self.__response.Headers.Link.Next(r)
    ...

after

return paginator.Paginate(url, **params)

2回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/web/service/github/api/v3/users/Emails.py", line 25, in Gets
    paginator = web.http.Paginator(web.service.github.api.v3.Response.Response(web.http.Response.Response()))
TypeError: __init__() takes 1 positional argument but 2 were given

1回目の修正が間違っていた。Responseコンストラクタに引数は不要。

paginator = web.http.Paginator(web.service.github.api.v3.Response.Response())

3回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/web/service/github/api/v3/users/Emails.py", line 26, in Gets
    paginator = web.http.Paginator(web.service.github.api.v3.Response.Response())
TypeError: 'module' object is not callable

またしても間違えていた。正しくは以下。

paginator = web.http.Paginator.Paginator(web.service.github.api.v3.Response.Response())

4回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/cui/register/command/Inserter.py", line 83, in Run
    self.__db.Accounts['AccessTokens'].insert(self.__CreateRecordToken(account['Id'], token, j_ssh['id']))
KeyError: 'Id'

Id列が存在しない。そんなわけがない。テーブル作成に失敗している?

$ sqlite3 GitHub.Accounts.sqlite3 
sqlite> .tables
AccessTokens  Accounts    

なぜかこの2テーブルしかない。datasetでPythonからSQLiteを操作するとき、テーブルが存在しないと勝手にテーブルを作成する。たぶんそれのせい。そもそも最初にDB作成されるはずなのだが。

./cui/register/command/Inserter.py

self.__db = database.src.Database.Database()
self.__db.Initialize()

エラーのせいでDBファイルだけ作成されてテーブル作成されなかった?GitHub.Accounts.sqlite3ファイル削除してもう一度やってみる。しかし同じ結果。

./database/Database.pyでDB新規生成するコードは実装されている。

if not os.path.isfile(self.__files['account']):
    m = database.src.account.Main.Main(self.__files['account'])
    m.Create()

pdb

https://docs.python.jp/3/library/pdb.html

python3 -m pdb GitHubUserRegister.py insert -u some_user_0 -p some_pass -s github.com.some_user_0
(Pdb) h

Documented commands (type help <topic>):
========================================
EOF    c          d        h         list      q        rv       undisplay
a      cl         debug    help      ll        quit     s        unt      
alias  clear      disable  ignore    longlist  r        source   until    
args   commands   display  interact  n         restart  step     up       
b      condition  down     j         next      return   tbreak   w        
break  cont       enable   jump      p         retval   u        whatis   
bt     continue   exit     l         pp        run      unalias  where    

Miscellaneous help topics:
==========================
pdb  exec

s|ステップイン。現在の行を実行し、最初に実行可能なものがあらわれたときに (呼び出された関数の中か、現在の関数の次の行で) 停止します。 n|ステップオーバー。現在の関数の次の行に達するか、あるいは関数が返るまで実行を継続します。 (next と step の差は step が呼び出された関数の内部で停止するのに対し、 next は呼び出された関数を (ほぼ) 全速力で実行し、現在の関数内の次の行で停止するだけです。) r|ステップアウト。現在の関数が返るまで実行を継続します。 c|ブレークポイントに出会うまで、実行を継続します。 j|次に実行する行を指定します。最も底のフレーム中でのみ実行可能です。 unt|引数[lineno]なしだと、現在の行から 1 行先まで実行します。

ブレークポイントを貼る

bブレークポイントを貼る。

(Pdb) b ./database/src/Database.py:107
Breakpoint 1 at /tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/Database.py:107

ブレークポイントまで実行

cブレークポイントまで実行。

(Pdb) c

現在行のコード表示

前後11行分。

(Pdb) l
102                     m = database.src.gnu_license.Main.Main(self.__files['gnu_license'])
103                     m.Run()
104                 self.__gnu_license = dataset.connect('sqlite:///' + self.__files['gnu_license'])
105     
106             # アカウントDB生成(ファイル、テーブル作成。データ挿入はCUIにて行う)
107 B->          if None is self.__account:
108                 self.__account = dataset.connect('sqlite:///' + self.__files['account'])
109                 if not os.path.isfile(self.__files['account']):
110                     m = database.src.account.Main.Main(self.__files['account'])
111                     m.Create()
112                 self.__account = dataset.connect('sqlite:///' + self.__files['account'])

変数の値をみる

pで変数値を表示する。

(Pdb) p self._Database__account
None

self.__accountのようにprivateな値を参照するときは少し面倒。

(Pdb) p self.__account
*** AttributeError: 'Database' object has no attribute '__account'

バグに気づく

Database.py

# アカウントDB生成(ファイル、テーブル作成。データ挿入はCUIにて行う)
if None is self.__account:
    self.__account = dataset.connect('sqlite:///' + self.__files['account']) # こいつのせいでSQLiteファイル新規作成されてしまう
    if not os.path.isfile(self.__files['account']):
        m = database.src.account.Main.Main(self.__files['account'])
        m.Create()
    self.__account = dataset.connect('sqlite:///' + self.__files['account'])

正しくは以下。

# アカウントDB生成(ファイル、テーブル作成。データ挿入はCUIにて行う)
if None is self.__account:
    if not os.path.isfile(self.__files['account']):
        m = database.src.account.Main.Main(self.__files['account'])
        m.Create()
    self.__account = dataset.connect('sqlite:///' + self.__files['account'])

API、GnuLicenseのDBも同様のバグがあったのでコメントアウトしておいた。

再確認

  • GitHub.Accounts.sqlite3ファイル削除する
  • (Pdb) b ./database/src/Database.py:107
  • (Pdb) c
(Pdb) l
102                     m = database.src.gnu_license.Main.Main(self.__files['gnu_license'])
103                     m.Run()
104                 self.__gnu_license = dataset.connect('sqlite:///' + self.__files['gnu_license'])
105     
106             # アカウントDB生成(ファイル、テーブル作成。データ挿入はCUIにて行う)
107 B->          if None is self.__account:
108     #            self.__account = dataset.connect('sqlite:///' + self.__files['account'])
109                 if not os.path.isfile(self.__files['account']):
110                     m = database.src.account.Main.Main(self.__files['account'])
111                     m.Create()
112                 self.__account = dataset.connect('sqlite:///' + self.__files['account'])
(Pdb) p self._Database__account
None
(Pdb) s
> /tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/Database.py(109)__OpenDb()
-> if not os.path.isfile(self.__files['account']):
(Pdb) p self._Database__account
None
(Pdb) s
--Call--
> /usr/lib/python3.4/genericpath.py(27)isfile()
-> def isfile(path):
(Pdb) p path
'/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/res/db/GitHub.Accounts.sqlite3'
(Pdb) p isfile(path)
False
(Pdb) s
> /usr/lib/python3.4/genericpath.py(29)isfile()
-> try:
(Pdb) r
--Return--
> /usr/lib/python3.4/genericpath.py(32)isfile()->False
-> return False
(Pdb) s
> /tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/Database.py(110)__OpenDb()
-> m = database.src.account.Main.Main(self.__files['account'])
(Pdb) 

アカウント作成処理が実行されるルートになった。これで問題ないはず。

ブレープポイント解除

clで解除。

(Pdb) cl ./database/src/Database.py:107

ブレークポイントまで実行。(1つもないから最後まで実行される)

(Pdb) c

エラー

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/web/service/github/api/v3/repositories/Repositories.py", line 48, in gets
    return paginator.Paginate(url, **params)
NameError: name 'params' is not defined

1回目の修正で以下のコードを削除してしまっていた。復元しておく。

params = self.__GetCreateParameter(visibility, affiliation, type, sort, direction, per_page)

再度実行するもエラー無し。

コマンド2実行

python3 GitHubUserRegister.py insert -u user1_2fa -p some_pass -t dummy_2fa_secret -s github.com.user1_2fa

問題なし。ただ、CC0ライセンス情報を取得するAPIが発行されたようにみえた。ライセンスのマスターDBを作成していなかったのか?

DB初回作成

ついでだから、全DBを削除して最初からやってみる。./database/res/db/配下のDBファイルをすべて削除して以下コマンドを実行する。

python3 GitHubUserRegister.py insert -u some_user_0 -p some_pass -s github.com.some_user_0

1回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/Database.py", line 90, in __OpenDb
    m = database.src.language.Main.Main(self.__files['lang'])
AttributeError: 'module' object has no attribute 'Main'

エラーが出てしまった。これはDatabase系コードもテストすべきか。

importが間違っていたっぽいので修正。

#import database.src.language.insert.Main
import database.src.language.Main

ほかにも*.insert.Mainで使われていないモジュールのimport文があったのでコメントアウトした。

2回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/language/Main.py", line 24, in __Insert
    source = database.src.language.insert.LanguageSource.LanguageSource()
AttributeError: 'module' object has no attribute 'insert'

以下のimport文が足りなかったので追記した。

import database.src.language.insert.LanguageSource
import database.src.language.insert.Inserter

GitHub.Languages.sqlite3ファイルが作成されていたので削除しておいた。たぶん中途半端な状態なので。

3回目(エラー)

以下はAPI用DBと思われる。

48   Users.Emails.List   GET user/emails Basic,Token 200 https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
以下の行は列ヘッダと数が合わないため処理しません。
48  Users.Emails.List   GET user/emails Basic,Token 200 https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
49  Users.Emails.Add    POST    user/emails Basic,Token 201 https://developer.github.com/v3/users/emails/#add-email-addresses
以下の行は列ヘッダと数が合わないため処理しません。
49  Users.Emails.Add    POST    user/emails Basic,Token 201 https://developer.github.com/v3/users/emails/#add-email-addresses
50  Users.Emails.Delete DELETE  user/emails Basic,Token 204 https://developer.github.com/v3/users/emails/#delete-email-addresses
以下の行は列ヘッダと数が合わないため処理しません。
50  Users.Emails.Delete DELETE  user/emails Basic,Token 204 https://developer.github.com/v3/users/emails/#delete-email-addresses
51  Users.Emails.TogglePrimary  PATCH   user/email/visibility   Basic,Token 200 https://developer.github.com/v3/users/emails/#toggle-primary-email-visibility
以下の行は列ヘッダと数が合わないため処理しません。
51  Users.Emails.TogglePrimary  PATCH   user/email/visibility   Basic,Token 200 https://developer.github.com/v3/users/emails/#toggle-primary-email-visibility

以下の行は列ヘッダと数が合わないため処理しません。

/database/src/api/res/tsv/Apis.tsvのうちIdが48〜51のレコードを以下のように変更した。Grants列が無かったのがエラーの原因。

48   Users.Emails.List   GET user/emails Basic,Token user:email,user 200 https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user
49  Users.Emails.Add    POST    user/emails Basic,Token user:email,user 201 https://developer.github.com/v3/users/emails/#add-email-addresses
50  Users.Emails.Delete DELETE  user/emails Basic,Token user:email,user 204 https://developer.github.com/v3/users/emails/#delete-email-addresses
51  Users.Emails.TogglePrimary  PATCH   user/email/visibility   Basic,Token user:email,user 200 https://developer.github.com/v3/users/emails/#toggle-primary-email-visibility

たぶんimport不足。

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/Database.py", line 103, in __OpenDb
    m = database.src.gnu_license.Main.Main(self.__files['gnu_license'])
AttributeError: 'module' object has no attribute 'Main'

import database.src.gnu_license.Mainを追記した。

生成されたDBファイルを全削除してもう一度やる。

4回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/language/Main.py", line 28, in __Insert
    inserter.Insert(self.source.Get())
AttributeError: 'Main' object has no attribute 'source'

ローカル変数なのにselfで参照していたのが原因。以下のように修正した。

#inserter.Insert(self.source.Get())
inserter.Insert(source.Get())

5回目(エラー)

DBファイル削除して再実行するもエラー。

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/database/src/gnu_license/insert/main.py", line 11, in __init__
    self.__db.Licenses = dataset.connect('sqlite:///' + path_gnu_licenses_sqlite3)
AttributeError: 'GnuSite' object has no attribute '_GnuSite__db'

db(Databaseインスタンス)は引数で渡されていない。DB生成前だから当然か。以下のように単なるローカル変数にした。

def __init__(self, path_gnu_licenses_sqlite3):
#    self.__db.Licenses = dataset.connect('sqlite:///' + path_gnu_licenses_sqlite3)
    self.__db_Licenses = dataset.connect('sqlite:///' + path_gnu_licenses_sqlite3)

6回目(成功!)

DBファイル削除して再実行。成功した!DBの初回作成に2〜3分間ほどかかった。指定ユーザのリポジトリ数が多いと取得時間も長くなる。作成されたDBファイルは以下。

これで初回DB作成の動作確認は完了と考えていいか。

コマンド2実行

DB初回作成後、あらためてコマンド2を実行。成功した。

python3 GitHubUserRegister.py insert -u user1_2fa -p some_pass -t dummy_2fa_secret -s github.com.user1_2fa

CC0ライセンス情報を取得するAPIが発行されたようにみえたが、よくみるとDBから取得したようだ。

コマンド3実行

成功。11分間かかった。

python3 GitHubUserRegister.py insert -u ytyaru -p some_pass -s github.com.ytyaru

このアカウントはリポジトリが200以上あるので時間がかかる。サーバ負荷とリクエスト上限対策のため1リクエスト2秒間待機する。2秒*200件=400秒間=約7分間は待機時間として必要。

また、以前Idが30番台のうちいくつかのリポジトリはライセンス情報が取得できていなかった。LICENSE.txtは正常なはずなのに。原因不明のままだった。とくにエラーなく進んだためアップローダのプログラム上は問題ないと思われる。

また、リポジトリ一覧取得は1リクエスト100件取得するように実装した。リポジトリは206件あったので3回リクエストしたはず。エラーなく終了したためページネーションに成功したと思われる。

コマンド4実行

成功。

python3 GitHubUserRegister.py insert -u user3_dummy -p some_pass -s github.com.user3_dummy

コマンド5実行

python3 GitHubUserRegister.py insert -u user4_test -p some_pass -s github.com.user4_test

1回目(エラー)

  File "/tmp/GitHub.Uploader.UnittestAfter.OperationCheck.201705051253/cui/register/command/Inserter.py", line 168, in __GetGitHubSsh
    j_ssh = client.SshKeys.Gets(mailaddress, public_key)
TypeError: Gets() takes 2 positional arguments but 3 were given

使用するAPIが間違っていた。以下のように修正した。

#j_ssh = client.SshKeys.Gets(mailaddress, public_key)
j_ssh = client.SshKeys.Create(public_key, title=mailaddress)

DBファイルは作成されていなかった。

2回目(成功!)

できた。DBファイルの存在を確認した。

コマンド6実行

成功。

python3 GitHubUserRegister.py insert -u pylangstudy -p some_pass

これまでのコマンドと違って、SSH鍵の生成も行う。リポジトリ数はゼロ。

所感

長かった…。