Pygithub: Limite de taxa de pesquisa de suporte

Criado em 10 abr. 2017  ·  13Comentários  ·  Fonte: PyGithub/PyGithub

Parece que a função get_rate_limit retornará o que o Github considera o limite de taxa 'core'. No entanto, existem limites de taxa diferentes para pesquisar código. Veja aqui .

No momento, não há uma maneira de obter os limites de taxa de código de pesquisa, tanto quanto posso dizer.

feature request

Comentários muito úteis

Para as pessoas que chegam aqui do mecanismo de pesquisa, modifiquei um pouco a função de @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

Esta função pode ser usada da seguinte forma:

@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)

Todos 13 comentários

Eu vejo o mesmo problema. Aqui está um pequeno script que exemplifica o problema.

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 Este recurso é necessário para um aplicativo que estou tentando construir

@brentshermana para seu aplicativo, considere inspecionar cabeçalhos de limite de taxa (da última resposta; veja no meu exemplo acima) ou pesquisar o endpoint /rate_limit você mesmo. Que contém informações sobre todos os tipos de limites de taxa e não conta para nenhum limite de taxa.

Eventualmente, seria bom se o PyGithub não apenas analisasse rate mas também analisasse resources do que /rate_limit retorna. A informação está lá, infelizmente não é disponibilizada aos consumidores da biblioteca.

Além disso, a lista paginada deve retornar o limite de taxa para pesquisa de código se retornar resultados de tal pesquisa, ou seja, o que estiver armazenado em _headers['x-ratelimit-*'] .

btw: Acabei de notar, o campo rate do JSON retornado por /rate_limit está obsoleto e as informações em resources são a alternativa recomendada: https://developer.github.com/ v3/rate_limit/#deprecation -notice

Estou fazendo exatamente isso. Se alguém quiser adaptar isso e tentar fazer um pull request, você tem minha benção:

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)

Estou enfrentando um problema em que minha iteração sobre os resultados de search_issues para após 1.020 resultados, quando deveria haver 1.869 resultados. Meu script para sempre no mesmo ponto. Isso poderia ser um problema de limitação de taxa?

Eu não recebo um erro, os resultados simplesmente se esgotam. Se eu colocar minha string de consulta diretamente na interface da Web do GitHub, vejo todos os 1869 resultados, conforme o esperado. 1020 é um múltiplo de 30, o que me faz pensar se é problema de paginação?

Código é o seguinte:

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)

Muito obrigado por todas as dicas que você pode compartilhar sobre o que pode estar errado aqui.

Eu também tentei iterar por issues.reversed para ver se começaria no final dos meus resultados esperados de 1869. No entanto, neste caso, recebo apenas 30 edições, desde a primeira página de resultados.

Em uma investigação mais aprofundada, parece que estou chegando ao limite de 1.000 resultados por pesquisa .

Que tal fornecermos mais um método get_search_rate_limit() para o limite de taxa de pesquisa, enquanto o get_rate_limit() existente analisará o limite de taxa "principal" mais recente sugerido pelo Github: https://developer.github.com/ v3/rate_limit/

O limite de taxa da API de pesquisa e o limite de taxa do GraphQL já estão disponíveis. Um método para todos.

Por padrão, ele mostrará o limite de taxa "núcleo". Você também pode obter o limite de taxa de pesquisa/graphql acessando os respectivos atributos.

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)

Parece ótimo, obrigado @sfdye!

Para emular a função de espera do @brentshermana para evitar problemas com a limitação da taxa de pesquisa, agora você pode fazer algo assim:

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!"

Observe que chamar get_rate_limit() introduzirá um pequeno atraso, portanto, você pode querer minimizar a frequência com que chama isso.

Para as pessoas que chegam aqui do mecanismo de pesquisa, modifiquei um pouco a função de @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

Esta função pode ser usada da seguinte forma:

@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)

Versão modificada do decorador do pokey acima para levar em conta core/search/graphql.
Também foi adicionado um atraso de 30 segundos porque o Github não redefine o limite de taxa exatamente no momento em que diz.

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

Esta página foi útil?
0 / 5 - 0 avaliações