Development Chores#
This file aims to detail guidelines for some monotonous tasks contributors to this library may need to complete in different recurring scenarios.
Preliminaries#
To start development, you must have git installed, since it is the version control system used for this project. Run the following:
git clone https://github.com/jonathandung/asyncutils.git
cd asyncutils
make setup
# installs prek v0.5.10, the latest version of uv, ruff and ty (as tools), then
# creates a virtual environment at .venv using uv and installs asyncutils in editable
# mode along with its development dependencies into the environment
. .venv/bin/activate # or how you would normally activate the uv environment in your
# preferred shell
Adding words to the incorrect spelling whitelist#
Edit assets/words.txt and check for regressions by running cspell ..
Bumping the version#
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.mdat the project root from the replace operation.A core developer will help you create a GitHub release with default release notes using
make rel. This will automatically trigger, among others, a stable Read the Docs build, a 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 said core developer tries their best to correct grammatical or spelling mistakes.
Cleaning artifacts and caches#
Safer: make clean; more aggressive: make clean-all.
Implementing a new utility#
Add proper stubs for it in the .pyi file and include it in the appropriate
<submodule>_all declaration in asyncutils/_internal/submodules.py. Also add
tests such that at least the required coverage is still met.
Adding a new submodule#
Search for ‘rwlocks’ (without the quotes) across all files in the repository, excluding asyncutils/rwlocks.py, asyncutils/_internal/types.pyi and docs/source/chores.rst. This should reveal all the locations where submodule names are referenced. Still check each one to see if it is a correct place.
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.
Add a test file for the new submodule importing all names from the submodule.
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.
For collaborators and those interested, this is the general procedure:
Edit the setup of the argument parser and the dictionary of defaults
Nin_internal/unparsed.py.Edit
format.json5by adding the key in the same order as the new help from the parser appears and a comment describing what it does, and take note of the line numbers where the pairs corresponding to logging configuration are shifted to.If the value of the configuration option is to be accessible by users at runtime, it should be in the form of a symbol in
config.Update
config.pyi, noting the line numbers at which the logging-related declarations appear.Edit the definition of
cfg_to_argv()and update the test suite to account for that, preserving round-trip conversion as promised.Update the literalinclude’s in logging.rst with the line numbers jotted down from steps 2 and 4.
Run
make docsand look at the help pages in the generated documentation as a sanity check.
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, much 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/format.hjson, asyncutils/format.toml, asyncutils/format.yaml
Update them accordingly.
asyncutils/context.pyi
Be sure to update the contextual constant count:
in the
all_contextual_constsdocstring,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 assigned to the name
Cthat contains the option names mapped to their factory defaults on line 5. Edit it accordingly.
Regenerating the lockfile after dependency updates#
Run make lock.
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 and friends. 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, you may want .rst for its rich directive support that integrates seamlessly with Sphinx and allows for smoother redirection.
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, add an entry to the mapping representing sources and respective targets for copy statements inlined in .readthedocs.yaml, maintaining alphabetical order.
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. Then run make docs.
Modifying the Makefile#
Remember to sync up the Makefile with make.bat in the same directory, 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. Finally, run make docs.
Adding tests#
Make sure the name of the test function is prefixed with “test_” such that
pytest correctly discovers it. Run pytest on each test individually
with the pytest-asyncio plugin instead of pytest-asyncio-cooperative (since its
benefits do not apply for singular tests), to verify the test is written correctly and
your implementation is resilient against edge cases. Do not drop edge cases from tests
because they do not pass; try your best to fix the code instead. If you are unable to
do so, please ask for help in the issue or PR discussion.
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, and include the statements you entered in the console in your issue or PR description. The maintainer who is to merge your PR can write the tests for you.
See also
- This basic pytest how-to
assert statement usage and other fundamental guidelines, for those more 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 badges in the readme:
make badges
If the tests are failing, do not commit the badges, since reviewers would assume your PR is ready for merging when you do so, and may close the PR just because they don’t appear with a passing status.