とりあえず今回動作確認したケースではユーザ登録できるはず。
成果物
GitHub.Uploader.UnittestAfter.OperationCheck.201705051253
開発環境
- Linux Mint 17.3 MATE 32bit
- SQLite 3.8.2
- Python 3.4.3
前回まで
動作確認する概要
/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ファイルは以下。
- GNU.Licenses.sqlite3
- GitHub.Accounts.sqlite3
- GitHub.Apis.sqlite3
- GitHub.Languages.sqlite3
- GitHub.Licenses.sqlite3
- GitHub.Repositories.some_user_0.sqlite3
これで初回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鍵の生成も行う。リポジトリ数はゼロ。
所感
長かった…。