update_manager: refactor github API requests
Save a request cache in the CommandHelper that stores the etag and value for each URL requested. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
88ee83c7d8
commit
f2b48d0f1a
|
@ -44,6 +44,7 @@ if TYPE_CHECKING:
|
||||||
APIComp = klippy_apis.KlippyAPI
|
APIComp = klippy_apis.KlippyAPI
|
||||||
SCMDComp = shell_command.ShellCommandFactory
|
SCMDComp = shell_command.ShellCommandFactory
|
||||||
DBComp = database.MoonrakerDatabase
|
DBComp = database.MoonrakerDatabase
|
||||||
|
JsonType = Union[List[Any], Dict[str, Any]]
|
||||||
|
|
||||||
MOONRAKER_PATH = os.path.normpath(os.path.join(
|
MOONRAKER_PATH = os.path.normpath(os.path.join(
|
||||||
os.path.dirname(__file__), "../../.."))
|
os.path.dirname(__file__), "../../.."))
|
||||||
|
@ -362,6 +363,7 @@ class CommandHelper:
|
||||||
|
|
||||||
AsyncHTTPClient.configure(None, defaults=dict(user_agent="Moonraker"))
|
AsyncHTTPClient.configure(None, defaults=dict(user_agent="Moonraker"))
|
||||||
self.http_client = AsyncHTTPClient()
|
self.http_client = AsyncHTTPClient()
|
||||||
|
self.github_request_cache: Dict[str, CachedGithubResponse] = {}
|
||||||
|
|
||||||
# GitHub API Rate Limit Tracking
|
# GitHub API Rate Limit Tracking
|
||||||
self.gh_rate_limit: Optional[int] = None
|
self.gh_rate_limit: Optional[int] = None
|
||||||
|
@ -403,7 +405,7 @@ class CommandHelper:
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
resp = await self.github_api_request(url, is_init=True)
|
resp = await self.github_api_request(url, is_init=True)
|
||||||
assert resp is not None
|
assert isinstance(resp, dict)
|
||||||
core = resp['resources']['core']
|
core = resp['resources']['core']
|
||||||
self.gh_rate_limit = core['limit']
|
self.gh_rate_limit = core['limit']
|
||||||
self.gh_limit_remaining = core['remaining']
|
self.gh_limit_remaining = core['remaining']
|
||||||
|
@ -454,9 +456,8 @@ class CommandHelper:
|
||||||
|
|
||||||
async def github_api_request(self,
|
async def github_api_request(self,
|
||||||
url: str,
|
url: str,
|
||||||
etag: Optional[str] = None,
|
|
||||||
is_init: Optional[bool] = False
|
is_init: Optional[bool] = False
|
||||||
) -> Optional[Dict[str, Any]]:
|
) -> JsonType:
|
||||||
if self.gh_limit_remaining == 0:
|
if self.gh_limit_remaining == 0:
|
||||||
curtime = time.time()
|
curtime = time.time()
|
||||||
assert self.gh_limit_reset_time is not None
|
assert self.gh_limit_reset_time is not None
|
||||||
|
@ -464,6 +465,13 @@ class CommandHelper:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"GitHub Rate Limit Reached\nRequest: {url}\n"
|
f"GitHub Rate Limit Reached\nRequest: {url}\n"
|
||||||
f"Limit Reset Time: {time.ctime(self.gh_limit_remaining)}")
|
f"Limit Reset Time: {time.ctime(self.gh_limit_remaining)}")
|
||||||
|
if url in self.github_request_cache:
|
||||||
|
cached_request = self.github_request_cache[url]
|
||||||
|
etag: Optional[str] = cached_request.get_etag()
|
||||||
|
else:
|
||||||
|
cached_request = CachedGithubResponse()
|
||||||
|
etag = None
|
||||||
|
self.github_request_cache[url] = cached_request
|
||||||
headers = {"Accept": "application/vnd.github.v3+json"}
|
headers = {"Accept": "application/vnd.github.v3+json"}
|
||||||
if etag is not None:
|
if etag is not None:
|
||||||
headers['If-None-Match'] = etag
|
headers['If-None-Match'] = etag
|
||||||
|
@ -498,7 +506,7 @@ class CommandHelper:
|
||||||
f"Forbidden GitHub Request: {resp.reason}")
|
f"Forbidden GitHub Request: {resp.reason}")
|
||||||
elif resp.code == 304:
|
elif resp.code == 304:
|
||||||
logging.info(f"Github Request not Modified: {url}")
|
logging.info(f"Github Request not Modified: {url}")
|
||||||
return None
|
return cached_request.get_cached_result()
|
||||||
if resp.code != 200:
|
if resp.code != 200:
|
||||||
retries -= 1
|
retries -= 1
|
||||||
if not retries:
|
if not retries:
|
||||||
|
@ -516,7 +524,8 @@ class CommandHelper:
|
||||||
self.gh_limit_reset_time = float(
|
self.gh_limit_reset_time = float(
|
||||||
resp.headers['X-Ratelimit-Reset'])
|
resp.headers['X-Ratelimit-Reset'])
|
||||||
decoded = json.loads(resp.body)
|
decoded = json.loads(resp.body)
|
||||||
decoded['etag'] = etag
|
if etag is not None:
|
||||||
|
cached_request.update_result(etag, decoded)
|
||||||
return decoded
|
return decoded
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
f"Retries exceeded for GitHub API request: {url}")
|
f"Retries exceeded for GitHub API request: {url}")
|
||||||
|
@ -559,12 +568,28 @@ class CommandHelper:
|
||||||
self.server.send_event(
|
self.server.send_event(
|
||||||
"update_manager:update_response", notification)
|
"update_manager:update_response", notification)
|
||||||
|
|
||||||
def get_system_update_command(self):
|
def get_system_update_command(self) -> str:
|
||||||
return APT_CMD
|
return APT_CMD
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
self.http_client.close()
|
self.http_client.close()
|
||||||
|
|
||||||
|
class CachedGithubResponse:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.etag: Optional[str] = None
|
||||||
|
self.cached_result: JsonType = {}
|
||||||
|
|
||||||
|
def get_etag(self) -> Optional[str]:
|
||||||
|
return self.etag
|
||||||
|
|
||||||
|
def get_cached_result(self) -> JsonType:
|
||||||
|
return self.cached_result
|
||||||
|
|
||||||
|
def update_result(self, etag: str, result: JsonType) -> None:
|
||||||
|
self.etag = etag
|
||||||
|
self.cached_result = result
|
||||||
|
|
||||||
|
|
||||||
class PackageDeploy(BaseDeploy):
|
class PackageDeploy(BaseDeploy):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
config: ConfigHelper,
|
config: ConfigHelper,
|
||||||
|
@ -644,7 +669,6 @@ class WebClientDeploy(BaseDeploy):
|
||||||
self.version: str = "?"
|
self.version: str = "?"
|
||||||
self.remote_version: str = "?"
|
self.remote_version: str = "?"
|
||||||
self.dl_url: str = "?"
|
self.dl_url: str = "?"
|
||||||
self.etag: Optional[str] = None
|
|
||||||
self.refresh_condition: Optional[Condition] = None
|
self.refresh_condition: Optional[Condition] = None
|
||||||
self._get_local_version()
|
self._get_local_version()
|
||||||
logging.info(f"\nInitializing Client Updater: '{self.name}',"
|
logging.info(f"\nInitializing Client Updater: '{self.name}',"
|
||||||
|
@ -674,15 +698,11 @@ class WebClientDeploy(BaseDeploy):
|
||||||
# Remote state
|
# Remote state
|
||||||
url = f"https://api.github.com/repos/{self.repo}/releases/latest"
|
url = f"https://api.github.com/repos/{self.repo}/releases/latest"
|
||||||
try:
|
try:
|
||||||
result = await self.cmd_helper.github_api_request(
|
result = await self.cmd_helper.github_api_request(url)
|
||||||
url, etag=self.etag)
|
assert isinstance(result, dict)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception(f"Client {self.repo}: Github Request Error")
|
logging.exception(f"Client {self.repo}: Github Request Error")
|
||||||
result = {}
|
result = {}
|
||||||
if result is None:
|
|
||||||
# No change, update not necessary
|
|
||||||
return
|
|
||||||
self.etag = result.get('etag', None)
|
|
||||||
self.remote_version = result.get('name', "?")
|
self.remote_version = result.get('name', "?")
|
||||||
release_assets: Dict[str, Any] = result.get('assets', [{}])[0]
|
release_assets: Dict[str, Any] = result.get('assets', [{}])[0]
|
||||||
self.dl_url = release_assets.get('browser_download_url', "?")
|
self.dl_url = release_assets.get('browser_download_url', "?")
|
||||||
|
|
Loading…
Reference in New Issue