Profile
Back to NewsBack
Dev.to 7 min
Reader Mode
Are You a Python Developer or a Python Expert? 7 Tests I Failed Building 3 Desktop Apps

Are You a Python Developer or a Python Expert? 7 Tests I Failed Building 3 Desktop Apps

18 hours ago

Real security bugfixes from desktop app builds

Description: Python security, screen capture, spring physics, HiDPI and PyInstaller .exe gotchas. 7 tests from shipping 3 no-telemetry Python apps for Windows (ATLOCK, ASNIPP, APIC).

Most Python developers can write a for loop, a Flask route and a pandas one-liner.

Python experts know what happens before line 1 runs, what Python can't do, and what breaks the moment your script leaves your laptop.

I found out which one I was while building three Windows desktop apps in pure Python: ATLOCK (a security suite), ASNIPP (a screenshot and screen recorder) and APIC (an image toolkit). No ads, no analytics SDKs, no bloat. Every test below is a real bug or design decision from one of them.

Score yourself honestly. 👇


Test 1: Do you know that import order can be a security decision? (ATLOCK)

Quick: what does import ctypes have to do with security?

On Windows, a DLL imported later can be loaded from the current directory. That is how DLL planting attacks work. The fix has to run before the other imports:

import sys, ctypes

if sys.platform == "win32":
    k32 = ctypes.WinDLL("kernel32", use_last_error=True)
    k32.SetDllDirectoryW.argtypes = [ctypes.c_wchar_p]
    k32.SetDllDirectoryW("")                      # drop the CWD from the DLL search path
    k32.SetDefaultDllDirectories.argtypes = [ctypes.c_uint32]
    k32.SetDefaultDllDirectories(0x1000)          # LOAD_LIBRARY_SEARCH_DEFAULT_DIRS

# only NOW import the heavy stuff
import cryptography, argon2

Developer: puts imports in alphabetical order.
Expert: knows import order is a runtime security boundary.

This is the very first thing that runs in ATLOCK, my pure-Python security suite.


Test 2: Can your screen recorder avoid recording itself? (ASNIPP)

I built a screen recorder with a floating control bar. First recording: the bar showed up in the video. 🤦

You don't need to hide and show windows. One Win32 call, straight from Python, does it:

import ctypes

def exclude_from_capture(widget):
    hwnd = int(widget.winId())
    # 0x11 = WDA_EXCLUDEFROMCAPTURE (Windows 10 2004+)
    ctypes.windll.user32.SetWindowDisplayAffinity(hwnd, 0x11)

The window stays visible to you and is invisible to every screen capture. That is how ASNIPP keeps its own toolbars, drawing overlay and recording border out of your videos.

Developer: crops the bar out in post.
Expert: knows ctypes gives Python the whole Windows API.


Test 3: Can your app decide what you meant, without asking? (APIC)

Good desktop software removes clicks. In APIC, my image toolkit, the rule is simple:

  • Drop one image → open it in the editor.
  • Drop many → open the batch converter.
def on_files(self, paths: list[str]):
    if not paths:
        paths, _ = QFileDialog.getOpenFileNames(
            self, "Open Image(s)", "",
            "Images (*.png *.jpg *.jpeg *.webp *.bmp *.tiff *.gif *.ico *.heic *.avif)")
    if len(paths) == 1:
        self.open_in_edit(paths[0])
    elif paths:
        self.open_in_convert(paths)

def dropEvent(self, event):
    self.on_files([u.toLocalFile() for u in event.mimeData().urls()])

The home screen, the file dialog and drag-and-drop all go through one function. Add a new entry point and you add zero logic.

Developer: writes three code paths.
Expert: writes one funnel and lets every input flow into it. The UI is split into one module per mode (edit, convert, compress, search), and main.py only wires them together.


Test 4: Do you know the difference between "encrypted" and "encrypted correctly"? (ATLOCK)

Developer: AES.new(key, AES.MODE_ECB) and done.

Expert checklist:

  • AEAD, not just encryption: AES-256-GCM authenticates every chunk.
  • Slow KDF for passwords: Argon2id, not a bare SHA-256.
  • Never reuse a nonce with the same key.
  • Bind the header with AAD so metadata can't be swapped.
import os
from argon2.low_level import hash_secret_raw, Type
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def seal(password: bytes, data: bytes) -> bytes:
    salt = os.urandom(16)
    key = hash_secret_raw(password, salt, time_cost=3, memory_cost=64 * 1024,
                          parallelism=4, hash_len=32, type=Type.ID)
    nonce = os.urandom(12)
    ct = AESGCM(key).encrypt(nonce, data, associated_data=b"v1")
    return salt + nonce + ct

(Simplified. ATLOCK's real container format also does chunking, key commitment and versioning.)


Test 5: Can you make a Qt animation feel alive with 6 lines of math? (ASNIPP)

ASNIPP's capture bar is a "Dynamic Island" that bounces open. Qt's built-in easing curves felt stiff, so I wrote a damped spring and plugged it into QEasingCurve:

import math
from PyQt6.QtCore import QEasingCurve

def spring(zeta, omega):
    wd = omega * math.sqrt(max(1e-6, 1 - zeta * zeta)) if zeta < 1 else 0.0
    def f(t):
        if t <= 0: return 0.0
        if t >= 1: return 1.0
        if zeta < 1:    # under-damped: overshoots, then settles (the bounce)
            return 1 - math.exp(-zeta * omega * t) * (math.cos(wd * t) + (zeta * omega / wd) * math.sin(wd * t))
        return 1 - math.exp(-omega * t) * (1 + omega * t)   # critically damped: no overshoot
    return f

BOUNCY = spring(0.66, 11.0)   # keep it at MODULE level (see below)

curve = QEasingCurve(QEasingCurve.Type.Linear)
curve.setCustomType(BOUNCY)

The trap: if the function is a temporary object, Python garbage-collects it while Qt still holds a pointer, and you get random crashes. Keep custom callables alive at module level. Only people who've been bitten know this one.


Test 6: Does your app look sharp on a 4K laptop with 150% scaling? (APIC)

Most Python GUI apps look blurry or oddly sized on modern displays. In Qt 6 the fix is one policy before you create the app:

from PyQt6.QtWidgets import QApplication
from PyQt6.QtCore import Qt

QApplication.setHighDpiScaleFactorRoundingPolicy(
    Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)   # no rounding: 125%/150% stay crisp

app = QApplication(sys.argv)
app.setApplicationName("APIC")
app.setOrganizationName("Akhouri Systems")

PassThrough keeps fractional scales like 125% and 150% exact instead of snapping to 100% or 200%. Add one try/except AttributeError for older Qt builds, and your app looks native everywhere.

Developer: tests on their own monitor.
Expert: tests at 100%, 125%, 150% and 200%.


Test 7: Can you explain why Python can't truly erase a password from memory? (ATLOCK)

password = "hunter2"
password = None   # is it gone? 🙂

No. Python str and bytes are immutable, and the interpreter is free to copy them. The best you can do is keep secrets in a mutable bytearray and wipe it explicitly:

buf = bytearray(b"hunter2")
# ... use it ...
for i in range(len(buf)):
    buf[i] = 0

An honest Python security tool says this out loud instead of promising magic. ATLOCK's source header lists what it can't do: memory wiping is best-effort, SSD shredding can't be guaranteed, and a same-user attacker can modify the script. Python experts document their limits.


Bonus: "Why does it work in VS Code and silently do nothing as a .exe?"

All three of my apps ship as Python compiled with PyInstaller (--onefile --windowed). It cost me an evening before I learned this: in a windowed build, sys.stdout and sys.stderr are None. Any print(), logging call or library traceback can fail silently, and your app "just doesn't open."

import sys, os

for name in ("stdout", "stderr"):
    if getattr(sys, name, None) is None:
        setattr(sys, name, open(os.devnull, "w", encoding="utf-8"))

# and show errors somewhere a human can see them:
import ctypes
ctypes.windll.user32.MessageBoxW(None, "Startup failed: ...", "App error", 0x10)

Try the code yourself

I'm the developer behind all of this, and the three apps are the proof that it works outside tutorials. They're built by Akhouri Systems, each is pure Python packaged as an .exe in a zip, with no ads and no analytics. The only network features are the ones you trigger yourself (for example ATLOCK's optional breach check, which sends only a 5-character hash prefix).

App Python lesson it proves What it is Get it
🔐 ATLOCK v5 Tests 1, 4, 7 Security suite: AES-256-GCM + Argon2id file guard, password vault, TOTP/FIDO2, anti-tamper Direct download (zip) · Microsoft Store
📸 ASNIPP v1 Tests 2, 5 Dynamic-Island-style screenshot, screen recorder, live drawing and editor Direct download (zip)
🖼️ APIC v1 Tests 3, 6 Image edit, convert, compress and search toolkit Direct download (zip)

If you're learning how Python behaves outside a notebook, run them and then ask what's going on under the hood. That's the fastest way I know to level up.


Your score

  • 0–2 tests known: You're a Python developer. Great start, now go break things.
  • 3–5: Solid. You've shipped something real.
  • 6–7: You're a Python expert. Come argue with me in the comments.

Which test did you fail first? Mine was the windowed-.exe one, and I lost an entire evening to it. Tell me yours below 👇

Chat with me