Pygithub: 検索レート制限をサポート

作成日 2017年04月10日  ·  13コメント  ·  ソース: PyGithub/PyGithub

get_rate_limit関数は、Githubが「コア」レート制限と見なすものを返すようです。 ただし、コードの検索にはさまざまなレート制限があります。 ここを参照してください。

今のところ、私が知る限り、検索コードのレート制限を取得する方法はありません。

feature request

最も参考になるコメント

検索エンジンからここに到着した人のために、@ bbi-yggyの関数を少し変更しました。

from datetime import datetime, timezone

def rate_limited_retry(github):
    def decorator(func):
        def ret(*args, **kwargs):
            for _ in range(3):
                try:
                    return func(*args, **kwargs)
                except RateLimitExceededException:
                    limits = github.get_rate_limit()
                    reset = limits.search.reset.replace(tzinfo=timezone.utc)
                    now = datetime.now(timezone.utc)
                    seconds = (reset - now).total_seconds()
                    print(f"Rate limit exceeded")
                    print(f"Reset is in {seconds:.3g} seconds.")
                    if seconds > 0.0:
                        print(f"Waiting for {seconds:.3g} seconds...")
                        time.sleep(seconds)
                        print("Done waiting - resume!")
            raise Exception("Failed too many times")
        return ret
    return decorator

この関数は次のように使用できます。

@rate_limited_retry(github)
def run_query(import_string):
    query_string = f"language:Python \"{import_string}\""
    return list(github.search_code(query_string))

results = run_query(import_string)

全てのコメント13件

同じ問題が発生します。 これは、問題を例示する小さなスクリプトです。

import os
from datetime import datetime
from github import Github

# Login
TOKEN = os.getenv("GITHUB_ACCESS_TOKEN")
github = Github(TOKEN)

# Get initial rate limit and reset time
rl1 = github.get_rate_limit().rate
print("RL1 | Limit: {}, Remaining: {}, Reset: {}.".format(
    rl1.limit, rl1.remaining, rl1.reset))
# RL1 | Limit: 5000, Remaining: 5000, Reset: 2017-09-22 17:26:35.

# Perform a search
results = github.search_code("Hello World")

# Rate limit of Github instance is unchanged after a search
rl2 = github.get_rate_limit().rate
print("RL2 | Limit: {}, Remaining: {}, Reset: {}.".format(
    rl2.limit, rl2.remaining, rl2.reset))
# RL2 | Limit: 5000, Remaining: 5000, Reset: 2017-09-22 17:26:35.

# The PaginatedList instance has a Requestor with the same info
rl3 = results._PaginatedList__requester.rate_limiting
rl3_reset = datetime.utcfromtimestamp(int(
        results._PaginatedList__requester.rate_limiting_resettime))
print("RL3 | Limit: {}, Remaining: {}, Reset: {}.".format(
    rl3[0], rl3[1], rl3_reset))
# RL3 | Limit: 5000, Remaining: 5000, Reset: 2017-09-22 17:26:35.

# However, the actual ContentFile results show a different limit
# The Requester of each individual result ...
result = results[0]
rl4 = result._requester.rate_limiting
rl4_reset = datetime.utcfromtimestamp(int(
        result._requester.rate_limiting_resettime))
print("RL4 | Limit: {}, Remaining: {}, Reset: {}.".format(
    rl4[1], rl4[0], rl4_reset))
# RL4 | Limit: 30, Remaining: 29, Reset: 2017-09-22 16:27:36.

# ... and headers stored in the content file directly show a different rate limit.
rl5_limit = result._headers['x-ratelimit-limit']
rl5_remaining = result._headers['x-ratelimit-remaining']
rl5_reset = datetime.utcfromtimestamp(int(
        result._headers['x-ratelimit-reset']))
print("RL5 | Limit: {}, Remaining: {}, Reset: {}.".format(
    rl5_limit, rl5_remaining, rl5_reset))
# RL5 | Limit: 30, Remaining: 29, Reset: 2017-09-22 16:27:36.

# In the end, the main Github instance still shows the original full rate limit
rl6 = github.get_rate_limit().rate
print("RL6 | Limit: {}, Remaining: {}, Reset: {}.".format(
    rl6.limit, rl6.remaining, rl6.reset))
# RL6 | Limit: 5000, Remaining: 5000, Reset: 2017-09-22 17:26:35.

+1この機能は私が構築しようとしているアプリケーションに必要です

アプリケーションの@brentshermanaは、レート制限ヘッダー(最後の応答の、上記の例を参照)を検査するか、 /rate_limitエンドポイントを自分でポーリングすることを検討してください。 これには、あらゆる種類のレート制限に関する情報が含まれており、レート制限にはカウントされません。

最終的には、PyGithubがrateを解析するだけでなく、 /rate_limitが返すものからresourcesも解析するのがよいでしょう。 情報はそこにあります、残念ながらそれは図書館の消費者に利用可能にされません。

また、ページ付けされたリストは、そのような検索の結果、つまり_headers['x-ratelimit-*']に格納されているものを返す場合、コード検索のレート制限を返す必要があります。

ところで:私はちょうど気づきました、 /rate_limitによって返されるJSONからのフィールドrate $は非推奨であり、 resourcesの情報が推奨される代替手段です: https ://developer.github.com/

私はまさにそれをやっています。 誰かがこれを適応させてプルリクエストを試みたいのなら、あなたには私の祝福があります:

def wait(seconds):
    print("Waiting for {} seconds ...".format(seconds))
    time.sleep(seconds)
    print("Done waiting - resume!")

def api_wait():
    url = 'https://api.github.com/rate_limit'
    response = urlopen(url).read()
    data = json.loads(response.decode())
    if data['resources']['core']['remaining'] <= 10:  # extra margin of safety
        reset_time = data['resources']['core']['reset']
        wait(reset_time - time.time() + 10)
    elif data['resources']['search']['remaining'] <= 2:
        reset_time = data['resources']['search']['reset']
        wait(reset_time - time.time() + 10)

search_issuesからの結果に対する反復が、1869件の結果があるはずの1020件の結果の後に停止するという問題が発生しています。 私のスクリプトは毎回同じ時点で停止します。 これは律速の問題でしょうか?

エラーは発生しません。結果がなくなるだけです。 クエリ文字列をGitHubWebインターフェイスに直接配置すると、期待どおりに1869件の結果がすべて表示されます。 1020は30の倍数であるため、ページ付けの問題かどうか疑問に思います。

コードは次のとおりです。

querystring = "type:pr is:closed repo:xxxx closed:2017-07-01..2018-06-30"
issues = git.search_issues(query=querystring, sort="updated", order="asc")
for issue in issues:
    pull = issue.as_pull_request()
    print "%s: %s" % (pull.number, pull.title)

ここで何がうまくいかないかについて共有できるヒントに感謝します。

また、 issues.reversedを繰り返し処理して、予想される1869年の結果の最後に開始されるかどうかを確認しました。 ただし、この場合、結果の最初のページから30の問題しか取得できません。

さらに調査したところ、検索制限ごとに1000件の結果が発生しているようです。

検索レート制限にもう1つのメソッドget_search_rate_limit()を提供し、既存のget_rate_limit()はGithubによって提案された最新の「コア」レート制限を解析します: https ://developer.github.com/

SearchAPIのレート制限とGraphQLのレート制限が利用可能になりました。 すべてのための1つの方法。

デフォルトでは、「コア」レート制限が表示されます。 それぞれの属性にアクセスして、検索/ graphqlレート制限を取得することもできます。

r = g.get_rate_limit()
>>> r
RateLimit(core=Rate(remaining=4923, limit=5000))
>>> r.search
Rate(remaining=30, limit=30)
>>> r.graphql
Rate(remaining=5000, limit=5000)

よさそうだ、@ sfdyeに感謝!

@brentshermanaの待機関数をエミュレートして、検索レート制限の問題を回避するために、次のような操作を実行できます。

from datetime import datetime

def api_wait_search(git):
  limits = git.get_rate_limit()
  if limits.search.remaining <= 2:
    seconds = (limits.search.reset - datetime.now()).total_seconds()
    print "Waiting for %d seconds ..." % (seconds)
    time.sleep(seconds)
    print "Done waiting - resume!"

get_rate_limit()を呼び出すとわずかな遅延が発生するため、これを呼び出す頻度を最小限に抑えることができます。

検索エンジンからここに到着した人のために、@ bbi-yggyの関数を少し変更しました。

from datetime import datetime, timezone

def rate_limited_retry(github):
    def decorator(func):
        def ret(*args, **kwargs):
            for _ in range(3):
                try:
                    return func(*args, **kwargs)
                except RateLimitExceededException:
                    limits = github.get_rate_limit()
                    reset = limits.search.reset.replace(tzinfo=timezone.utc)
                    now = datetime.now(timezone.utc)
                    seconds = (reset - now).total_seconds()
                    print(f"Rate limit exceeded")
                    print(f"Reset is in {seconds:.3g} seconds.")
                    if seconds > 0.0:
                        print(f"Waiting for {seconds:.3g} seconds...")
                        time.sleep(seconds)
                        print("Done waiting - resume!")
            raise Exception("Failed too many times")
        return ret
    return decorator

この関数は次のように使用できます。

@rate_limited_retry(github)
def run_query(import_string):
    query_string = f"language:Python \"{import_string}\""
    return list(github.search_code(query_string))

results = run_query(import_string)

core / search / graphqlを考慮に入れるために上記のポーキーのデコレータの修正バージョン。
また、Githubが指定した時点でレート制限を正確にリセットしないため、30秒の遅延が追加されました。

def rate_limited_retry():
    def decorator(func):
        def ret(*args, **kwargs):
            for _ in range(3):
                try:
                    return func(*args, **kwargs)
                except RateLimitExceededException:
                    limits = gh.get_rate_limit()
                    print(f"Rate limit exceeded")
                    print("Search:", limits.search, "Core:", limits.core, "GraphQl:", limits.graphql)

                    if limits.search.remaining == 0:
                        limited = limits.search
                    elif limits.graphql.remaining == 0:
                        limited = limits.graphql
                    else:
                        limited = limits.core
                    reset = limited.reset.replace(tzinfo=timezone.utc)
                    now = datetime.now(timezone.utc)
                    seconds = (reset - now).total_seconds() + 30
                    print(f"Reset is in {seconds} seconds.")
                    if seconds > 0.0:
                        print(f"Waiting for {seconds} seconds...")
                        time.sleep(seconds)
                        print("Done waiting - resume!")
            raise Exception("Failed too many times")
        return ret
    return decorator

このページは役に立ちましたか?
0 / 5 - 0 評価

関連する問題

diegotejadav picture diegotejadav  ·  5コメント

AdyaAbhra picture AdyaAbhra  ·  5コメント

psychemedia picture psychemedia  ·  5コメント

rthill91 picture rthill91  ·  4コメント

surajjacob picture surajjacob  ·  4コメント