Skip to main content

pyi-doctor

PyPI Python Downloads License: MIT

한국어 문서 (Korean)

Your PyInstaller exe works on your PC, then Windows Defender calls it a trojan on someone else's. pyi-doctor checks why.

In one call it builds a portable exe and a setup wizard, test-starts the build outside your Python environment, scores common antivirus false-positive risk factors, and lists the third-party licenses you are redistributing.

pip install pyi-doctor
pyi-doctor diagnose MyApp.exe
from pyi_doctor import Project, build_all

project = Project(entry="main.py", name="MyApp", version="1.0.0", publisher="Me")
report = build_all(project, log=print)
print(report.ok)
dist/release/
├── MyApp-1.0.0-portable-win64.zip   # onedir build (+ THIRD_PARTY_LICENSES.txt)
├── MyApp-1.0.0-win64.exe            # single-file build (if onefile = true)
├── MyApp-1.0.0-setup.exe            # Inno Setup wizard (Windows + Inno Setup)
├── MyApp-1.0.0.cdx.json             # CycloneDX 1.5 SBOM
├── THIRD_PARTY_LICENSES.txt
├── SHA256SUMS.txt
├── av-submission-notes.txt          # text for false-positive submission forms
├── report.md / report.json

What it does

Step What happens
Portable build PyInstaller onedir build (default) with --noupx, a Windows version resource and your icon. Optional onefile build.
Installer Writes an Inno Setup script (fixed AppId for upgrades, per-user install by default, modern wizard, Start menu / optional desktop shortcut, uninstaller, English + Korean and other languages) and compiles it when Inno Setup 6.3+ is installed.
Smoke test Copies each build to a temp folder and starts it with Python's environment variables and folders removed from the environment. Reports missing modules, data files and DLL errors with hints (e.g. "add 'x' to hidden_imports").
Antivirus diagnosis Reads the PE file (pure Python) and scores commonly cited risk factors: UPX sections, onefile, missing version resource, no signature, prebuilt bootloader, default icon. On Windows it can also scan with the local Microsoft Defender CLI (no remediation).
Licenses + SBOM Maps bundled modules (from PyInstaller's TOC files) to installed distributions and writes THIRD_PARTY_LICENSES.txt (also placed next to the exe and inside the installer) and a CycloneDX SBOM.
Release folder Collects the files, writes SHA256SUMS.txt, a report and false-positive submission notes. Optional upload to a GitHub release through the GitHub CLI (gh).

Only the onedir build is required to succeed; every other step records its own status, so a missing tool (for example Inno Setup) does not stop the rest.

Tools such as briefcase and pyship focus on shipping an app. pyi-doctor focuses on the problems that tend to show up after shipping a PyInstaller build: "it works on my PC only", "Windows Defender calls it a trojan", and "which licenses am I redistributing?".

Why this exists

pyi-doctor was built by an elementary school student in Korea, with the help of AI coding tools, after his own PyInstaller apps kept getting flagged by antivirus software.

Install

pip install pyi-doctor

Requirements: Python 3.9+, PyInstaller 6+ (installed as a dependency). The installer step needs Windows and Inno Setup 6.3 or newer (not bundled; set PYI_DOCTOR_ISCC if it is not in the default location). The GitHub upload needs the GitHub CLI.

Build the Windows exe on Linux or macOS (experimental)

PyInstaller does not cross-compile, so a Windows exe normally needs a Windows machine (or Wine). pyi-doctor build --target win builds a PyInstaller-format Windows exe on Linux or macOS without Wine:

pip install pyi-doctor
pyi-doctor build --target win -r requirements.txt
from pyi_doctor import Project, build_windows

project = Project(entry="main.py", name="MyApp", version="1.0.0", requirements=["requests"])
onedir = build_windows(project)                  # dist/portable/MyApp/MyApp.exe (+ _internal)
single = build_windows(project, onefile=True)    # dist/onefile/MyApp.exe

You get the onedir folder, an optional single-file exe, and an Inno Setup script with relative paths (dist/MyApp.iss) that compiles on Windows. For comparison, pynsist also builds on Linux/macOS but produces an installer only.

How it works:

  1. Downloads the python.org Windows embeddable package (python3XY.dll, stdlib) for the same Python minor version as the build machine, the win_amd64 PyInstaller wheel of the installed PyInstaller version (Windows bootloader), and win_amd64 / pure-Python wheels of your requirements (pip download, with Windows environment markers). Cached in ~/.cache/pyi-doctor (PYI_DOCTOR_CACHE).
  2. Finds modules with PyInstaller's modulegraph against those folders, and DLLs used by .pyd files from their PE import tables (pefile). PyInstaller hooks are run with replacements that read the unpacked files instead of importing the package.
  3. Writes PYZ, base_library.zip and the CArchive in PyInstaller's format, adds icon, version information and manifest to the bootloader as PE resources (pure Python), and sets the PE checksum.
  4. Runs the antivirus risk-factor diagnosis on the result. It does not start the exe; examples/github-actions/cross-build-windows.yml builds on ubuntu-latest and starts the exe on windows-latest.

Scope. The build stops before writing files, with a list of reasons, when any of these apply:

  • target other than 64-bit Windows (x64); no 32-bit or ARM64
  • a requirement without a pure-Python or win_amd64 wheel for your Python version on PyPI
  • a PyInstaller hook that needs to import the package, defines hook(hook_api), or calls a helper that cannot be answered from files
  • tkinter (and so customtkinter): the embeddable package has no Tcl/Tk
  • a DLL needed by a .pyd that is neither in the wheels nor a known Windows system DLL

Checked on Linux with Python 3.13 (the end-to-end test also passes on 3.12): stdlib (ssl, sqlite3, ctypes), requests, PyYAML (C extension), click, rich, tqdm, colorama, charset-normalizer (mypyc), Pillow, pydantic 2 (Rust extension), psutil, orjson and pywin32 built and ran under Wine 9 in onedir and onefile mode; numpy built, but could not be run under Wine 9. Other packages are untested. Results on real Windows can differ, so start the exe on Windows before distributing it.

Settings: requirements (defaults to [project].dependencies), -r FILE, --python-version X.Y.Z (must match the build Python's X.Y). pyinstaller_args and PNG icons are not supported by the cross build.

Configuration

Add a table to your pyproject.toml (pyi-doctor init --write adds a template):

[tool.pyi-doctor]
entry = "main.py"
name = "MyApp"
version = "1.0.0"
publisher = "Your Name"
description = "What the app does"
icon = "assets/app.ico"
app_id = "PUT-A-FIXED-GUID-HERE"   # keep it fixed after the first release
console = false
onefile = false
license_file = "LICENSE"            # shown in the installer
add_data = ["assets=assets"]
hidden_imports = []
languages = ["english", "korean"]

name, version and description fall back to [project]. If app_id is not set, a GUID is derived from publisher and name; changing either then changes the GUID, so set it explicitly before the first release.

All settings
Key Default Meaning
entry required Script PyInstaller starts from
name required Exe / installer name
version 0.1.0 Version string; the first four numbers go into Windows version resources
publisher, description, copyright, website "" Metadata for version resources, installer and reports
icon none .ico file
app_id derived Installer GUID
console false true for command-line apps
onefile false Also build a single-file exe
upx false Allow UPX (adds a high-weight risk factor)
hidden_imports, excludes [] Passed to PyInstaller
add_data [] "src=dest", "src;dest" or ["src", "dest"]
pyinstaller_args [] Extra PyInstaller arguments
license_file none License page in the installer
privileges lowest lowest (per-user, can be raised in the wizard) or admin
languages ["english", "korean"] Installer languages available in your Inno Setup
smoke_args, smoke_timeout [], 15 Arguments and seconds for the smoke test
requirements [project].dependencies --target win only: requirement strings or "-r file.txt" for the Windows wheels
output_dir, work_dir dist, build Output folders

Command line

pyi-doctor build                        # full pipeline from pyproject.toml
pyi-doctor build --onefile --no-defender
pyi-doctor build --github-release v1.0.0 --github-repo me/myapp
pyi-doctor build --target win -r requirements.txt   # Windows exe from Linux/macOS (experimental)
pyi-doctor diagnose dist/portable/MyApp/MyApp.exe   # score any exe
pyi-doctor diagnose MyApp.exe --no-icon             # also count the default-icon factor
pyi-doctor smoke dist/portable/MyApp/MyApp.exe -- --version
pyi-doctor iss                          # write the Inno Setup script only
pyi-doctor init --write                 # add [tool.pyi-doctor] to pyproject.toml

Library API

from pyi_doctor import Project, build_portable, build_installer, smoke_test, diagnose, collect_licenses

project = Project.from_pyproject("pyproject.toml")
onedir = build_portable(project)                     # Artifact(kind="onedir", path=..., folder=...)
result = smoke_test(onedir.path, timeout=10)         # SmokeResult(ok, status, missing_modules, ...)
risk = diagnose(onedir.path, icon_configured=True)   # Diagnosis(score, level, factors, defender, ...)
setup = build_installer(project, onedir.folder)      # Windows + Inno Setup
packages, mode = collect_licenses(project.work_path / "onedir")

build_all(project, onefile=..., installer=..., smoke_test=..., diagnose=..., defender=..., licenses=..., sbom_file=..., github_tag=..., target="win") runs everything and returns a Report with .ok, .steps, .to_markdown() and .to_json().

About the antivirus score

The score adds fixed weights for factors that are commonly reported to affect heuristic detection of PyInstaller executables (UPX 30, onefile 20, no version resource 15, unsigned 15, prebuilt bootloader 10, default icon 5). It is meant for comparing your own builds. It does not predict the verdict of Microsoft Defender or any other product, and a low score does not mean a file will not be flagged.

pyi-doctor does not obfuscate, encrypt or otherwise hide code from scanners. It applies ordinary distribution practices (version information, no packer, onedir layout, signing advice) and helps you prepare a false-positive report. Code signing with a certificate from a trusted authority is generally the most effective measure and is outside the scope of this tool.

Measured on the bundled example

examples/hello, Linux x86_64, Python 3.13, PyInstaller 6.22.3 (installer step skipped because it needs Windows):

Step Time
onedir build 4.5 s
onefile build 12.2 s
licenses + SBOM 0.8 s
smoke test (both builds) 0.6 s
diagnosis < 0.1 s
release folder (zip + checksums) 1.5 s
total 19.7 s

Test suite: 167 tests (including 2 real PyInstaller builds and 2 cross builds) on Linux; CI runs the unit tests on Python 3.9–3.14 on Linux and Windows, and the cross build on Linux with a start test on Windows. Windows-specific paths (version resource, Inno Setup compilation, Defender scan, error-dialog detection in the smoke test) run in the Windows CI jobs.

Known limitations

  • Windows features (installer, version resource, Defender scan) need Windows. On other systems the .iss script is still written.
  • The smoke test is a heuristic. It removes Python from the environment but does not reproduce a clean PC (installed DLLs, Visual C++ runtime, fonts, etc.). GUI apps that keep running are counted as started. Windowed apps that crash show a PyInstaller error dialog; on Windows pyi-doctor looks for that dialog by window title, which may not catch every case.
  • The bootloader check compares the installed PyInstaller bootloader with the hashes recorded in its wheel. It describes the PyInstaller in the current environment, not an exe built elsewhere.
  • License notices come from package metadata, which can be missing or wrong; packages loaded dynamically may be missed. Review the generated file.
  • Only PyInstaller is supported (no Nuitka / cx_Freeze). No code signing and no auto-update.
  • The Windows cross build (--target win) is experimental: it covers only the scope listed above, is not checked against every PyInstaller hook, and does not start the exe. Downloads from python.org and PyPI are not signature-checked beyond HTTPS.

Disclaimer

pyi-doctor is rule-based and may miss problems or report problems that do not exist. Its reports are informational and do not replace your own testing, review of licenses, or obligations under applicable law; checking the results and complying with license terms and laws remain the user's responsibility. The software is provided "AS IS", without warranty of any kind, under the MIT License.

pyi-doctor is an independent project and is not affiliated with or endorsed by the PyInstaller project, Jordan Russell / Inno Setup, Microsoft, or GitHub. Product names are trademarks of their respective owners.

License

MIT © 2026 Juwon Seo (서주원, OMEGA SYSTEMS) · Contact: seodes105@gmail.com

Metadata

Release files for pyi-doctor 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyi-doctor 1.3.0
File Size Uploaded Provenance Actions
pyi_doctor-1.3.0.tar.gz 88.3 kB With provenance Details for pyi_doctor-1.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pyi-doctor 1.3.0
File Interpreter ABI Platform Actions
pyi_doctor-1.3.0-py3-none-any.whl Python 3 none any Details for pyi_doctor-1.3.0-py3-none-any.whl

Total release size: 165.6 kB

Release files / pyi_doctor-1.3.0.tar.gz

Download URL pyi_doctor-1.3.0.tar.gz
Size 88.3 kB
Tags Source
SHA-256 checksum
How to use checksums
f10ba4a988ded027adb78d10da970913a3238b0e590a7b112e6b4db579968d04
BLAKE2b-256 checksum
How to use checksums
74bd53ba0d59e9926240fb6ea1d819682b6c10886fd235929cd85978ae94e73d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.

Transparency log

Release files / pyi_doctor-1.3.0-py3-none-any.whl

Download URL pyi_doctor-1.3.0-py3-none-any.whl
Size 77.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bf53e1e5274cf6bd101cfc06eeb278274d286893b76cd3b23f311f17d820702e
BLAKE2b-256 checksum
How to use checksums
cd2b72b4979f1d333a4f7adf6d75402b27fd695887d3dbd05f0bb477fd6a3fbc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.

Transparency log

Security for pyi-doctor 1.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pyi_doctor-1.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page