visualdynamics.update¶
update
¶
Check whether a newer version of Visual Dynamics is available.
This module checks; it does not update. It reads one small JSON file, the manifest, from one URL, and the app reports the answer in its status line (0.1.0 is available), with a dialog when a newer version exists. Nothing is downloaded or replaced, and nothing runs with privileges.
The request is made only when someone asks for it: in the app, File >
Check for Updates, and never at startup; from a script, a call to
attempt, fetch or check. The only information it sends about
the program is a User-Agent header, VisualDynamics/ followed by the
version number. Like any web request it reaches the host from the
machine's IP address, and the program keeps no record of the check.
This is all the updating there is except in the packaged macOS app. An
updater has to verify what it downloaded before running it, or anyone
who can change the download can run code on the machine. The macOS app
updates itself through Sparkle (gui/updater.py): every update is
signed with an EdDSA key held only by the release process, and the app
refuses anything that does not verify against SPARKLE_PUBLIC_KEY.
Windows will get WinSparkle once its installer is signed, and a pip
install updates with pip (upgrade_command).
The manifest is served from visualdynamics.org, a static host. The
release workflow writes it from each published release as
web/launch/latest.json and deploys it with the site. Its shape:
{"version": "0.1.0", "url": "https://.../releases", "notes": "..."}
Functions:
| Name | Description |
|---|---|
parse_version |
A version string as a tuple to compare: '0.10.2' -> (0, 10, 2, 3, 0). |
newer |
Whether |
trust_store |
The TLS context the check verifies visualdynamics.org with: the |
reason_for |
Why the manifest could not be read, in one sentence for a status |
attempt |
Read the manifest, and say why not when it cannot be read. |
fetch |
The manifest as published, or None when it cannot be read. |
check |
The manifest when it offers a newer version, else None. |
upgrade_command |
The command that updates this copy, when it is a pip install. |
Functions:¶
parse_version
¶
A version string as a tuple to compare: '0.10.2' -> (0, 10, 2, 3, 0).
The parts are numbers, so 0.10 sorts above 0.9; compared as strings they would sort the other way and offer a downgrade as an upgrade. A leading 'v' is ignored, and a part that cannot be read becomes zero instead of raising, so a malformed manifest means no update is offered.
The last two entries are the stage and its number. A pre-release sorts below its release: '0.1.0a1' is (0, 1, 0, 0, 1) and '0.1.0' is (0, 1, 0, 3, 0). The stage is 0 for a (alpha), 1 for b (beta), 2 for rc (release candidate), spelled as Python spells them, and 3 for a release.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The version, as the manifest or |
required |
Returns:
| Type | Description |
|---|---|
tuple of int
|
Major, minor and patch (missing parts are zero), then the stage and its number. |
Source code in src/visualdynamics/update.py
newer
¶
Whether available is a later version than running, which
defaults to the version running now. Compared by parse_version.
Source code in src/visualdynamics/update.py
trust_store
¶
The TLS context the check verifies visualdynamics.org with: the certificates the operating system trusts.
With truststore installed (MIT licensed, and what pip uses), the
context verifies against the system's own store: the Keychain on
macOS, the certificate store on Windows, OpenSSL's system bundle on
Linux. A network that inspects HTTPS re-signs every site with its
own certificate authority, which IT installs in that store, so the
check works on such a network wherever the browser and curl do.
Without truststore, the context is Python's default one. When that
holds no certificates, the bundle certifi ships is loaded into it,
and when certifi is not installed either, the fetch fails with a
certificate error.
Source code in src/visualdynamics/update.py
reason_for
¶
Why the manifest could not be read, in one sentence for a status line.
The sentence names the cause instead of the exception: an HTTP
status, a certificate that could not be verified, no answer within
timeout, an answer that is not a readable manifest, or a host that
could not be reached. A proxy, a filtered domain, an unverifiable
certificate, a slow network and a site that is down cannot be told
apart from outside the program, so the message is what tells them
apart.
urlopen wraps a refused connection, a timeout and a certificate
that will not verify in a URLError whose reason is the real
exception, so that is unwrapped first. HTTPError is a URLError
too, and is read before unwrapping because its status is the news.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
error
|
BaseException
|
What the fetch raised. |
required |
timeout
|
float
|
The timeout it was given, in seconds, for the message. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The reason, in words. |
Source code in src/visualdynamics/update.py
attempt
¶
Read the manifest, and say why not when it cannot be read.
This is what File > Check for Updates calls, and fetch is built on
it. It reads at most 64 kB, and every failure is returned as a
reason (reason_for), never raised, so a person waiting on the menu
item is told which thing went wrong.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Where the manifest is; |
MANIFEST
|
timeout
|
float
|
How long to wait, in seconds; |
TIMEOUT
|
Returns:
| Type | Description |
|---|---|
tuple
|
|
Source code in src/visualdynamics/update.py
fetch
¶
The manifest as published, or None when it cannot be read.
The same request as attempt, without the reason. Use attempt
where "up to date" and "could not ask" need different words, as
they do in the app's menu item.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Where the manifest is; |
MANIFEST
|
timeout
|
float
|
How long to wait, in seconds; |
TIMEOUT
|
Returns:
| Type | Description |
|---|---|
dict or None
|
The manifest, or None. |
Source code in src/visualdynamics/update.py
check
¶
The manifest when it offers a newer version, else None.
For a script that only wants to know whether to act. Every failure
is None as well: no network, a captive portal, a 404, a truncated
file, a version that does not parse. The request blocks for up to
timeout seconds, so a caller with a window to keep responsive runs
it off the main thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Where the manifest is; |
MANIFEST
|
timeout
|
float
|
How long to wait, in seconds; |
TIMEOUT
|
Returns:
| Type | Description |
|---|---|
dict or None
|
The manifest, when its version is later than the one running. |
Source code in src/visualdynamics/update.py
upgrade_command
¶
The command that updates this copy, when it is a pip install.
The command is spelled with this interpreter's own path, so it
updates this environment and not whichever pip the shell finds
first. The app shows it with a button that copies it; it never runs
it.
Returns:
| Type | Description |
|---|---|
str or None
|
|