Skip to content

jmcore.version

jmcore.version

Centralized version management for JoinMarket NG.

This is the single source of truth for the project version. All components inherit their version from here.

Attributes

GITHUB_RELEASES_URL = 'https://api.github.com/repos/joinmarket-ng/joinmarket-ng/releases/latest' module-attribute

VERSION = __version__ module-attribute

__version__ = '0.19.3' module-attribute

logger = logging.getLogger(__name__) module-attribute

Classes

UpdateCheckResult dataclass

Result of a GitHub update check.

Source code in jmcore/src/jmcore/version.py
61
62
63
64
65
66
@dataclass(frozen=True)
class UpdateCheckResult:
    """Result of a GitHub update check."""

    latest_version: str
    is_newer: bool
Attributes
is_newer: bool instance-attribute
latest_version: str instance-attribute

Functions

check_for_updates_from_github(socks_proxy: str | None = None, timeout: float = 30.0) -> UpdateCheckResult | None async

Check GitHub for the latest release and compare with the local version.

This function makes an HTTP request to the GitHub API. When socks_proxy is provided, the request is routed through the given SOCKS5 proxy (e.g. Tor).

Privacy note: This contacts GitHub and reveals your IP (or Tor exit node). Only call this when the user has explicitly opted in via check_for_updates.

Args: socks_proxy: Optional SOCKS5 proxy URL (e.g. "socks5h://127.0.0.1:9050"). timeout: HTTP request timeout in seconds.

Returns: UpdateCheckResult with the latest version and whether it is newer, or None if the check failed for any reason.

Source code in jmcore/src/jmcore/version.py
 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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
async def check_for_updates_from_github(
    socks_proxy: str | None = None,
    timeout: float = 30.0,
) -> UpdateCheckResult | None:
    """Check GitHub for the latest release and compare with the local version.

    This function makes an HTTP request to the GitHub API. When socks_proxy is
    provided, the request is routed through the given SOCKS5 proxy (e.g. Tor).

    **Privacy note**: This contacts GitHub and reveals your IP (or Tor exit node).
    Only call this when the user has explicitly opted in via ``check_for_updates``.

    Args:
        socks_proxy: Optional SOCKS5 proxy URL (e.g. "socks5h://127.0.0.1:9050").
        timeout: HTTP request timeout in seconds.

    Returns:
        UpdateCheckResult with the latest version and whether it is newer,
        or None if the check failed for any reason.
    """
    import httpx

    client_kwargs: dict[str, Any] = {}
    if socks_proxy:
        try:
            from httpx_socks import AsyncProxyTransport

            from jmcore.tor_isolation import normalize_proxy_url

            # python-socks does not support the socks5h:// scheme directly.
            # normalize_proxy_url converts socks5h:// -> socks5:// + rdns=True
            # so that .onion addresses are resolved by Tor.
            normalized = normalize_proxy_url(socks_proxy)

            transport = AsyncProxyTransport.from_url(normalized.url, rdns=normalized.rdns)
            client_kwargs["transport"] = transport
            logger.debug(
                "Update check configured with SOCKS proxy: %s (rdns=%s)",
                socks_proxy,
                normalized.rdns,
            )
        except ImportError:
            logger.warning("httpx-socks not available, update check without proxy")
        except Exception:
            logger.warning("Failed to configure SOCKS proxy for update check", exc_info=True)

    try:
        async with httpx.AsyncClient(
            timeout=timeout,
            follow_redirects=True,
            **client_kwargs,
        ) as client:
            response = await client.get(
                GITHUB_RELEASES_URL,
                headers={"Accept": "application/vnd.github+json"},
            )
            response.raise_for_status()

        data = response.json()
        tag_name: str = data["tag_name"]
        latest = _parse_version_tag(tag_name)
        current = get_version_tuple()
        latest_str = f"{latest[0]}.{latest[1]}.{latest[2]}"

        logger.debug("Update check: current=%s, latest=%s", __version__, latest_str)
        return UpdateCheckResult(latest_version=latest_str, is_newer=latest > current)

    except Exception:
        logger.warning("Failed to check for updates from GitHub", exc_info=True)
        return None

get_version() -> str

Return the current version string.

Source code in jmcore/src/jmcore/version.py
26
27
28
def get_version() -> str:
    """Return the current version string."""
    return __version__

get_version_info() -> dict[str, str | int]

Return version information as a dictionary.

Source code in jmcore/src/jmcore/version.py
37
38
39
40
41
42
43
44
45
def get_version_info() -> dict[str, str | int]:
    """Return version information as a dictionary."""
    major, minor, patch = get_version_tuple()
    return {
        "version": __version__,
        "major": major,
        "minor": minor,
        "patch": patch,
    }

get_version_tuple() -> tuple[int, int, int]

Return the version as a tuple of (major, minor, patch).

Source code in jmcore/src/jmcore/version.py
31
32
33
34
def get_version_tuple() -> tuple[int, int, int]:
    """Return the version as a tuple of (major, minor, patch)."""
    parts = __version__.split(".")
    return (int(parts[0]), int(parts[1]), int(parts[2]))