PMBlue updater
Download and maintain individual Python modules from https://update.pmblue.us/. The updater shares one bulk version response between module checks, validates downloaded Python, and replaces files atomically. Signed downloads can be required by configuring a trusted public key.
pmblue_update calls self_update() for the updater itself. It may contact the server, write _update_cache.json in the current working directory, and replace its own source file. The newly downloaded updater takes effect on a subsequent import in a new process or an explicit reload.Installation and checking several modules
The client requires requests; all other imported libraries and verification primitives are available in Python 3.7. Use a requests release compatible with your Python interpreter. Signature verification does not require cryptography on clients. Obtain the updater from your trusted deployment and make it importable alongside the modules you maintain.
import pmblue_update
# Updates files in this directory without importing those modules.
for name in ("toolbox", "dialpad", "aiosqliteObj"):
pmblue_update.update_str(name, path="modules", adv_info=True)
Use update_str when preparing files for another process. Use update(module) when you already hold an imported module and want it reloaded after a download. Use import_mod for a module that may need its first download; its path must also be importable by Python.
import pmblue_update
import toolbox
pmblue_update.update(toolbox, adv_info=True)
# Inside a module that declares __version__:
pmblue_update.self_update()
Bulk versions and caches
remote_version(name, timeout=3) requests /update/versions once, caches the complete mapping in memory for REFRESH_TIME (30 seconds), and returns a Version object for the requested module. A signed client uses /update/manifest instead, obtaining all versions, hashes, and signatures together. Downloads remain one request per changed module.
# Example /update/versions response; values follow installed server files.
{
"pmblue_update": "0.3.6",
"toolbox": "1.5.1.1",
"dialpad": "1.3",
"normalize": null
}
The unsigned client falls back to /update/{name}/version when the bulk endpoint returns 404. It remembers that fallback for the process lifetime. Missing modules and modules without a declared version cause LookupError through the bulk lookup. In legacy fallback, a missing module's HTTP 404 raises requests.HTTPError before the response text is checked.
self_update, update, and update_str also consult a disk cache of per-module check times. CACHE=False disables this disk cache, but not the shared in-memory version response. update_str(refresh_time=...) overrides only the disk interval. Disk timestamps are currently written before network checks, so failed requests can suppress another check until that interval passes.
Installation and rollback boundaries
- The download must have a successful HTTP status and compile as Python.
- When a trusted key is configured, the content hash and Ed25519 signature must match the manifest.
- The updater writes a temporary file in the destination directory, flushes and synchronizes it, preserves an existing file's permission bits, then replaces the destination using
os.replace. updatereloads the imported module;import_modimports it. If that operation raises an ordinary exception, the previous source bytes are restored, or a newly created file is removed. The reload rollback does not catchSystemExitorKeyboardInterrupt.
The transaction covers the source file. It cannot undo side effects already performed by imported code or fully restore a partially mutated in-memory module. self_update and update_str do not import the downloaded code. import_mod uses importlib.import_module, which can return an already loaded module without reloading it. Atomic replacement does not provide a transaction across several modules.
Optional Ed25519 verification
Before starting a signed client, configure PMBLUE_TRUSTED_PUBLIC_KEY with the base64-encoded 32-byte Ed25519 public key supplied by your server administrator. Public keys must be distributed through a trusted channel. The server must already be configured to sign manifests.
# Shell configuration, before importing pmblue_update:
export PMBLUE_TRUSTED_PUBLIC_KEY='<base64 public key>'
The client checks the SHA-256 digest of the exact downloaded bytes and verifies a signature over this UTF-8 message (with no final newline):
pmblue-module-v1
<module name>
<module version>
<lowercase SHA-256 hex digest>
The verifier uses integer arithmetic and hashlib.sha512, validates canonical encodings and the public key subgroup, and accepts the same keys and signatures as the server's Ed25519 implementation. When a key is configured, a missing manifest or invalid signature prevents installation; there is no automatic downgrade to an unsigned download. Without a configured key, legacy HTTPS downloads remain available. See server signing setup for key generation.
Publishing a module
Declare a numeric dotted __version__ and call self_update(primary_mod=True) from that module. If the bulk lookup reports the module absent, the updater offers to add it. A missing module on an older server's legacy endpoint instead follows the connection-error path and does not show that prompt. If its local version is newer, it offers to replace the server copy. It asks for confirmation and the server upload password, then posts base64 source bytes. Running the updater file directly also uses this publisher mode.
The server changes the exact bytes primary_mod=True to primary_mod=False in distributed downloads. It also applies this transformation before calculating hashes and signatures. The server's separate top-level updater copy may differ from the published modules/pmblue_update.py.
Choosing an API
| Function | Behavior and result |
|---|---|
self_update(...) | Checks the calling file; returns True after replacement, False on many no-update/error paths, and None on a cache hit or successful add. Does not reload the caller. |
update(mod, ...) | Checks and reloads an imported module. Returns the module after a download or handled connection failure; returns None on a cache hit and normally when current with adv_info=False. |
update_str(name, path="", ...) | Checks and replaces a file without importing it. Returns None. Supports a disk-cache refresh_time override. |
import_mod(name, path="", ...) | Downloads if absent, then imports by dotted path. Include a trailing separator for a nonempty path; use paths inside an importable package. It can return False on a version-request connection failure. |
update_check(mod, ...) | Compares the bulk server version to mod.__version__; returns a boolean. Uses the full mod.__name__, unlike update, which uses its last component. |
update_reload(mod, ...) | Checks disk version and reloads locally; makes no direct version request. The current string-versus-Version comparison can trigger a reload even for the same version. |
maintain / maintenance | Convenience wrappers that print errors. For existing files, maintain currently omits path when it calls update_str; use the explicit update_str loop above for another directory. |
get_version / Version | Reads __version__ without importing a file; parses up to four numeric components. This is not a general PEP 440 or semantic-version parser. |
remote_version / install_module | Lower-level helpers. A signed installation needs metadata previously fetched through remote_version. install_module returns previous source bytes or None. |
Objects, parameters, and results
Version: a parsed local or remote version
Version(value) converts its input to text, extracts a numeric dotted value, and records up to four components. Instances are returned by get_version and remote_version. Construct them yourself when comparing a local declaration to a server result. Use strings for input: a float such as 1.10 has already lost the distinction between 1.10 and 1.1.
| Member | Meaning and use |
|---|---|
raw | The extracted numeric text, not the original unprocessed input. For a conventional quoted declaration, quotes and surrounding text are removed. |
major, minor, patch, extra | Integer components. Omitted components are None; an empty numeric extraction uses a major component of zero. |
short | Legacy major/minor representation, often a float. It cannot represent all dotted versions faithfully; prefer the component fields for display and avoid using it as a release identifier. |
str(version) | Joins the stored components as dotted text. repr(version) produces a debugging form such as <Version 1.2.3>. |
left < right, left > right, left == right | Use two Version objects. Equality compares their dotted text, so Version("1.2") and Version("1.2.0") are not equal. The legacy comparison implementation has additional ordering edge cases; see compatibility notes below. |
from pmblue_update import Version, get_version
release = Version("1.2.3")
print(release.major, release.minor, release.patch, release.extra)
# 1 2 3 None
print(str(release)) # 1.2.3
print(release < Version("1.2.4")) # True for this comparison
local = get_version("toolbox.py", path="modules")
if local is not None:
print("Local toolbox:", str(local))
Downloaded response and manifest records
install_module(name, path, response) expects a response object with raise_for_status() and byte-valued content; a normal requests.Response provides these. It returns the old file's bytes, or None if no previous file existed. The name selects signed metadata, while path is the actual destination file, including .py.
A manifest record is a dictionary containing version, sha256, and, when signed, signature. The updater manages the cached records internally. Fetch through remote_version before a low-level signed installation; a response object alone does not supply trusted metadata.
Common function options
| Option | Meaning |
|---|---|
adv_info | Print extra progress information. It also affects an existing return-value branch in update, so retain your original module reference instead of always assigning its result. |
ignore_err | Suppresses errors on documented wrapper paths. It does not bypass HTTP, Python syntax, checksum, or signature validation before replacement. |
timeout | Version-request timeout passed to requests. Most module downloads use timeout + 2; the update branch of import_mod still uses 5 seconds, and publisher POST calls do not set a timeout. |
primary_mod | Enables interactive upload prompts in self_update. Leave false for unattended consumer processes. |
path | A directory for file-based helpers. update_str normalizes a trailing separator; import_mod also uses the path to derive an import name and currently needs a trailing separator in nonempty input. |
refresh_time | update_str's per-module disk-cache interval. It does not alter the 30-second shared server-version cache. |
Worked examples
Inspect installed versions without updating those modules
This compares source declarations to server metadata. It does not import or install toolbox or Dialpad. Importing the updater itself still performs its normal startup check.
from pathlib import Path
import requests
import pmblue_update
for name in ("toolbox", "dialpad"):
local = pmblue_update.get_version(name + ".py", path="modules")
try:
remote = pmblue_update.remote_version(name, timeout=5)
except (requests.RequestException, LookupError, ValueError) as error:
print(name, "could not be checked:", error)
continue
print(name, "installed:", str(local) if local else "not versioned",
"available:", str(remote))
# The two remote_version calls normally share one bulk response.
Prepare a directory and report each module separately
Use explicit update_str calls when the destination is another directory. They avoid the current path-handling limitation in maintain. Inspect the file after each call because connection errors can be handled internally without raising.
from pathlib import Path
import pmblue_update
destination = Path("modules")
destination.mkdir(exist_ok=True)
for name in ("toolbox", "aiosqliteObj"):
try:
pmblue_update.update_str(
name, path=str(destination), adv_info=True,
timeout=5, refresh_time=0,
)
installed = pmblue_update.get_version(name + ".py", str(destination))
print(name, "file version:", str(installed) if installed else "unavailable")
except Exception as error:
print(name, "installation was not completed:", error)
Perform a verified low-level installation
Set PMBLUE_TRUSTED_PUBLIC_KEY in the process environment before startup. This example requires that setting, fetches signed metadata, downloads the module, and installs it without executing the downloaded module. Validation failures propagate and leave the existing file in place.
import os
from pathlib import Path
import requests
import pmblue_update
if not os.environ.get("PMBLUE_TRUSTED_PUBLIC_KEY"):
raise RuntimeError("Configure a trusted public key before installing")
name = "toolbox"
available = pmblue_update.remote_version(name, timeout=5)
download = requests.get(
pmblue_update.URL + "update/" + name + "/module", timeout=10,
)
destination = Path("modules")
destination.mkdir(exist_ok=True)
previous = pmblue_update.install_module(
name, str(destination / (name + ".py")), download,
)
print("Installed", str(available), "replaced existing file:", previous is not None)
Compatibility and current limits
- Existing public update function signatures and legacy server endpoints remain available.
- Version parsing strips leading text and stops at nonnumeric content. The comparison implementation has legacy edge cases around multi-digit minor components and differing component counts; avoid assuming packaging-standard ordering.
- Several wrappers re-raise a generic
Exception;ignore_err=Truecan suppress errors while preserving the previous installed file. It does not permit installing an invalid signed file. - A server publish between a cached manifest and a download can produce a hash mismatch. The existing file is kept; retry after the 30-second manifest cache expires.
- Python 3.7-compatible syntax and APIs are used, but an actual Python 3.7 runtime was not available for the latest local verification.
Server routes, signing setup, and deployment · All guides
Complete source API
Generated from modules/pmblue_update.py; version 0.3.6. Includes public functions, classes, directly declared methods, properties, and Python protocol methods. Conditional APIs may require optional dependencies. Inherited members and dynamically assigned attributes are explained in the guide where relevant.
Signatures and docstrings are extracted without importing or running the module. A property or AsyncProperty decorator changes how a member is accessed; see its decorators and the guide.
Classes and objects
Module functions
install_moduleremote_versionget_versioncurrent_moduleself_updateupdateimport_modupdate_checkupdate_reloadupdate_strmaintainmaintenance
install_module(name, path, response)
Source line 138
Verify a download before replacing the installed Python file.
| Parameter | Passing convention | Default / required |
|---|---|---|
name | positional or keyword | required |
path | positional or keyword | required |
response | positional or keyword | required |
remote_version(name, timeout=3)
Source line 165
Get a module version, sharing one server response across checks.
| Parameter | Passing convention | Default / required |
|---|---|---|
name | positional or keyword | required |
timeout | positional or keyword | 3 |
class Version
Source line 209
Construct: Version(v_str)
Fields assigned by the constructor: extra, major, minor, patch, raw, short. Some assignments may be conditional; see the object guide for meaning and lifecycle.
Declared functions, properties, and nested objects:
Version.__init__— methodVersion.__lt__— methodVersion.__gt__— methodVersion.__eq__— methodVersion.__str__— methodVersion.__repr__— method
Version.__init__(self, v_str)
Source line 210
| Parameter | Passing convention | Default / required |
|---|---|---|
v_str | positional or keyword | required |
Version.__lt__(self, other)
Source line 255
| Parameter | Passing convention | Default / required |
|---|---|---|
other | positional or keyword | required |
Version.__gt__(self, other)
Source line 281
| Parameter | Passing convention | Default / required |
|---|---|---|
other | positional or keyword | required |
Version.__eq__(self, other: object) -> bool
Source line 283
| Parameter | Passing convention | Default / required |
|---|---|---|
other: object | positional or keyword | required |
Return annotation: bool.
Version.__str__(self)
Source line 288
No caller-supplied parameters are declared.
Version.__repr__(self)
Source line 290
No caller-supplied parameters are declared.
get_version(mod_name, path='')
Source line 310
| Parameter | Passing convention | Default / required |
|---|---|---|
mod_name | positional or keyword | required |
path | positional or keyword | '' |
current_module()
Source line 330
No caller-supplied parameters are declared.
self_update(adv_info=False, ignore_err=False, timeout=3, primary_mod=False)
Source line 333
| Parameter | Passing convention | Default / required |
|---|---|---|
adv_info | positional or keyword | False |
ignore_err | positional or keyword | False |
timeout | positional or keyword | 3 |
primary_mod | positional or keyword | False |
update(mod, adv_info=False, ignore_err=False, timeout=3, extra='')
Source line 425
| Parameter | Passing convention | Default / required |
|---|---|---|
mod | positional or keyword | required |
adv_info | positional or keyword | False |
ignore_err | positional or keyword | False |
timeout | positional or keyword | 3 |
extra | positional or keyword | '' |
import_mod(mod_name, path='', adv_info=False, info=True, ignore_err=False, timeout=3)
Source line 496
| Parameter | Passing convention | Default / required |
|---|---|---|
mod_name | positional or keyword | required |
path | positional or keyword | '' |
adv_info | positional or keyword | False |
info | positional or keyword | True |
ignore_err | positional or keyword | False |
timeout | positional or keyword | 3 |
update_check(mod, ignore_err=False, timeout=3)
Source line 606
| Parameter | Passing convention | Default / required |
|---|---|---|
mod | positional or keyword | required |
ignore_err | positional or keyword | False |
timeout | positional or keyword | 3 |
update_reload(mod, info=True, ignore_err=False)
Source line 629
| Parameter | Passing convention | Default / required |
|---|---|---|
mod | positional or keyword | required |
info | positional or keyword | True |
ignore_err | positional or keyword | False |
update_str(mod_name, path='', adv_info=False, ignore_err=False, timeout=3, refresh_time=None)
Source line 672
| Parameter | Passing convention | Default / required |
|---|---|---|
mod_name | positional or keyword | required |
path | positional or keyword | '' |
adv_info | positional or keyword | False |
ignore_err | positional or keyword | False |
timeout | positional or keyword | 3 |
refresh_time | positional or keyword | None |
maintain(mod_name, path='', adv_info=False)
Source line 739
| Parameter | Passing convention | Default / required |
|---|---|---|
mod_name | positional or keyword | required |
path | positional or keyword | '' |
adv_info | positional or keyword | False |
maintenance(*mod_names, path='', adv_info=False)
Source line 763
| Parameter | Passing convention | Default / required |
|---|---|---|
mod_names | extra positional arguments (*args) | optional collection |
path | keyword only | '' |
adv_info | keyword only | False |