Thank you for your interest in contributing to usethis! There are issues labeled Good First Issue which are good opportunities for new contributors.
Since usethis is at early stages of development, please ensure a GitHub Issue is opened before starting work on a new feature or bug fix. This helps to ensure that the feature is aligned with the project's goals and that there is no duplication of effort. Sometimes these Issues don't have enough guidance in them, so consider asking for some more guidance from the ticket creator before getting started.
uv is required to install the development environment. You can install it using the instructions here:
https://docs.astral.sh/uv/getting-started/installation/
Then with the current working directory set to the project root, run:
uv syncThis project uses the prek framework (similar to pre-commit) to manage Git hooks. To install the hooks, run:
uv run prek installTo run the tests, simply run:
uv run pytestTo run usethis against the development repository itself, use:
uvx --from . usethis <command>This can be a useful data point for gauging the behaviour of changes or new commands on a moderately complex project repo.
Tests are written using the pytest framework. The test suite is located in the tests directory. The tests are organized into subdirectories with a directory structure that mirrors the structure of the code being tested. This makes it easy to find the tests for a specific module or function. For example: src/x/y/z.py would be tested at tests/x/y/test_z.py.
Tests are usually organized into classes centred around the objects being tested; either modules or classes. For example, tests for a class MyClass would be tested in a class TestMyClass. If testing a method of a class, the method tests would be nested within the class test. For example, tests for the my_method method of MyClass would be in a nested class TestMyMethod within TestMyClass. These classes, and the test functions themselves do not usually need docstrings, and their use is discouraged. The test function names are usually descriptive enough to make it clear what is being tested.
PRs should ideally include tests for any new features or bug fixes.
To diagnose slow test speeds, you can run
uv run pyinstrument -m pytestWith any pytest options you wish to include, e.g. -k to run specific tests, or --collect-only to only profile test collection time. This will generate a CLI-friendly report of where time is being spent. For an interactive HTML report, you can run pyinstrument with the -r=html option before the -m pytest part.
A common pattern in the test suite is to use a pytest fixture to get a temporary directory, and then use the usethis._test.change_cwd context manager in the test to simulate running usethis from within a project. If you're writing a new test and noticing unexpected creations or modifications of files, it's a sign that the working directory has not been properly configured for the test.
Documentation is hosted at https://usethis.readthedocs.io/en/stable/. It can be served locally with:
uv run mkdocs serveDocstrings use Google Style. Note that type annotations should not be repeated in the docstring, since these are already present in the function signature.
Git is used for version control, using Trunk-based development.
It is recommended that you use signed commits, although this is not a requirement. Please see this guide from the VS Code project for instructions on how to do this: https://github.com/microsoft/vscode/wiki/Commit-Signing
This project uses Import Linter to enforce a software architecture. Refer to the [[tool.importlinter.contracts]] sections in pyproject.toml to understand the structure of the project.
The usethis._config.usethis_config object manages global application state that affects behavior across the entire application. This design avoids the need for pass-through variables that would otherwise need to be threaded through many layers of function calls. It provides a context manager, usethis_config.set(), which temporarily overrides global settings:
# Temporarily suppress all output except warnings and errors
with usethis_config.set(alert_only=True):
# Code here runs with modified config
do_something()
# Original settings are automatically restoredThis project supports all versions of Python until end of life. The development environment uses the oldest supported version, which is given in the .python-version file. The GitHub Actions pipeline tests all supported versions.
To ensure the --quiet flag is always effective, avoid using simple print statements. Instead, use the purpose-built usethis._console.plain_print function.
The usethis name should not be capitalized (i.e. not Usethis or UseThis), even at the beginning of a sentence. It should only be styled in monospace as usethis when referring to the command itself.
These colours are used in branding materials:
- Green: #00c000
- Orange: #f26622
- Grey: #999999
- Darker Grey: #424242
Along with the fonts EB Garamond and Cairo.
To add a new usethis badge interface, follow these steps:
- Define a
get_<badge_name>_badgefunction insrc/usethis/_core/badge.py. Try to keep the definitions in alphabetical order. - Declare the interface in
src/usethis/_ui/interface/badge.py. Again, keep the declarations in alphabetical order. The pattern is basically just boilerplate with the other interfaces, but you need to give a description of your command for the--helpoption. - Add a test for your badge in
tests/usethis/_ui/interface/test_interface_badge.py. Follow the pattern of the existing tests, although you only need thetest_addcase, which simply tests that the command runs without error. - Declare a recommended badge placement in the
get_badge_orderfunction insrc/usethis/_core/badge.py. This helps ensure the badges are arranged in an opinionated way relative to existing badges.
Finally, run the command on this project, to make sure the badge gets inserted correctly with valid Markdown syntax. Check it renders successfully and that any hyperlink works as expected.
Tool implementations are defined in classes in the usethis._tool.impl module. To add a new tool, follow these steps:
- If your tool uses bespoke configuration files (e.g. its configuration does not fit within standard files like
pyproject.toml, orsetup.cfg), you will need to define classes for these. - These classes should be added to
usethis._config_fileand should subclass an appropriate base class based on the file type (YAML, TOML, INI, etc.) e.g.YAMLFileManager. - Name the classes based on the filename of the configuration file, following the pattern of the other classes in the module.
- Register the file in the
files_managerfunction in theusethis._config_filemodule.
- Create a submodule in
usethis._tool.implfor your tool, e.g. for a tool named Xyz name itusethis._tool.impl.xyz. - Declare this new submodule in the
.importlinterconfiguration to architecturally describe its dependency relationships with other tools' submodules. For example, does your tool integrate with pre-commit? It should be in a higher layer module than thepre-commitsubmodule. - Define a
usethis._tool.base.ToolSpecsubclass, e.g. for a tool named Xyz, define a classXyzToolSpec(ToolSpec). - Start by implementing its
metaproperty method (which supplies the tool's name, managed files, and rule configuration), then work through the other methods. Most methods have default implementations, but even in those cases you will need to consider them individually and determine an appropriate implementation. For example, methods which specify the tool's dependencies default to empty dependencies, but you shouldn't rely on this. - Mark all methods in your
ToolSpecsubclass with the@typing.finaldecorator. This prevents the methods from being accidentally overridden in theToolsubclass. - Then, define a subclass of the
ToolSpecsubclass you just created, which also subclassesusethis._tool.base.Tool, e.g. for a tool named Xyz, define a classXyzTool(XyzToolSpec, Tool). The only method this usually requires a non-default implementation for isconfig_specto specify which configuration sections should be set up for the tool (and which sections the tool manages). However, you may find it helpful to provide custom implementations for other methods as well, e.g.print_how_to_use. - Mark your
Toolsubclass with@finalas well, to prevent further subclassing.
- In
usethis._tool.all_, add yourToolsubclass to theSupportedToolTypeunion, and to theALL_TOOLSlist. - In
usethis._tool.impl.spec.all_, add yourToolSpecsubclass to theALL_TOOL_SPECSlist. Both lists must stay in alphabetical order and in sync; thetest_in_sync_with_all_toolstest will fail if they differ.
- Create a function in
usethis._core.toolnameduse_<tool_name>, e.g. for a tool named Xyz, create a functionuse_xyz(). - The function should follow the pattern of the other functions in the module. It is responsible for invoking methods on your
Toolsubclass to perform the operations necessary to add your tool, remove it, and display a message explaining how the tool is used.
- Create a function in
usethis._ui.interface.toolnamed after your tool in lowercase, e.g. for a tool named Xyz, create a functionxyz(). - The function should follow the pattern of the other functions in the module. It is responsible for invoking your
use_*function via the_run_toolwrapper.
- You should write tests in
tests/usethis/_core/test_core_toolfor theuse_*function, following the pattern of the other tests in that module for other tools.
- Some tests may need updating as a result of new tool registration. In particular:
TestGetUsageTabletests intests/usethis/_core/test_list.py— these enumerate all tools and their statuses. Add aUsageRowfor your new tool.
- Finally, you should update the README and documentation to include your new tool. You can follow the pattern of the existing tools in the documentation.
By contributing, you agree that your contributions will be licensed under the MIT license. See the LICENSE file.