-
Notifications
You must be signed in to change notification settings - Fork 2
/
check_update.py
111 lines (89 loc) · 3.6 KB
/
check_update.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env python
import json
from http.client import HTTPResponse
import sys
from types import ModuleType
from typing import Any, Dict, List, Optional, TypedDict
from urllib import request
import slack_bolt
import slack_sdk
from packaging.version import Version
import slack_cli_hooks.version
from slack_cli_hooks.error import PypiError
from slack_cli_hooks.protocol import Protocol, build_protocol
PROTOCOL: Protocol
DEPENDENCIES: List[ModuleType] = [slack_cli_hooks, slack_bolt, slack_sdk]
ErrorType = TypedDict("ErrorType", {"message": str})
OutputType = TypedDict(
"OutputType",
{"name": str, "url": str, "releases": List[Dict[str, Any]], "error": ErrorType},
total=False,
)
class Release:
def __init__(
self,
name: str,
current: Optional[Version] = None,
latest: Optional[Version] = None,
message: Optional[str] = None,
url: Optional[str] = None,
error: Optional[Dict[str, str]] = None,
):
self.name = name
if current and latest:
self.current = str(current)
self.latest = str(latest)
self.update = current < latest
self.breaking = (latest.major - current.major) > 0
if error:
self.error = error
if message:
self.message = message
if url:
self.url = url
def pypi_get(project: str, headers={"Accept": "application/json"}) -> HTTPResponse:
# Based on https://warehouse.pypa.io/api-reference/json.html
url = f"https://pypi.org/pypi/{project}/json"
pypi_request = request.Request(method="GET", url=url, headers=headers)
return request.urlopen(pypi_request)
def pypi_get_json(project: str) -> Dict[str, Any]:
pypi_response = pypi_get(project)
charset = pypi_response.headers.get_content_charset() or "utf-8"
raw_body = pypi_response.read().decode(charset)
if pypi_response.status > 200:
PROTOCOL.debug(f"Received status {pypi_response.status} from {pypi_response.url}")
PROTOCOL.debug(f"Headers {dict(pypi_response.getheaders())}")
PROTOCOL.debug(f"Body {raw_body}")
raise PypiError(f"Received status {pypi_response.status} from {pypi_response.url}")
return json.loads(raw_body)
def extract_latest_version(payload: Dict[str, Any]) -> str:
if "info" not in payload:
raise PypiError("Missing `info` field in pypi payload")
if "version" not in payload["info"]:
raise PypiError("Missing `version` field in pypi payload['info']")
return payload["info"]["version"]
def build_release(dependency: ModuleType) -> Release:
name = dependency.__name__
try:
pypi_json_payload = pypi_get_json(name)
return Release(
name=name,
current=Version(dependency.version.__version__),
latest=Version(extract_latest_version(pypi_json_payload)),
)
except PypiError as e:
return Release(name=name, error={"message": str(e)})
def build_output(dependencies: List[ModuleType] = DEPENDENCIES) -> OutputType:
output: OutputType = {"name": "Slack Bolt", "url": "https://api.slack.com/automation/changelog", "releases": []}
errors = []
for dep in dependencies:
release = build_release(dep)
output["releases"].append(vars(release))
if hasattr(release, "error"):
errors.append(release.name)
if errors:
output["error"] = {"message": f"An error occurred fetching updates for the following packages: {', '.join(errors)}"}
return output
if __name__ == "__main__":
PROTOCOL = build_protocol(argv=sys.argv)
PROTOCOL.respond(json.dumps(build_output()))