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, anda = 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, and0 < x < 10;- list and dict methods:
pop,insert,remove,sort,index,extend,clear,get(k, default),keys,values,items, andsorted()andreversed(); - comprehensions inside a function, with
any()andall(); - 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,
*argsand**kwargs, a function inside a function, recursion, and alambdakept in a name; raise,try ... else, more than oneexcept, andassert;for ... elseandwhile ... else, tuples as values, andisinstance();print(), to Logcat on the phone, andbool(),divmod()andpow().
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 // 2andn ** 2gave nothing, andreturn a, breturned nothing;len(x) + 1joined text:"abc"gave31;- a
forover a dict held in a name, or over text, ran no times; range(0, 10, 2)ignored its step;items[1:]anditems + ["d"]cut and joined the JSON as text, and"x" in danditems.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)andround(-0.025, 2)disagreed with CPython;- a
while ... elseleft itselseout without a word; f"{items}"showed the JSON,["a","b"], where CPython shows['a', 'b'];greet("Ana")withdef greet(name, greeting="Hello"), andglobal COUNTwithCOUNT += 1, passed ApkPy and failed injavac.
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.
certifiis a new dependency, for a Python from python.org on macOS, which has no root certificates of its own. apkpy run --qrserves the APK and nothing else. It served the APK's whole folder, usually the project, to the network while the QR was up.apkpy setupchecks 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.