get_rate_limit ํจ์๋ Github์ด '์ฝ์ด' ์๋ ์ ํ์ผ๋ก ๊ฐ์ฃผํ๋ ๊ฒ์ ๋ฐํํ ๊ฒ ๊ฐ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ฝ๋ ๊ฒ์์๋ ๋ค๋ฅธ ์๋ ์ ํ์ด ์์ต๋๋ค. ์ฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ์ญ์์ค.
์ง๊ธ ๋น์ฅ์ ๋ด๊ฐ ๋งํ ์ ์๋ ํ ๊ฒ์ ์ฝ๋ ์๋ ์ ํ์ ์ป์ ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
๋๋ ๊ฐ์ ๋ฌธ์ ๋ฅผ ๋ณธ๋ค. ๋ค์์ ๋ฌธ์ ๋ฅผ ์์ํ๋ ์์ ์คํฌ๋ฆฝํธ์ ๋๋ค.
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-*']
์ ์ ์ฅ๋ ๋ชจ๋ ํญ๋ชฉ์ ๋ฐํํ๋ ๊ฒฝ์ฐ ์ฝ๋ ๊ฒ์์ ๋ํ ์๋ ์ ํ์ ๋ฐํํด์ผ ํฉ๋๋ค.
btw: ๋ฐฉ๊ธ $#$ /rate_limit
$#$์์ ๋ฐํ๋ JSON์ rate
ํ๋๋ ๋ ์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉฐ resources
์ ์ ๋ณด๊ฐ ๊ถ์ฅ๋๋ ๋์์์ ์์์ต๋๋ค. https://developer.github.com/ v3/rate_limit/#deprecation -๊ณต์ง
๋ด๊ฐ ์ ํํ ํ๊ณ ์์ด์. ๋๊ตฌ๋ ์ง ์ด๊ฒ์ ์ ์ฉํ๊ณ ํ ๋ฆฌํ์คํธ๋ฅผ ์๋ํ๊ณ ์ถ๋ค๋ฉด ์ ์ถ๋ณต์ด ์์ต๋๋ค.
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)
1869๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ์์ด์ผ ํ๋๋ฐ 1020๊ฐ์ ๊ฒฐ๊ณผ ์ดํ์ search_issues์ ๊ฒฐ๊ณผ์ ๋ํ ๋ฐ๋ณต์ด ์ค์ง๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ด ์คํฌ๋ฆฝํธ๋ ๋งค๋ฒ ๊ฐ์ ์ง์ ์์ ๋ฉ์ถฅ๋๋ค. ์ด๊ฒ์ด ์๋ ์ ํ ๋ฌธ์ ๊ฐ ๋ ์ ์์ต๋๊น?
์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์๊ณ ๊ฒฐ๊ณผ๋ง ๋์ต๋๋ค. ๋ด ์ฟผ๋ฆฌ ๋ฌธ์์ด์ GitHub ์น ์ธํฐํ์ด์ค์ ์ง์ ๋ฃ์ผ๋ฉด ์์๋๋ก 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๊ฐ ์ ํ ์ ๋๋ฌํ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
๊ธฐ์กด get_rate_limit()
๊ฐ Github์์ ์ ์ํ ์ต์ "์ฝ์ด" ์๋ ์ ํ์ ๊ตฌ๋ฌธ ๋ถ์ํ๋ ๋์ ๊ฒ์ ์๋ ์ ํ์ ๋ํด get_search_rate_limit()
๋ฐฉ๋ฒ์ ํ๋ ๋ ์ ๊ณตํ๋ ๊ฒ์ ์ด๋ป์ต๋๊น: https://developer.github.com/ v3/rate_limit/
๊ฒ์ API ์๋ ์ ํ ๋ฐ GraphQL ์๋ ์ ํ์ ์ง๊ธ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ชจ๋๋ฅผ ์ํ ํ๋์ ๋ฐฉ๋ฒ์ ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก "์ฝ์ด" ๋น์จ ์ ํ์ด ํ์๋ฉ๋๋ค. ๊ฐ ์์ฑ์ ์ก์ธ์คํ์ฌ search/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์ ๊ณ ๋ คํ๋๋ก ์์ pokey ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์์ ํ์ต๋๋ค.
๋ํ 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
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
๊ฒ์ ์์ง์์ ์ฌ๊ธฐ์ ๋์ฐฉํ๋ ์ฌ๋๋ค์ ์ํด @bbi-yggy์ ๊ธฐ๋ฅ์ ์ฝ๊ฐ ์์ ํ์ต๋๋ค.
์ด ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.