Overview
Contributions of all forms are welcome, whether that is bug reports, feature requests or questions because our documentation isn't clear.
Please refer to the installation instructions for cloning and install the development version of AFMSlicer and its dependencies.
Cloning the repository
If you wish to make changes to the code base and are not a collaborator on the repository you will have to fork the repository, clone it, make your changes and the submit a pull request.
# Collaborator
git clone git@github.com:ns-rse/AFMSlicer.git
# Forked copy
git clone git@github.com:<YOUR_GITHUB_USERNAME>/AFMSlicer.git
Install Additional Dependencies
If you are to contribute you should install the additional dependencies for undertaking work and enable the pre-commit hooks.
If you haven't already create a virtual environment and install the packages.
cd AFMSlicer
uv venv --python=3.11
source .venv/bin/activate
uv sync
uv pip install -e . --group dev
Creating a Branch
Typically you will create a branch to make changes on (). It is not compulsory but we try to use a consistent
nomenclature for branches that shows who has worked on the branch, the issue it pertains to and a short description of
the work. To which end you will see branches with the form <GITHUB_USERNAME>/<GITHUB_ISSUE>-<DESCRIPTION>. Some
examples are shown below…
| Branch | User | Issue | Description |
|---|---|---|---|
ns-rse/5-readme-bagdes |
ns-rse |
#5 | Adding badges to README.md |
Here we ensure the main branch is up-to-date and create a new branch ns-rse/13-new-feature
You can now start working on your feature or bug fix making regular commits.
Software Development
To make the codebase easier to maintain we ask that you follow the guidelines below on coding style, linting, typing,
documentation and testing. These entail a number of additional dependencies that can be installed using the --group
dev flag to uv pip install as shown below.
This will pull in all the dependencies we use for development (dev), tests (tests) and writing documentation
(docs).
Coding Style/Linting
Using a consistent coding style has many benefits (see Linting : What is all the fluff
about?). For this project we aim to adhere to PEP8 - the style Guide
for Python Code and do so using the formatting linters black and ruff. Ruff implements the
checks made by Flake8), isort, mypy and numpydoc-validation. We also
like to ensure the code passes pylint which helps identify code duplication and reduces some of the code
smells that we are all prone to making. A .pylintrc is included in the repository. These checks are run
on all Pull Requests via pre-commit.ci and have to pass before contributions can be merged to main.
Many popular IDEs such as VSCode, PyCharm, Spyder and Emacs all have support for integrating these linters into your workflow such that when you save a file the linting/formatting is automatically applied.
Pre-commit
pre-commit is a powerful and useful tool that runs hooks on your code prior to making commits. For a more
detailed exposition see pre-commit : Protecting your future self. The
repository includes pre-commit as a development dependency as well as a .pre-commit-config.yaml. To use these
locally you should have already installed all the dev dependencies in your virtual environment. You then need to
install pre-commit configuration and hooks (NB this will download specific virtual environments that pre-commit
uses when running hooks so the first time this is run may take a little while).
If these fail then you will not be able to make a commit until they are fixed. Several of the linters will
automatically format files so you can simply git add -u . those and try committing straight away. flake8 does not
correct files automatically so the errors will need manually correcting.
If you do not enable and resolve issues reported by pre-commit locally before making a pull request you will find the
pre-commit.ci GitHub Action will fail, preventing your Pull Request from being merged. You can
shorten the feedback loop and speed up the resolution of errors by enabling pre-commit locally and resolving issues
before making your commits.
Typing
Whilst Python is a dynamically typed language (that is the type of an object is determined dynamically) the use of Type Hints is expected as it makes reading and understanding the code considerably easier for contributors and the code base more robust. These are checked on commits and pull-requests via a pre-commit hook that runs mypy. For more on Type Hints see PEP483 and PEP484.
Documentation
All classes, methods and functions should have numpy docstrings defining their functionality, parameters and
return values. Pylint will note and report the absence of docstrings by way of the
missing-function-docstring condition and the docstrings are checked the the pre-commit hook
numpydoc-validation. Further, when new methods that introduce changes to the configuration are
incorporated into the package they should be documented under Parameter
Configuration. pre-commit has the markdownlint-cli2 hook
enabled to lint all Markdown files and will where possible automatically fix things, but some issues need resolving
manually.
Testing
New features should have unit-tests written and included under the tests/ directory to ensure the functions work as
expected. The pytest framework is used for running tests along with a number of plugins (syrupy for
regression testing; pytest-mpl for testing generated Matplotlib images). In conjunction with
pre-commit we leverage pytest-testmon) to run tests on each commit, but as the test
suite is large and can take a while to run pytest-testmon restricts tests to only files that have changed (code or
tests) or changes in environment variables and dependencies. You will need to create a database locally on first run and
so should run the following before undertaking any development.
This will create a database (.testmondata) which tracks the current state of the repository, this file is ignored by
Git (via .gitignore) but keeps track of the state of the repository and what has changed so that the pre-commit hook
Pytest (testmon) only attempts to run the tests when changes have been made to files that impact the tests.
Debugging
To aid with debugging we include the snoop package as a dev dependency. The package is disabled by default,
but when you have a class, method or function you wish to debug you should add snoop.install(enabled=True) to the file
you wish to debug and use the @snoop decorator around the function/method you wish to debug.
Configuration
As described in Parameter Configuration options are primarily passed to AFMSlicer via a
YAML configuration file. When introducing new features that require configuration options you will have to
ensure that the default configuration file (afmslicer/default.yaml) is updated to include your options and that
corresponding arguments are added to the entry point (please refer to Adding Modules page which
covers this). Further the afmslicer.validation.validate.config() function, which checks a valid configuration file
with all necessary fields has been passed when invoking afmslicer sub-commands, will also need updating to include new
options in the Schema against which validation of configuration files is made.
IDE Configuration
Linters such as black, flake8 and pylint can be configured to work with your IDE so that say Black and/or
formatting is applied on saving a file or the code is analysed with pylint on saving and errors reported. Setting up
and configuring IDEs to work in this manner is beyond the scope of this document but some links to articles on how to do
so are provided.
- Linting Python in Visual Studio Code
- Code Analysis — Spyder for
pylintfor Black see How to use code formatter Black with Spyder. - Code Quality Assistance Tips and Tricks, or How to Make Your Code Look Pretty? | PyCharm
- Reformat and rearrange code | PyCharm
- Advanced Python Development Workflow in Emacs | Serghei's Blog
- Getting started with lsp-mode for Python