Skip to content

Instantly share code, notes, and snippets.

@NelsonMinar
Last active July 17, 2024 14:40
Show Gist options
  • Save NelsonMinar/3f2b4d8f6b2175e1117dba7736214bf6 to your computer and use it in GitHub Desktop.
Save NelsonMinar/3f2b4d8f6b2175e1117dba7736214bf6 to your computer and use it in GitHub Desktop.
Claude teaching me how to do Python packaging with pytest and click and VS.Code

Prompt:

make me the absolute simplest Python project that properly packages a command line utility. it should be configured with pyproject.toml and be installable with pip.

Response:

I'll create a simple Python project for a command line utility that prints "Hello, World!". This project will use pyproject.toml for configuration and will be installable with pip. Here's the step-by-step process to create this project:

  1. Create the project structure:
hello_world_cli/
├── src/
│   └── hello_world_cli/
│       ├── __init__.py
│       └── main.py
├── pyproject.toml
└── README.md
  1. Create the main.py file:
def main():
    print("Hello, World!")

if __name__ == "__main__":
    main()
  1. Create the pyproject.toml file:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "hello-world-cli"
version = "0.1.0"
authors = [
  { name = "Your Name", email = "your.email@example.com" },
]
description = "A simple Hello World CLI"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.scripts]
hello-world = "hello_world_cli.main:main"
  1. Create a simple README.md:
# Hello World CLI

A simple command line utility that prints "Hello, World!".

## Installation

pip install hello-world-cli

## Usage

After installation, run the command:

hello-world

This project structure provides the absolute simplest Python project that properly packages a command line utility. It uses pyproject.toml for configuration and can be installed with pip.

To install and use this project:

  1. Navigate to the project directory containing pyproject.toml.
  2. Run pip install . to install the package.
  3. After installation, you can run the command hello-world from anywhere in your terminal.

Would you like me to explain any part of this setup in more detail?

Prompt:

how would I add some pytest tests to this?

Response:

Certainly! Adding pytest tests to your project is a great way to ensure your code works as expected. Here's how you can add pytest tests to your Hello World CLI project:

  1. First, update your project structure to include a tests directory:
hello_world_cli/
├── src/
│   └── hello_world_cli/
│       ├── __init__.py
│       └── main.py
├── tests/
│   └── test_main.py
├── pyproject.toml
└── README.md
  1. Create the test_main.py file in the tests directory:
from hello_world_cli.main import main

def test_main(capsys):
    main()
    captured = capsys.readouterr()
    assert captured.out == "Hello, World!\n"

This test uses the capsys fixture provided by pytest to capture the output of the main() function and assert that it matches the expected output.

  1. Update your pyproject.toml to include pytest configuration and dev dependencies:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "hello-world-cli"
version = "0.1.0"
authors = [
  { name = "Your Name", email = "your.email@example.com" },
]
description = "A simple Hello World CLI"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.scripts]
hello-world = "hello_world_cli.main:main"

[tool.pytest.ini_options]
pythonpath = [
  "src"
]

[project.optional-dependencies]
dev = [
    "pytest",
]

The [tool.pytest.ini_options] section tells pytest where to look for your source code.

The [project.optional-dependencies] section defines a dev extra that includes pytest, allowing you to install development dependencies separately.

  1. Update your README.md to include information about running tests:
# Hello World CLI

A simple command line utility that prints "Hello, World!".

## Installation

pip install hello-world-cli

## Usage

After installation, run the command:

hello-world

## Development

To set up the development environment:

1. Clone the repository
2. Install the package with development dependencies:

pip install -e ".\[dev\]"

3. Run tests:

pytest

Now you can run your tests using pytest. Here's how to use it:

  1. Install your package with dev dependencies:
    pip install -e ".[dev]"
    
  2. Run the tests:
    pytest
    

This setup provides a basic structure for testing your CLI application. As your project grows, you can add more test files and test cases to ensure all aspects of your application work correctly.

Would you like me to explain any part of this testing setup in more detail?

Prompt:

now I also want to use click for the command line tool. add that.

Response:

Certainly! I'll update the project to use Click for the command-line interface. Click is a popular Python package for creating command-line interfaces with minimal code. Here's how we can modify the project to incorporate Click:

  1. Update the project structure (no changes needed):
hello_world_cli/
├── src/
│   └── hello_world_cli/
│       ├── __init__.py
│       └── main.py
├── tests/
│   └── test_main.py
├── pyproject.toml
└── README.md
  1. Modify the main.py file to use Click:
import click

@click.command()
@click.option('--name', default='World', help='Who to greet')
def main(name):
    """Simple program that greets NAME"""
    click.echo(f"Hello, {name}!")

if __name__ == "__main__":
    main()
  1. Update the test_main.py file to test the Click command:
from click.testing import CliRunner
from hello_world_cli.main import main

def test_main_default():
    runner = CliRunner()
    result = runner.invoke(main)
    assert result.exit_code == 0
    assert result.output == "Hello, World!\n"

def test_main_with_name():
    runner = CliRunner()
    result = runner.invoke(main, ['--name', 'Alice'])
    assert result.exit_code == 0
    assert result.output == "Hello, Alice!\n"
  1. Update the pyproject.toml file to include Click as a dependency:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "hello-world-cli"
version = "0.1.0"
authors = [
  { name = "Your Name", email = "your.email@example.com" },
]
description = "A simple Hello World CLI using Click"
readme = "README.md"
requires-python = ">=3.7"
dependencies = [
    "click",
]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

[project.scripts]
hello-world = "hello_world_cli.main:main"

[tool.pytest.ini_options]
pythonpath = [
  "src"
]

[project.optional-dependencies]
dev = [
    "pytest",
]
  1. Update the README.md to reflect the new Click functionality:
# Hello World CLI

A simple command line utility that greets you, built with Click.

## Installation

pip install hello-world-cli

## Usage

After installation, run the command:

hello-world

You can also specify a name to greet:

hello-world --name Alice

## Development

To set up the development environment:

1. Clone the repository
2. Install the package with development dependencies:

pip install -e ".\[dev\]"

3. Run tests:

pytest

Now your project uses Click for the command-line interface. This gives you a more robust CLI with built-in help messages and easy option parsing.

To use this updated version:

  1. Install the package: pip install .
  2. Run the command: hello-world (this will print "Hello, World!")
  3. Or run with a name: hello-world --name Alice (this will print "Hello, Alice!")

The tests now use Click's CliRunner to simulate CLI invocations, allowing you to test different inputs easily.

Would you like me to explain any part of this Click integration in more detail?

Prompt:

I'd like to debug this code in vs.code. but when I press F5 on main.py nothing happens, I think because of click. how do I get vs.code to invoke a click command under the debugger?

Response:

You're right that debugging Click applications in VS Code can be a bit tricky because the main() function is typically invoked by Click rather than being called directly. Here's how you can set up VS Code to debug your Click application:

  1. First, modify your main.py slightly to allow for easier debugging:
import click
import sys

@click.command()
@click.option('--name', default='World', help='Who to greet')
def main(name):
    """Simple program that greets NAME"""
    click.echo(f"Hello, {name}!")

if __name__ == "__main__":
    # This allows the script to be run directly and accept command line arguments
    sys.exit(main())
  1. Create a launch.json file in the .vscode directory of your project. If it doesn't exist, you can create it by going to the Run view (Ctrl+Shift+D), clicking on "create a launch.json file", and selecting Python.
  2. In the launch.json file, add a configuration like this:
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Click CLI",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/src/hello_world_cli/main.py",
            "args": ["--name", "Alice"],
            "console": "integratedTerminal"
        }
    ]
}

This configuration does the following:

  • Sets the program to run as your main.py file.
  • Provides command-line arguments (["--name", "Alice"]) that Click will parse.
  • Uses the integrated terminal, which works well with Click's output.
  1. Now, you can set breakpoints in your code as needed.
  2. To debug, go to the Run view, select "Python: Click CLI" from the dropdown at the top, and click the green play button or press F5.

This should run your Click application with the provided arguments and stop at any breakpoints you've set.

If you want to debug with different arguments, you can either:

a) Modify the args array in the launch.json file, or b) Create multiple configurations in launch.json with different sets of arguments.

For example, to add a configuration without any arguments:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Click CLI (default)",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/src/hello_world_cli/main.py",
            "args": [],
            "console": "integratedTerminal"
        },
        {
            "name": "Python: Click CLI (with name)",
            "type": "python",
            "request": "launch",
            "program": "${workspaceFolder}/src/hello_world_cli/main.py",
            "args": ["--name", "Alice"],
            "console": "integratedTerminal"
        }
    ]
}

Now you can choose which configuration to run from the dropdown in the Run view.

This setup should allow you to debug your Click application effectively in VS Code. Let me know if you need any clarification or have any other questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment