Skip to content

ApkPy 1.14.0

Released 2026-10-20. python -m pip install --upgrade apkpy==1.14.0.

Most of the Python an app is written with now reaches the phone, and Python that compiled but answered wrong there now answers what CPython answers. An app can sign in with Microsoft or any OpenID Connect provider. And the Previewer checks HTTPS certificates, as the phone does.

The Python you write every day

ApkPy translates Python rather than running it, so a construct either has a translation or stops the build at its line. 1.14.0 adds most of the constructs that still stopped it:

  • unpacking: a, b = parts, a, b = b, a, for key, value in d.items():, enumerate(), zip(), first, *rest = items, and a = b = 0;
  • changing an item: row["k"] = v, items[i] = v, row["k"] += 1, del items[0];
  • None, is None, a or b, not x, a comparison kept in a name, and 0 < x < 10;
  • list and dict methods: pop, insert, remove, sort, index, extend, clear, get(k, default), keys, values, items, and sorted() and reversed();
  • comprehensions inside a function, with any() and all();
  • text: lstrip, rstrip, zfill, capitalize, isalpha, ljust, center, text[::-1], splitlines, chr, ord, "-" * 10;
  • f-string widths and percentages ({n:05d}, {x:.0%}), .format() and % formatting;
  • default values, *args and **kwargs, a function inside a function, recursion, and a lambda kept in a name;
  • raise, try ... else, more than one except, and assert;
  • for ... else and while ... else, tuples as values, and isinstance();
  • print(), to Logcat on the phone, and bool(), divmod() and pow().

The test apps for these were run on a phone, and every label and every print() line was the same as CPython's.

A tuple shows with round brackets, (1, 2), and cannot change: changing one stops the build, as CPython raises there. The whole list, and what still stops the build (class, with, sets), is on Compatibility and limits.

def receipt(items):
    total = 0
    for name, price in items.items():
        total += price
    lines = [f"{name:<10}{price:>6.2f}" for name, price in items.items()]
    return "\n".join(lines), total


def show():
    text, total = receipt({"tea": 2.5, "cake": 3})
    note = "free delivery" if total > 5 else None
    out.set_value(text + "\n" + (note or ""))

Python that answered wrong on the phone

These compiled in 1.13.1 and did something on the phone that the Previewer, which runs CPython, did not:

  • n // 2 and n ** 2 gave nothing, and return a, b returned nothing;
  • len(x) + 1 joined text: "abc" gave 31;
  • a for over a dict held in a name, or over text, ran no times;
  • range(0, 10, 2) ignored its step;
  • items[1:] and items + ["d"] cut and joined the JSON as text, and "x" in d and items.count("a") searched it;
  • text[0] gave nothing, and slices of text could cut an emoji in half;
  • -7 % 3, a division by zero (Infinity), a whole number past 2^31 (it wrapped round), int(2.5) and round(-0.025, 2) disagreed with CPython;
  • a while ... else left its else out without a word;
  • f"{items}" showed the JSON, ["a","b"], where CPython shows ['a', 'b'];
  • greet("Ana") with def greet(name, greeting="Hello"), and global COUNT with COUNT += 1, passed ApkPy and failed in javac.

Each now answers what CPython answers: the generated Java was run against CPython on the same inputs, emoji and accents included.

Sign in with Microsoft, or any OpenID Connect provider

auth.login("microsoft", "YOUR_APPLICATION_CLIENT_ID",
           tenant="contoso.onmicrosoft.com", then=home)

auth.login(issuer="https://login.example.com", client_id="YOUR_CLIENT_ID", then=home)

tenant= is your Entra directory; issuer= reads Okta, Auth0, Keycloak or a company sign-in from its /.well-known/openid-configuration. auth.claims() is the person's ID token as a dict, a token close to expiring is renewed on its own, and a revoked session signs the app out. then= opens only after a sign-in that worked. See Sign-in, with how to register the app in the Entra admin center.

The flow was checked on a phone against a test provider that speaks the same protocol: PKCE, renewal, a session that survives a force-stop, a revoked session, and a forged answer refused. A real Entra tenant was not used.

Security

Socket reviews every release of every package on PyPI, and its review of 1.13.1 raised three points that were true. All three are fixed:

  • The Previewer checks HTTPS certificates, as the phone does. Audio, pictures, downloads and the city lookup switched the check off. A certificate that cannot be verified now fails the request, with the host and the reason. certifi is a new dependency, for a Python from python.org on macOS, which has no root certificates of its own.
  • apkpy run --qr serves the APK and nothing else. It served the APK's whole folder, usually the project, to the network while the QR was up.
  • apkpy setup checks what it downloads before unpacking it: the JDK against Adoptium's sha256, Gradle against its published sha256, and Google's command-line tools against Google's own package list. An archive entry that would land outside its folder stops the install too.

Upgrade

python -m pip install --upgrade apkpy==1.14.0, and build again. Nothing in an app has to change. Code that stopped the build in 1.13.1 may now build; code that built gives the same answers, except where the list above says it was wrong.

What stays different

Everything on the phone is text, and ApkPy decides from the source rather than from the running value, so a few things still answer differently from CPython: whole numbers are 64-bit, a tuple inside a list shows with square brackets, and a + b between two values read from a dict joins them when neither is known to be a number. The list is on Compatibility and limits.