Contributing
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>/TopoStatsAFMSlicer.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.
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 with the following command.
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