The following is a guide for contributing to Uplink. Thanks for taking the time to improve an open source project!
This guide is a work in progress. So, if you have suggestions for improving the contributors' experience, the release process, or really anything concerning the source code's management, please feel free to contact @prkumar directly.
We use the GitHub issue tracker for wrangling bug reports and feature requests. Before you open an issue, please do a quick search against both closed and open issues to ensure that the defect or feature request has not been previously reported or addressed.
We use GitHub milestones to associate approved features and verified defects to their respective target release versions (e.g., v1.0.0
or v0.x
). Also, we use GitHub projects to group issues by long-term enhancement efforts. (e.g., Support asynchronous requests).
As we're in initial development, the project is accepting feature requests! To request a feature, open a GitHub issue, summarize the enhancement, and add the feature request label.
Install all development dependencies using:
pipenv install --dev
pipenv run pre-commit install
If you are unfamiliar with pipenv but are comfortable with virtualenvs, you can alternatively run pip install pipenv
inside the virtualenv you are already using then invoke the commands from above. This will setup your virtualenv correctly.
Before submitting a pull request, run all tests with tox:
tox
To find a feature or bug to work on, checkout the open GitHub issues with the label "help wanted". Once you have found an issue that you'd like to work on, add a comment to the issue expressing your interest in developing the feature or fixing the bug, and mention @prkumar in your comment.
Moreover, changes to the source code typically address one of the following:
And, their development typically follows this workflow:
- Fork the repository if you don't have write-access.
- Make your changes, adhering to the style guide.
- Add or update tests.
- Update documentation, if necessary.
- Add yourself to the
authors
list inpyproject.toml
. - When your changes are ready for review, open a pull request.
- Merge changes.
In this section, we'll outline the various development stages of Uplink. And, as the development process and branching model go hand-in-hand, we'll also cover how our branching workflow ties into each stage.
To start off, let's address the two, everlasting branches: stable
and master
.
As the production branch, this branch should be the most stable branch. Nominally, stable
strictly contains merge commits, each tagged with a release version (e.g, v0.1.0
). See releases for more on how we handle the releasing process.
This branch contains work-in-progress for the next immediate release.
Subject to inclusion in some future release, features are logically cohesive units of work that add value to Uplink. In like manner, fixing a minor bug adds value by addressing a value destroying characteristic of the code, a defect.
Further, a minor bug is a defect that we can wait to patch in a coming minor or major release. A defect that requires immediate attention needs a critical bug fix.
Development of a feature or a minor bug fix should happen on a feature branch, which contains work for the next or some distant release. To start a feature branch, branch off of master
. Preferably, prefix the branch name with feature/
(or feature/v{version}/
, where {version} is the feature's target release version -- e.g., feature/v1.0.0/*
) for clarity. Make your changes on this branch, then when ready to merge, open a pull request against master
.
Importantly, if your changes are not targeted for the next immediate release, keep them on the feature branch until master
is bumped to the target version. However, you may open a pull request beforehand.
Also, after we have merged your changes into master
, you should delete your feature branch, to keep the Git repository uncluttered.
Critical bugs are defects affecting the latest released version of Uplink and require immediate action (i.e., we can't wait and patch the defect in a coming major or minor release). We assign the label "critical" to GitHub issues tracking critical bugs.
To address a critical bug, we need to:
- Create a hotfix branch off
stable
. - Increment the patch number.
- Implement a fix.
- Open pull request against
master
, and another againststable
.
Once we're ready to begin the release process, we'll create a release branch off an appropriate commit of master
. The name of a release branch should follow the pattern release/v{version}
, where {version}
is the target release version number (e.g., release/v1.0.0
).
Once the release branch is merged into stable
, we consider the release completed. However, up until this point, we can make necessary changes to the release branch, while normal feature development continues on master
.
When merging the release branch into stable
, perform an explicit, non fast-forward merge. Then, on the merge commit in stable
, create a tag named v{version}
, where {version}
is the target release version number (e.g., v1.0.0
). Tagging the commit prompts our CI pipeline to deploy the latest release to PyPI.
Notably, before removing a release branch, we'll need to merge the branch into master
to incorporate commits made after the release branch was cut. Moreover, once a release branch is cut, we need to bump the version number on master
.
Depending on the type of change you are making, the branching model may require merging your work into one or two target branches (typically one is master
). Be sure to open a pull request for each target branch.
- Open a pull request (PR) to merge your forked branch, the candidate, into a base branch of this repository.
- Add Raj (
prkumar
) as a reviewer. - If your PR fails the CI check, investigate the build log for cause of failure, address locally, and update the candidate branch. Repeat this step until the PR passes the CI check.
- If your PR fails the Codecov check, check the PR's Codecov report to identify modules experiencing a test coverage drop. Improve testing locally, then update the candidate branch.
- Once all checks have passed and the assigned reviewers have approved, a maintainer will merge your pull requests into the base branch by selecting "Merge Pull Request" (i.e., a
--no-ff
merge). - If the CI build for the merge commit fails, you should revert the merge commit, address the issue locally, update the candidate branch, then revisit step 3.
We use the unit testing framework pytest
. Unit and integrations tests are kept under the tests
directory, written in Python modules that match the filename pattern test_*.py
.
Notably, the conftest.py
files define several pytest fixtures, for injecting a mock instance of an interface (defined in uplink.interfaces
) or utility class (defined in uplink.helpers
) into your tests.
As a rule of thumb, an integration test should not exercise an actual API -- i.e., a mocked HTTP client should be used instead. This guideline facilitates efficient CI builds, produces side-effect free tests, and makes everyone happy 🤗.
To that end, the following pytest fixtures are available when writing integration tests:
A fake HTTP client adapter that can be passed into the client
constructor parameter of any Consumer
subclass:
api = MyConsumer(base_url=..., client=mock_client)
Further, this fixture keeps a history
of "requests", which is helpful when we want to assert what the server should have received:
api = MyConsumer(base_url=..., client=mock_client)
# Makes a "mocked" HTTP request
api.get_user("prkumar")
# Retrieve details of the above request
request = mock_client.history[-1]
assert "GET" == request.method, "The HTTP Method should be GET"
Check out the definition of RequestInvocation
in tests/integration/__init__.py
for the full set of exposed request attributes available for assertion.
At minimum, most integrations tests require the mock_client
fixture.
An HTTP response builder that can creates a fake response to be returned by mock_client
:
# Mock user response
mock_response.with_json({"id": 123, "username": "prkumar"})
mock_client.with_response(mock_response)
api = MyConsumer(base_url=..., client=mock_client)
# Verify user is returned
response = api.get_user("prkumar")
assert {"id": 123, "username": "prkumar"} == response.json()
To maintain a consistent code style with the rest of Uplink, follow the Google Python Style Guide.
Notably, we use a Sphinx plugin that can parse docstrings adherent to this style. Checkout this page for examples of Google Python Style Guide docstrings.