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 结果的迭代在 1020 个结果之后停止,而应该有 1869 个结果。 我的脚本每次都在同一点停止。 这可能是一个限速问题吗?

我没有收到错误,结果就用完了。 如果我将查询字符串直接放入 GitHub Web 界面,那么我会看到所有 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 results per search limit

我们再提供一种方法get_search_rate_limit()用于搜索速率限制,而现有的get_rate_limit()将解析 Github 建议的最新“核心”速率限制: https ://developer.github.com/

搜索 API 速率限制和 GraphQL 速率限制现已推出。 一种方法。

默认情况下,它会显示“核心”速率限制。 您还可以通过访问相应的属性来获取搜索/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)

修改了上面 pokey 的装饰器以考虑 core/search/graphql。
还增加了 30 秒的延迟,因为 Github 并没有在它说的时候准确地重置速率限制。

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 等级