Development Chores#
This file aims to detail guidelines for some repetitive tasks contributors to this library may need to complete in different recurring scenarios.
make.bat (Windows)/the Makefile (otherwise) is often a handy companion in development. Execute make help to see the available Makefile targets.
More of them will likely be added on popular demand.
Added in version 0.9.7: Created make.bat at the project root, such that developers on Windows can basically use the Makefile without installing GNU Make.
If you are in powershell, make will not work directly and you must use .\make.bat instead, which is more verbose; thus, you are strongly
advised to develop in cmd.exe, the traditional command prompt.
Bumping the version#
You can use uv version --bump patch to increment the patch version, and do a per-file find-and-replace in your preferred IDE after inspecting
each instance to avoid unintended changes. __version__ is already instantiated from a string to streamline this step.
In pyproject.toml, there may be optional dependencies whose version coincides with the project’s, so take care not to modify those as well. Also exclude the changelog file (CHANGELOG.md, at the project root) from the replace operation.
A core developer of this project will help you create a GitHub release with the default release notes, which will automatically trigger a stable Read the Docs build, push to PyPI and cause a conda-forge bot automerge. If you include your desired remarks in the PR under a “Release Notes” section, those will be used as the release notes instead after the developer tries their best to correct grammatical or spelling mistakes.
Note
Our release schedule, though stabilizing, does not concern the frequency of patch releases.
Note
There is no lower bound on the number of patches per minor, though due to bit packing shenanigans and concern of code churn or low-quality changes, the upper bound is 256, i.e. the numbers from 0 to 255. The same applies for minor releases per major, but we aim to drop majors every year and minors per month, so this should be a non-issue.
Adding a new submodule#
Search for ‘rwlocks’ across all files in the repository, excluding asyncutils/rwlocks.py. This should reveal all the locations where submodule names are referenced without fail. Insert the new submodule name as a string in those occurrences, maintaining alphabetical order where applicable. Bear in mind that the submodule name should not have more than one word, should not contain underscores or uppercase letters, and a stub should be created for it that contributors should update in parallel. The stubtest tool from mypy will not work because we use ty ignores that mypy doesn’t recognize.
Adding a new configuration option#
This will not usually be done by a contributor, only a collaborator, because there should be evidence that the change is imperative or desirable in a dedicated GitHub discussions thread. After reviewing the pitch, the collaborator will step in and make necessary changes to the argument parser, documentation and internal machinery.
Adding a new contextual constant#
The name of the constant should be of the form UTILITYNAME_OPTIONALMETHODNAME_DEFAULT_ARGNAMEINCAPS if it acts as a dynamic default value, the
most common case by far. Note the uppercase, underscores and rigid format.
After verifying the integrity of the field name according to this metric, navigate to the following locations relative to the project root and complete the following:
asyncutils/format.json5
Find the submodule and add in part of the option name in the object under the appropriate pattern. If this causes there to be more than one option for a utility with no dedicated mapping having it as key, create one and merge the other in. If these guidelines sound vague, surrounding keys will help you. Bear in mind that because of organizational, readability, maintainability and consistency standards, the options should be in the same order as those in
context.pyi, as elaborated on below.Caution
The option name should be lowercase, as opposed to being fully capitalized like how you are recommended to access it.
Attention
Also remember to update the line numbers in the literalinclude directive referring to format.json5 in logging.rst.
asyncutils/context.pyi
Be sure to update the contextual constant count:
in the
all_contextual_constantsdocstring,within the
Contextfake dataclass body, andat the top level.
Keep alphabetical order within the submodule concerned, with submodules ordered alphabetically as well.
asyncutils/_internal/unparsed.py
There should be a massive dictionary called
Cthat contains the option names mapped to their factory defaults. Edit it accordingly.
Updating config.pyi#
Special care must be taken, since the Logging page depends on the exact line numbers at which specific symbols are declared here in the form of a literalinclude. The same applies for format.json5. Take a look at the current state of the site, and hopefully you can determine what the new line numbers should be accordingly.
Adding a documentation page#
Choose a format: .md or .rst. Though .md is easier to write, one may want .rst for its rich directive support that integrates seamlessly with Sphinx and allows for smoother redirection, though the MyST parser is improving to accommodate these.
Choose a location depending on how visible you wish the page to be:
docs/sourceor the project root.If the page is of paramount importance even to end users or people doing a read-through of the project, expand the README with a new section containing a summary and linking to the page on the bottom.
Update the relevant table of contents tree (toctree) in docs/source/index.rst. Do not move documents across the four different trees.
If copying from the root to the Read the Docs page is required during build, so that users can see it in both places, update .readthedocs.yaml by adding a cp (copy) statement in the post_create_environment job, following the syntax of its sibling commands.
Changing help messages for command-line arguments#
Remember not to indent the help strings when using multiline strings; keep them at the left margin such that they display correctly.
After all the changes, run .\scripts\genhelp.ps1 (Windows) or ./scripts/genhelp.sh (otherwise) from the repository root to regenerate the
help.rst file in the docs. Since relative paths are used, the current working directory very much matters.
Modifying the Makefile#
Remember to sync up the Makefile with make.bat and vice versa. If you wish to remove a target, it must have exceeded a previously declared
deprecation period, and been moved into a special section with a “Deprecated targets” header in the make help output.
Adding tests#
Make sure the name of the test function is prefixed with “test_” such that pytest correctly discovers it. Run pytest, selecting only that test, to verify the test is written correctly and your implementation is resilient against edge cases.
Tip
If you are puzzled as to how to write tests for your feature, you may simply try it out in the console and check its behaviour against your expectations. As long as you include the statements you entered in the console in your issue or PR description, the maintainer making the merge commit can write the tests for you.
See also
- This basic pytest how-to
includes assert statement usage and other fundamental guidelines, for those used to other frameworks like
unittest.
Before committing, run the whole test suite by entering the following command at the project root to check for regressions and update the relevant static badge in the README:
pytest --local-badge-output-dir assets --local-badge-generate status
If the tests are failing, do not commit the badge! Reviewers would assume your PR is ready for merging when you commit the badge, and may close the PR if it doesn’t appear with a passing status. Since codecov already handles coverage, there is no need to generate a coverage badge as well.
Note
The above snippet does require the pytest-local-badge plugin, but that should come packaged with the tests dependency group.
The test ought to go in the test source file corresponding to the submodule from which the feature can be publicly imported, even if its
implementation is spread across files, with the exceptions of the base and iterclasses submodules, whose tests I find inseparable with the logic for
tests for iters and decided to put them into tests/test_iters.py together.