Requests the latest release info from the TinyPilot Gatekeeper REST API. Returns: An UpdateInfo object, containing the information about the release. Raises: VersionRequestError: If an error occurred while making an HTTP request to the Gatekeeper API.
()
| 73 | |
| 74 | |
| 75 | def latest_version(): |
| 76 | """Requests the latest release info from the TinyPilot Gatekeeper REST API. |
| 77 | |
| 78 | Returns: |
| 79 | An UpdateInfo object, containing the information about the release. |
| 80 | |
| 81 | Raises: |
| 82 | VersionRequestError: If an error occurred while making an HTTP request |
| 83 | to the Gatekeeper API. |
| 84 | """ |
| 85 | # The URL is trusted because it's not based on user input. |
| 86 | url = f'{env.GATEKEEPER_BASE_URL}/community/available-update' |
| 87 | try: |
| 88 | # pylint: disable=line-too-long |
| 89 | with urllib.request.urlopen( # noqa: S310 # nosemgrep: dynamic-urllib-use-detected |
| 90 | url, |
| 91 | timeout=10) as response: |
| 92 | response_bytes = response.read() |
| 93 | except urllib.error.HTTPError as e: |
| 94 | message = e.fp.read().decode('utf-8').strip() |
| 95 | raise VersionRequestError( |
| 96 | f'Failed to request latest available version: {message}') from e |
| 97 | except urllib.error.URLError as e: |
| 98 | if (isinstance(e.reason, ssl.SSLCertVerificationError) and |
| 99 | e.reason.verify_message == 'certificate is not yet valid'): |
| 100 | raise CertificateNotYetValidError( |
| 101 | 'Server\'s certificate start date is ahead of TinyPilot\'s' |
| 102 | f' system date of {date.today():%Y-%m-%d}. Check the system' |
| 103 | f' date to ensure that it is accurate. {e}') from e |
| 104 | raise VersionRequestError( |
| 105 | f'Failed to request latest available version: {e}') from e |
| 106 | |
| 107 | try: |
| 108 | response_text = response_bytes.decode('utf-8') |
| 109 | except UnicodeDecodeError as e: |
| 110 | raise VersionRequestError( |
| 111 | 'Failed to decode latest available version response body as UTF-8' |
| 112 | ' characters.') from e |
| 113 | |
| 114 | try: |
| 115 | response_dict = json.loads(response_text) |
| 116 | except json.decoder.JSONDecodeError as e: |
| 117 | raise VersionRequestError( |
| 118 | 'Failed to decode latest available version response body as JSON.' |
| 119 | ) from e |
| 120 | |
| 121 | if not isinstance(response_dict, dict): |
| 122 | raise VersionRequestError( |
| 123 | 'Failed to decode latest available version response body as a JSON' |
| 124 | ' dictionary.') |
| 125 | |
| 126 | if not all(key in response_dict for key in ('version', 'kind', 'data')): |
| 127 | raise VersionRequestError( |
| 128 | 'Failed to get latest available version because of an incompatible' |
| 129 | ' response structure. Expected fields: version, kind, data. Got: ' |
| 130 | '' + ', '.join([*response_dict]) + '.') |
| 131 | |
| 132 | return UpdateInfo(version=response_dict['version'], |
nothing calls this directly
no test coverage detected