Advanced Python Programs | IT Developer
IT Developer

Advanced Python Programs



Share with a Friend

Introduction and Setup

Package a module and publish it to TestPyPI - Advanced Python Program

Prereqs: Python 3.10+,

pip install psutil black flake8 isort twine build rich

(some scripts also use colorama or tomllib on <3.11).

 

The program below is a complete, safe, and practical Python script that:

  1. Creates a sample package (module) with a pyproject.toml using PEP 621,
  2. Builds sdist and wheel using python -m build,
  3. Uploads the artifacts to TestPyPI using python -m twine upload (supports environment credentials or interactive prompt),
  4. Optionally verifies the upload by creating a temporary venv and installing the package from TestPyPI and importing it.

I include the full script, example outputs (what you’ll see), and a detailed step-by-step explanation of what each part does and why.

Important: This script does not publish anything without your explicit approval (you must provide credentials or allow the tool to prompt). Use TestPyPI for testing — it’s separate from the real PyPI. Do not put real production credentials here.


File Name: package_and_publish_testpypi.py

 

File Name : package_and_publish_testpypi.py

#!/usr/bin/env python3

"""

package_and_publish_testpypi.py

 

Create a sample package (pyproject.toml + module), build distributions, upload to TestPyPI,

and optionally verify installation from TestPyPI.

 

Usage examples:

    # Create project and build artifacts only (no upload)

    python package_and_publish_testpypi.py create MyPkg --version 0.1.0 --no-upload

 

    # Create, build and upload to TestPyPI (will prompt for credentials if none in env)

    python package_and_publish_testpypi.py create MyPkg --version 0.1.0

 

    # Create with unique timestamped version, upload, and verify install

    python package_and_publish_testpypi.py create MyPkg --unique --upload --verify

 

Notes:

 - Requires `build` and `twine` installed (pip install build twine).

 - For uploading via an API token set:

       TWINE_USERNAME="__token__"

       TWINE_PASSWORD="<pypi-AgENd...>"  (the API token value)

   Or the script will prompt you for username & password.

"""

 

from __future__ import annotations

 

import argparse

import getpass

import os

import subprocess

import sys

import tempfile

import time

from datetime import datetime

from pathlib import Path

from typing import Optional, Tuple

 

 

# ----------------------------

# Templates for the sample package

# ----------------------------

PYPROJECT_TPL = """[build-system]

requires = ["setuptools>=61", "wheel", "build"]

build-backend = "setuptools.build_meta"

 

[project]

name = "{name}"

version = "{version}"

description = "{description}"

readme = "README.md"

authors = [{{name = "{author}", email = "{email}"}}]

requires-python = ">=3.8"

license = {{text = "MIT"}}

keywords = ["sample", "test", "example"]

classifiers = [

  "Programming Language :: Python :: 3",

  "License :: OSI Approved :: MIT License",

]

"""

 

INIT_PY_TPL = '''"""

{pkg} — a tiny example package created by package_and_publish_testpypi.py

"""

 

__all__ = ["hello"]

__version__ = "{version}"

 

def hello(name: str) -> str:

    """Return a friendly greeting"""

    return f"Hello, {name}!"

'''

 

README_TPL = "# {name}\n\n{description}\n\nInstall from TestPyPI for verification."

 

GITIGNORE = """# Python caches

__pycache__/

*.py[cod]

 

# build artifacts

dist/

build/

*.egg-info/

 

# virtualenv

.venv/

"""

 

LICENSE_TPL = """MIT License

 

Copyright (c) {year} {author}

 

Permission is hereby granted, free of charge, to any person obtaining a copy...

"""

 

# ----------------------------

# Helpers

# ----------------------------

def run(cmd: list, cwd: Optional[Path] = None, capture: bool = False) -> Tuple[int, str]:

    """Run a command; return (rc, stdout+stderr)."""

    try:

        if capture:

            out = subprocess.check_output(cmd, cwd=str(cwd) if cwd else None, stderr=subprocess.STDOUT, text=True)

            return 0, out

        else:

            subprocess.check_call(cmd, cwd=str(cwd) if cwd else None)

            return 0, ""

    except subprocess.CalledProcessError as e:

        return e.returncode, (e.output or "")

    except FileNotFoundError as e:

        return 127, str(e)

 

 

def ensure_tool_available(module_name: str, human_cmd_description: str) -> None:

    """

    Ensure 'python -m module_name --version' succeeds. If not, prompt user to install it into current env.

    """

    rc, out = run([sys.executable, "-m", module_name, "--version"], capture=True)

    if rc != 0:

        print(f"[!] Tool '{module_name}' is required ({human_cmd_description}).")

        yn = input(f"Install '{module_name}' into the current interpreter now? [y/N]: ").strip().lower()

        if yn not in ("y", "yes"):

            print("[!] Aborting. Install the tool manually and re-run.")

            sys.exit(1)

        print(f"[*] Installing {module_name}...")

        rc2, out2 = run([sys.executable, "-m", "pip", "install", module_name], capture=True)

        if rc2 != 0:

            print("[!] Failed to install", module_name)

            print(out2)

            sys.exit(rc2)

        print(f"[+] Installed {module_name}.")

 

 

def write_sample_project(root: Path, name: str, version: str, description: str, author: str, email: str) -> None:

    """Create project skeleton and small module under root."""

    root.mkdir(parents=True, exist_ok=True)

    (root / "pyproject.toml").write_text(PYPROJECT_TPL.format(name=name, version=version, description=description, author=author, email=email), encoding="utf-8")

    (root / "README.md").write_text(README_TPL.format(name=name, description=description), encoding="utf-8")

    (root / ".gitignore").write_text(GITIGNORE, encoding="utf-8")

    (root / "LICENSE").write_text(LICENSE_TPL.format(year=datetime.utcnow().year, author=author), encoding="utf-8")

 

    pkg_dir = root / name

    pkg_dir.mkdir(exist_ok=True)

    (pkg_dir / "__init__.py").write_text(INIT_PY_TPL.format(pkg=name, version=version), encoding="utf-8")

 

    print(f"[+] Created project skeleton at {root.resolve()}")

    print("    files: pyproject.toml, README.md, LICENSE, .gitignore, package directory")

 

 

def build_distributions(project_dir: Path) -> None:

    """Run python -m build to create sdist & wheel in dist/"""

    print("[*] Building distributions with 'python -m build' ...")

    rc, out = run([sys.executable, "-m", "build", "--sdist", "--wheel"], cwd=project_dir, capture=True)

    if rc != 0:

        print("[!] Build failed:")

        print(out)

        sys.exit(rc)

    print(out)

    print("[+] Build completed. Check the 'dist/' directory.")

 

 

def upload_to_testpypi(project_dir: Path, username: Optional[str], password: Optional[str], repository_url: str = "https://test.pypi.org/legacy/") -> None:

    """

    Upload all files in dist/ to TestPyPI using twine. Credentials may be provided via args or environment.

    Environment variables TWINE_USERNAME/TWINE_PASSWORD take precedence if set.

    """

    env_user = os.environ.get("TWINE_USERNAME")

    env_pass = os.environ.get("TWINE_PASSWORD")

    if env_user:

        print("[i] Using TWINE_USERNAME from environment.")

        username = env_user

    if env_pass:

        password = env_pass

 

    if not username:

        username = input("TestPyPI username (or '__token__' if using API token): ").strip()

    if not password:

        password = getpass.getpass("TestPyPI password or API token: ")

 

    # Use python -m twine upload --repository-url <url> dist/*

    cmd = [sys.executable, "-m", "twine", "upload", "--repository-url", repository_url, "dist/*"]

    print("[*] Uploading to TestPyPI (this will prompt Twine for credentials unless provided via env)...")

    # Provide credentials as TWINE_USERNAME/TWINE_PASSWORD env for the subprocess

    env = os.environ.copy()

    env["TWINE_USERNAME"] = username

    env["TWINE_PASSWORD"] = password

    # Run twine as subprocess with env

    try:

        subprocess.check_call(cmd, cwd=str(project_dir), env=env)

    except subprocess.CalledProcessError as e:

        print("[!] Twine upload failed with exit code", e.returncode)

        sys.exit(e.returncode)

    print("[+] Upload to TestPyPI complete.")

 

 

def verify_install_from_testpypi(package_name: str, version: str) -> None:

    """

    Create a temporary venv, pip install the package from TestPyPI, and try to import it.

    This helps confirm your package is available and importable.

    """

    print("[*] Verifying installation from TestPyPI in a temporary venv...")

    tmpdir = Path(tempfile.mkdtemp(prefix="tpypi-verify-"))

    venv_dir = tmpdir / "venv"

    # create venv

    subprocess.check_call([sys.executable, "-m", "venv", str(venv_dir)])

    # determine python path

    if os.name == "nt":

        py = venv_dir / "Scripts" / "python.exe"

    else:

        py = venv_dir / "bin" / "python"

    py = str(py)

    # install from TestPyPI (no deps) to ensure we pull exactly the uploaded artifact:

    index_url = "https://test.pypi.org/simple/"

    pkg_spec = f"{package_name}=={version}"

    cmd_install = [py, "-m", "pip", "install", "--index-url", index_url, "--no-deps", pkg_spec]

    print(f"[*] Installing {pkg_spec} from TestPyPI into {venv_dir} ...")

    rc, out = run(cmd_install, capture=True)

    if rc != 0:

        print("[!] pip install from TestPyPI failed:")

        print(out)

        print("You may need to wait a minute for TestPyPI to index the package, or check that the upload succeeded.")

        sys.exit(rc)

    print(out)

    # try importing and calling hello()

    cmd_run = [py, "-c", f"import {package_name}; print('IMPORT_OK:', {package_name}.hello('Tester'))"]

    rc2, out2 = run(cmd_run, capture=True)

    if rc2 != 0:

        print("[!] Import test failed:")

        print(out2)

        sys.exit(rc2)

    print(out2.strip())

    print("[+] Verification SUCCEEDED. Temporary venv at:", venv_dir)

    print("[i] Remove it when done:", tmpdir)

 

 

# ----------------------------

# CLI

# ----------------------------

def build_parser() -> argparse.ArgumentParser:

    p = argparse.ArgumentParser(description="Create a sample package, build, and publish to TestPyPI")

    p.add_argument("action", choices=["create"], help="Action to perform (create builds and optionally uploads)")

    p.add_argument("name", help="Package name (must be a valid distribution name, e.g. mypkg)")

    p.add_argument("--version", default="0.1.0", help="Initial version (default: 0.1.0)")

    p.add_argument("--description", default="A sample test package", help="Short description")

    p.add_argument("--author", default=getpass.getuser() or "You", help="Author name")

    p.add_argument("--email", default="", help="Author email")

    p.add_argument("--unique", action="store_true", help="Append timestamp to version to ensure uniqueness (useful for repeated TestPyPI uploads)")

    p.add_argument("--no-upload", dest="upload", action="store_false", help="Do not upload to TestPyPI (just build)")

    p.add_argument("--upload", dest="upload", action="store_true", help="Upload to TestPyPI after building (default)")

    p.add_argument("--verify", action="store_true", help="After upload, attempt to pip install from TestPyPI and import")

    p.add_argument("--skip-tool-check", action="store_true", help="Skip checking for build/twine and skip auto-install prompt")

    return p

 

 

def main(argv: Optional[list] = None) -> None:

    args = build_parser().parse_args(argv)

    if args.action != "create":

        print("[!] Only 'create' action is supported in this script.")

        sys.exit(1)

 

    name = args.name.strip()

    version = args.version.strip()

    if args.unique:

        version = f"{version}.post{int(time.time())}"

 

    project_dir = Path(name)

    if project_dir.exists():

        print(f"[!] Directory '{project_dir}' already exists. Remove it or choose another name.")

        sys.exit(1)

 

    # 1) Create skeleton

    write_sample_project(project_dir, name, version, args.description, args.author, args.email)

 

    # 2) Ensure build & twine are available (unless skipped)

    if not args.skip_tool_check:

        ensure_tool_available("build", "create source & wheel distributions ('python -m build')")

        ensure_tool_available("twine", "upload distributions ('python -m twine upload')")

 

    # 3) Build distributions

    build_distributions(project_dir)

 

    # 4) Optionally upload to TestPyPI

    if args.upload:

        # Credentials may be provided via env; user will be prompted if not present

        upload_to_testpypi(project_dir, username=None, password=None)

 

    # 5) Optionally verify installation

    if args.upload and args.verify:

        # Wait a moment for TestPyPI to process the upload (small sleep helps typical cases)

        print("[i] Waiting a few seconds to allow TestPyPI to index the package...")

        time.sleep(3)

        verify_install_from_testpypi(name, version)

 

    print("[+] All done. Project dir:", project_dir.resolve())

 

 

if __name__ == "__main__":

    main()

What the script does — short summary


  1. Create a minimal project folder with:
    • pyproject.toml (PEP 621 metadata),
    • a package folder mypkg/__init__.py with a hello() function,
    • README.md, .gitignore, LICENSE.
  2. Ensure build and twine are available (optionally installing them if the user agrees).
  3. Build wheel and sdist: runs python -m build --sdist --wheel and writes artifacts to dist/.
  4. Upload to TestPyPI: runs python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*, using credentials from TWINE_USERNAME/TWINE_PASSWORD or prompting.
  5. Verify (optional): creates a temp venv, installs the just-uploaded package from TestPyPI and imports it, executing hello() to confirm.

Example run (expected console output)

These outputs are illustrative — your local build / twine text may vary.

Build only (no upload)

 

 
$ python package_and_publish_testpypi.py create samplepkg --version 0.1.0 --no-upload
[+] Created project skeleton at /home/alice/samplepkg
    files: pyproject.toml, README.md, LICENSE, .gitignore, package directory
[*] Building distributions with 'python -m build' ...
* Creating venv isolated environment
* Building sdist
* Building wheel
Successfully built samplepkg-0.1.0-py3-none-any.whl
[+] Build completed. Check the 'dist/' directory.
[+] All done. Project dir: /home/alice/samplepkg

Build + upload to TestPyPI (interactive credential prompt)

 

 
$ python package_and_publish_testpypi.py create samplepkg --unique
[+] Created project skeleton at /home/alice/samplepkg
    files: ...
[*] Installing build (if missing)...  # if it was missing and user allowed install
[*] Building distributions with 'python -m build' ...
... build output ...
[+] Build completed. Check the 'dist/' directory.
[i] Using TWINE_USERNAME from environment.  # if TWINE_USERNAME set
[*] Uploading to TestPyPI (this will prompt Twine for credentials unless provided via env)...
Uploading distributions to https://test.pypi.org/legacy/
Uploading samplepkg-0.1.0.post1630000000.tar.gz
  100%  5.00k/5.00k [00:01<00:00, 4.20kB/s]
Uploading samplepkg-0.1.0.post1630000000-py3-none-any.whl
  100% 10.0k/10.0k [00:01<00:00, 9.50kB/s]
View at https://test.pypi.org/project/samplepkg/0.1.0.post1630000000/
[+] Upload to TestPyPI complete.
[+] All done. Project dir: /home/alice/samplepkg


Upload + verify install

 

 
$ python package_and_publish_testpypi.py create samplepkg --unique --verify
... build output ...
[*] Uploading to TestPyPI ...
... twine output ...
[+] Upload to TestPyPI complete.
[i] Waiting a few seconds to allow TestPyPI to index the package...
[*] Verifying installation from TestPyPI in a temporary venv...
[*] Installing samplepkg==0.1.0.post1630000000 from TestPyPI into /tmp/tpypi-verify-xxxxx ...
Collecting samplepkg==0.1.0.post1630000000
  Downloading samplepkg-0.1.0.post1630000000-py3-none-any.whl (10 kB)
Successfully installed samplepkg-0.1.0.post1630000000
IMPORT_OK: Hello, Tester!
[+] Verification SUCCEEDED. Temporary venv at: /tmp/tpypi-verify-xxxxx
[+] All done. Project dir: /home/alice/samplepkg

Security & practical notes

  • Use TestPyPI for testing — it's separate from real PyPI.
  • Credentials:
    • Best practice: create an API token in TestPyPI and set TWINE_USERNAME="__token__" and TWINE_PASSWORD="<token value>" in your environment. This avoids entering your account password.
    • Do not paste secrets into shared consoles or commit them to code.
  • Unique versions: If you upload the same version twice to TestPyPI, it will be rejected. Use --unique to append a timestamp to the version (handy for repeated tests).
  • Delay for indexing: sometimes TestPyPI takes a short moment to index uploaded files — if verify fails, wait a minute and try again.
  • Permissions: If you run pip install or python -m build in system Python and you don't have write access, use virtualenv or run in a venv.
  • Reality check: This script runs tools (build, twine) as subprocesses, so it relies on those tools' behavior. The script checks tools and offers to install them into the current interpreter if missing.

Step-by-step code explanation

I'll explain the main logical parts of the script in order:

1) Templates & project layout

  • PYPROJECT_TPL — PEP 621 toml with build-system declaring build and setuptools & wheel, and project metadata (name, version, description, author).
  • INIT_PY_TPL — the package py containing hello() and __version__.
  • README_TPL, .gitignore, LICENSE — small supportive files.

2) run() helper

  • Runs arbitrary shell commands (subprocess) and returns (rc, output); capture=True returns combined stdout+stderr; otherwise it streams output to terminal.

3) ensure_tool_available()

  • Checks python -m <module> --version; if not found or failing, prompts user to install it into the current interpreter using pip install <module>. This helps avoid manual setup friction. The check can be skipped via --skip-tool-check.

4) write_sample_project()

  • Creates the folder structure and writes the files. This ensures the project is buildable with python -m build.

5) build_distributions()

  • Runs python -m build --sdist --wheel in the project dir. This produces dist/ with .tar.gz and .whl.

6) upload_to_testpypi()

  • Reads credentials from environment (TWINE_USERNAME, TWINE_PASSWORD) if present, otherwise prompts the user. It then runs python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* with those credentials set in the subprocess environment. This avoids exposing the password in command-line arguments.

Note: for token uploads, set TWINE_USERNAME="__token__" and TWINE_PASSWORD="<actual-token-value>".

7) verify_install_from_testpypi()

  • Creates a temporary venv, installs the just-uploaded package from TestPyPI using pip with --index-url https://test.pypi.org/simple/ --no-deps and then imports the package inside that venv and calls the hello() function to confirm the package installs and works.

8) CLI wiring (argparse) and flow (main)

  • Parses options: create name, --version, --unique, --no-upload, --verify, etc.
  • Ensures project folder does not already exist to avoid overwriting.
  • Writes the skeleton, ensures tools exist (unless skipped), builds, optionally uploads and verifies.

 

Final tips before you run

1. Install helper tools first (recommended):

 
pip install build twine

2. Create a TestPyPI account and optionally an API token: https://test.pypi.org/account/token/ Then export credentials (safer than interactive prompt):

 
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="pypi-AgENd...your-token-here..."

3. Run the script:

 
python package_and_publish_testpypi.py create mysample --unique --upload --verify