Skip to content

Developer Guide

StefanGreve edited this page Oct 10, 2023 · 8 revisions

Installation

Make sure that you have installed the latest stable version of Python before you proceed with the installation instructions. This project uses Python's built-in venv module for creating virtual environments, and its strongly recommended that you use the same tool. The commands below create and active a new virtual environment.

> git clone git@github.com:Advanced-Systems/anonpy.git
> cd ./anonpy
> python -m venv venv
> # On Linux, use: 'source venv/bin/activate'
> .\venv\Scripts\activate.ps1
> pip install -r requirements/development.txt
> pip install --editable .

The last command installs the module in Development Mode. You can use the deactivate command to turn off venv mode on any platform. Finally, run

> anonpy --version

to confirm that your development environment has been set up correctly.

Managing Multiple Python Versions on Windows

List which versions of Python are installed on your system:

py -0

Install a new Python version, e.g. 3.12:

winget install --id Python.3.12

Create a new virtual environment by targeting a specific version:

py -3.12 -m venv venv

Similarly, run a script with a specific Python interpreter:

py -3.12 test.py

Debugging

VS Code

Create a launch.json file in the .vscode folder located in project root and add the following lines:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "AnonPy CLI",
            "type": "python",
            "request": "launch",
            "module": "anonpy",
            "justMyCode": true,
            "args": [
                "preview",
                "--resource",
                "Aozkv26f"
            ]
        },
        {
            "name": "AnonPy Script",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "justMyCode": true,
            "console": "integratedTerminal"
        }
    ]
}

You may need to configure the args array to debug a specific combination of command line arguments. The .gitignore file is set up to ignore a test.py file at project root, which you can use to write scripts.

Development

CLI

The argparse module makes it possible to define default arguments. However, when default=True is used, it is hard to determine whether the user invoked a command with the implicit default value or by manually setting it. In these cases, it is recommended to use

from argparse import SUPPRESS

cfg_file = "anonpy.ini"

# parser builder code...
parser.add_argument("-c", "--config", type=Path, default=SUPPRESS, help="path to config file")
args = parser.parse_args()

# use the config flag, else construct a fallback path
cfg_path = getattr(args, "config", module_folder / cfg_file)

The --config flag will create a args.config attribute if and only if the flag is used in the command invocation. Thus, use the getattr method to avoid potential AttributeError.

Continuous Integration

CodeCov

Codecov is the all-in-one code coverage reporting solution that makes it possible to analyze a PR coverage test delta.

# validate configuration file before committing any changes
curl -X POST --data-binary "@.codecov.yml" https://codecov.io/validate

Documentation: https://docs.codecov.com/docs/codecovyml-reference

Unit Tests

This project uses pytest as a unit test framework. Some of its settings are stored in a pytest.ini file at project root level.

Additionally, the test suite also uses the pytest-xdist plugin for distributing tests across multiple CPUs to speed up test execution. Note that there are some known limitations for using this tool, in particular:

  1. pytest-xdist workers need to be able to collect parameters in order
- @pytest.mark.parametrize("param", ["a", "b"])
+ @pytest.mark.parametrize("param", sorted(["a", "b"]))
  1. Test methods that use the parametrize decorator should create resources uniquely, else workers run risk of getting in each others way and raise the following exception:
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process:

Use the built-in uuid module to annotate file names, e.g.

from uuid import uuid4

import pytest

@pytest.mark.parametrize("param", sorted(["a", "b"]))
def test_method(self: Self, param: str) -> None:
    # Arrange
    file = f"test_file_{uuid4()}.txt"
Clone this wiki locally