Thanks for taking the time to contribute! 🙇♀️🙇♂️ Every little bit of help counts!
How to make your first contribution:
- 1. Create a GitHub Account
- 2. Find an Issue to Work On
- 3. Install Git
- 4. Fork the Project
- 5. Clone your Fork
- 6. Create a New Branch
- 7. Run brutils Locally
- 8. Make your Changes
- 9. Test your Changes
- 10. Update READMEs
- 11. Commit and push your Changes
- 12. Add changelog Entries
- 13. Create a GitHub PR
- 14. Update your branch if Needed.
Ensure you have a GitHub account and you are logged in.
Visit the brutils issues page and find an issue that interests you and hasn't been assigned yet.
Leave a comment in the issue with "bora!". A bot will assign the issue to you. Once assigned, proceed to the next step.
Feel free to ask any questions in the issue's page before or during the development process.
When starting to contribute, it is advised to handle one issue at a time. This ensures others have the chance to collaborate and avoids inactive resources.
Make sure you have [Git installed][install-git].
Clone your fork locally.
Go into the brutils folder:
$ cd brutils-python
>
And create a new branch with the issue number you’re working on:
$ git checkout -b <issue_number>
>
Example:
$ git checkout -b 386
Switched to a new branch '386'
>
Create a virtualenv for brutils and activate it:
$ make shell
Spawning shell within ...-py3.x
emulate bash -c '. .../bin/activate'
To test if the virtual environment is correctly activated, run:
$ poetry env inf
Virtualenv
Python: 3.x.y
Implementation: CPython
...
Note: You need to run make shell
every time you open a new terminal window/tab.
Install the dependencies:
$ make install
git config --local core.hooksPath .githooks/
chmod -R +x .githooks
Installing dependencies from lock file
...
Now, you can use it in the same way described in the README.md file.
Run tests using the following command:
$ make test
make test
test__is_valid_mercosul (license_plate.test_is_valid.TestIsValidMercosul.test__is_valid_mercosul) ... ok
test__is_valid_old_format (license_plate.test_is_valid.TestIsValidOldFormat.test__is_valid_old_format) ... ok
....
----------------------------------------------------------------------
Ran XX tests in 0.000s
OK
Now it’s time to implement your changes.
Check the issue description for instructions or ideas in the section “Describe alternatives you considered.” Make sure your changes resolve everything mentioned in the issue.
We document our code using docstrings. All modules, classes, functions, and methods should have docstrings. Your changes should reflect updated docstrings, especially if any parameters were changed.
All docstrings must be written in English. You can use tools like Google Translate or ChatGPT for help. We’ll suggest changes to the translation if needed.
We follow this pattern for docstring consistency:
class Example:
"""
Explain the purpose of the class
Attributes:
x[dict]: Short explanation here
y[type, optional]: Short explanation here
"""
def __init__(self, x, y=None):
self.x = x
self.y = y
def foobar(self, w):
"""
Purpose of the function
Args:
name[type]: Short explanation here
Returns:
type: Short explanation here
Example:
>>> command 1
output 1
>>> command 2
output 2
"""
...
return value
Example:
def format_cep(cep): # type: (str) -> str | None
"""
Formats a Brazilian CEP (Postal Code) into a standard format.
This function takes a CEP (Postal Code) as input and, if it is a valid
8-digit CEP, formats it into the standard "12345-678" format.
Args:
cep (str): The input CEP (Postal Code) to be formatted.
Returns:
str: The formatted CEP in the "12345-678" format if it's valid,
None if it's not valid.
Example:
>>> format_cep("12345678")
"12345-678"
>>> format_cep("12345")
None
"""
return f"{cep[:5]}-{cep[5:8]}" if is_valid(cep) else None
One last thing to keep in mind while self-documenting code with docstrings that you can ignore docstrings in property decorators and magic methods.
Make sure you have created the necessary tests for every new change you made.
Run all the tests with make test
and make sure that they all passed.
PRs will not be merged if there is any test missing or failing.
Open an interactive environment to manually test your changes:
$ python
Python 3.x.y ...
Type "help", "copyright", "credits" or "license" for more information.
>>> # Test your changes here!
### 10. Update READMEs
Update the `brutils-python/README.md` and `brutils-python/README_EN.md` files with your changes.
These files are essential for the library’s documentation, helping users understand how to use the provided features. Therefore, it’s important to always keep them up to date.
• The brutils-python/README_EN.md file contains the documentation in English, and you can use the content already described in the docstring directly.
• The brutils-python/README.md file contains the documentation in Portuguese. For this one, simply translate the function’s docstring.
If you need help translating into Portuguese, tools like Google Translate or ChatGPT can assist. Don’t worry about potential translation mistakes, as we will suggest adjustments when necessary.
Example in Portuguese (README.md):
````md
### format_cep
Esta função recebe um CEP como entrada e, se for um CEP válido de 8 dígitos, formata-o no padrão "12345-678".
Argumentos:
- cep (str): O CEP a ser formatado.
Retorna:
- str: O CEP formatado no padrão "12345-678" se for válido, ou
None se não for válido.
Exemplo:
```python
>>> from brutils import format_cep
>>> format_cep('01310200')
'01310-200'
>>> format_cep("12345678")
"12345-678"
>>> format_cep("12345")
None
Example in English (README_EN.md):
````md
### format_cep
This function takes a CEP (Postal Code) as input and, if it is a valid
8-digit CEP, formats it into the standard "12345-678" format.
Args:
- cep (str): The input CEP (Postal Code) to be formatted.
Returns:
- str: The formatted CEP in the "12345-678" format if it's valid,
None if it's not valid.
Example:
```python
>>> from brutils import format_cep
>>> format_cep('01310200')
'01310-200'
>>> format_cep("12345678")
"12345-678"
>>> format_cep("12345")
None
```
Format your code by running the command:
$ make format
...
Exemple:
$ make format
31 files left unchanged
All checks passed!
Commit your changes:
$ git commit -a -m "<commit_message>"
...
Example:
$ git push --set-upstream origin 386
Running pre-push hook checks
All checks passed!
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 10 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 2.36 KiB | 2.36 MiB/s, done.
Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
remote:
remote: Create a pull request for '386' on GitHub by visiting:
remote: https://github.com/brazilian-utils/brutils-python/pull/new/386
remote:
To github.com:brazilian-utils/brutils-python.git
* [new branch] 386 -> 386
Make the necessary changes and commits and push them when ready.
A changelog is a file that contains a chronologically organized list of notable changes for each version of a project.
To make it easier for users and contributors to see exactly what notable changes have been made between each release (or version) of the project.
People. Whether they are consumers or developers, the end users of software are human beings who care about what’s in the software. When the software changes, people want to know why and how.
The brutils changelog is available at CHANGELOG.md.
- Changelogs are for humans, not machines.
- There should be an entry for every version.
- The same types of changes should be grouped together.
- Versions and sections should be linkable.
- The most recent version comes first.
- The release date of each version is displayed.
-
Security fixes: These should be documented with the type labeled as “security” to alert users to resolved security issues. Example: “Fixed a critical vulnerability that allowed remote code execution.”
-
User-facing changes: Changes that directly affect how users interact with the software, including new features, changes to existing features, or UI improvements. Example: “Added a new filter option on the results page to make searching easier.”
-
Significant performance improvements: Should be recorded when they result in noticeable improvements in speed or efficiency that impact the user experience. Example: “The home page loading time was reduced by 40% after backend optimization.”
-
Changes affecting compatibility: Adjustments to maintain compatibility with other tools, systems, or versions. Example: “Updated library X to version 2.0 to support the new version of Python.”
-
Changes to the public API: Changes that affect how developers interact with the public API, such as adding new routes or modifying existing ones. Example: “Added a new /api/v1/users route for user management.”
-
Dependency changes: Updates or changes to the project’s dependencies that may affect software behavior or compatibility. Example: “Updated dependency package Y to version 3.1.4, which includes important security fixes.”
Although the changelog is a valuable tool for documenting changes, some information should not be included. Here are some examples of what should not appear in the changelog:
-
Internal Code Changes: Changes that do not affect the end-user experience, such as internal refactoring that does not alter functionality, need not be documented. Example: “Refactored internal functions” or “Fixed inconsistent tests.”
-
Non-notable performance improvements: Performance improvements that do not result in noticeable changes or clear benefits for the end user do not need to be included. Example: “Optimized internal algorithms.”
-
Minor bug fixes: Bug fixes that do not impact the general use or end-user experience can be omitted. Example: “Fixed a small typo in the code.”
-
Documentation-only changes: Changes that affect only the documentation, without modifying the software behavior, usually don’t need to be included. Example: “Updated README.md to reflect new dependencies.”
-
Excessive technical details: Overly technical information that is irrelevant to the end user or does not provide context on the impact of the change should be avoided. Example: “Changed memory management in class X.”
-
Maintainer entries: Changes related only to the development or internal maintenance process, such as CI/CD tool configuration adjustments, are generally not relevant for the changelog. Example: “Updated GitHub Actions configuration.”
-
A bug introduced and fixed in the same release does not need a changelog entry.
Avoid including this information to keep the changelog focused and useful for project users and contributors.
A good changelog entry should be both descriptive and concise. It should explain the change to a reader with no prior context about the change. If it’s hard to be both concise and descriptive, lean toward being more descriptive.
- Bad : Go to the project order.
- Good: Display the user’s starred projects at the top of the “Go to project” dropdown.
The first example provides no context on where the change was made, why it was made, or how it benefits the user.
- Bad: Copy (some text) to the clipboard.
- Good: Update the tooltip of “Copy to clipboard” to indicate what is being copied.
Again, the first example is too vague and provides no context.
- Bad: Fix and improve CSS and HTML issues in the mini pipeline graph and builds dropdown.
- Good: Fix tooltips and hover states in the mini pipeline graph and builds dropdown.
The first example is too focused on implementation details. Users don’t care that we changed CSS and HTML, they care about the final result of those changes.
- Bad: Remove null values in the Commit object array returned by find_commits_by_message_with_elastic.
- Good: Fix 500 errors caused by Elasticsearch results referencing commits already collected by the garbage collector.
The first example focuses on how we fixed something, not on what was fixed. The rewritten version clearly describes the final benefit to the user (fewer 500 errors) and when it happens (when searching for commits with Elasticsearch).
Use your best judgment and try to put yourself in the position of someone reading the compiled changelog. Does this entry add value? Does it provide context on where and why the change was made?
The changelog is available in the CHANGELOG.md file.
First, you need to identify the type of your change. Types of changes:
Added
for new features.Changed
for changes to existing features.Deprecated
for features that will soon be removed.Fixed
for any bug fixes.Removed
for features that were removed.Security
in case of vulnerabilities.
You should always add new entries to the changelog in the Unreleased
section. At the time of release, we will move the changes from the Unreleased
section to a new version section.
So, within the Unreleased
section, you should add your entry to the appropriate section by type. If there is no section yet for the type of your change, you should add one.
Let’s see some examples. Suppose you have a new Fixed
change to add, and the current CHANGELOG.md file looks like this:
## [Unreleased]
### Added
- Utility `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358)
### Changed
- Utility `fmt_voter_id` renamed to `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221)
You would need to add a new Fixed
section and include your new entry there:
## [Unreleased]
### Added
- Utility `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358)
### Changed
- Utility `fmt_voter_id` renamed to `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221)
### Fixed
- My changelog message here. [#<issue_number>](<issue_link>)
Note that the order of sections by type matters. We have a lint that checks this, so the sections must be ordered alphabetically. First Added
, then Changed
, third Deprecated
, and so on.
Now, let’s say you have another entry to add, and its type is Added
. Since we already have a section for that, you should just add a new line:
## [Unreleased]
### Added
- Utility `get_address_from_cep` [#358](https://github.com/brazilian-utils/brutils-python/pull/358)
- My other changelog message here. [#<issue_number>](<issue_link>)
### Changed
- Utility `fmt_voter_id` renamed to `format_voter_id` [#221](https://github.com/brazilian-utils/brutils-python/issues/221)
### Fixed
- My changelog message here. [#<issue_number>](<issue_link>)
This content is based on the Keep a Changelog site, as we follow its guidelines.
Ensure your branch is up to date with main.
Here you will find how to deploy a new production version of brutils:
To create the issue, you can use the feature template, naming the issue Release v. Example
The name of the branch created for the release is related to the Issue number, as shown in this example
Increment the version number, following the Semantic Versioning,
in the pyproject.toml
file: https://github.com/brazilian-utils/brutils-python/blob/main/pyproject.toml#L3
Add a title for the new version with the new number and the current date, as seen in this example.
And add the version links, like this example
Create a PR named Release v<version>
containing the two changes mentioned above. In the description of the Pull Request, add the modified section of the changelog.
Once the PR is accepted and passes all checks, merge it.
The new version release in production is done automatically when a new release is created on GitHub.
- Fill in the
tag version
should bev<version>
(e.gv2.0.0
). - Fill in the
release title
should be the same as tag version (e.gv2.0.0
). - Fill in the
release description
should be the content copied from the CHANGELOG.md file from the corresponding version section.
Real examples are available at: https://github.com/brazilian-utils/brutils-python/releases
When the GitHub deployment is completed, the new version will also be automatically released on PyPI. Download the new brutils version from PyPI and test to ensure everything is working as expected.