Skip to content

Contributing#

Thank you for considering contributing to pysmo. We welcome your contribution! To make the process as seamless as possible, please read through this guide carefully.

What are Contributions?#

Broadly speaking, contributions fall into the following categories:

  • Questions: the easiest way to contribute to pysmo is ask for help if something is unclear or you get stuck. Questions provide important feedback that helps us determine what parts of pysmo might need attention, so we are always keen to help. Development of pysmo happens publicly on GitHub, and we kindly request that you ask your questions there too. You may either create a new issue or start a discussion. Please also feel free to answer any questions (including your own, should you figure it out!) to help out other users.
  • Bug reports: if something needs fixing in psymo we definitely want to hear about it! Please create a new issue, or add relevant information to an issue if one already exists for the bug you found. Bonus points if you also provide a patch that fixes the bug!
  • Enhancements: we reckon that if you are able to use pysmo, you are also able to write code that can become part of pysmo. These can be things such as useful functions, new pysmo types, or a cool example you would like to show off in the pysmo gallery.

Contributing towards making pysmo better does not necessarily mean you need to submit code for inclusion in pysmo. However, if you do want to submit code, we ask that you read the information and follow steps outlined in the remaining sections on this page.

Development Workflow#

As mentioned in the previous chapter, development of pysmo happens on GitHub. Therefore, code submitted via a pull request has a much greater chance to be included in pysmo than e.g. sending a patch to us via email.

Typically you would first fork pysmo and configure git to sync your fork with the upstream repository. Then you create a new feature branch where you add your code:

$ git checkout -b my_cool_feature

This feature branch is what you will also use to submit a pull request to pysmo when you are finished implementing your changes. Before submitting a pull request, please make sure you did the following:

  • Write code that adheres to the PEP 8 style guide.
  • Include unit tests with your submission. If you need help with that, feel free to contact us.
  • Run a code linter on your code and verify the unit tests all still pass. Essentially this simply means make tests still passes without errors.
  • In order to keep a clean git history, please rebase the pysmo master branch onto your feature branch, and squash all commits into a single, well documented commit. This avoids having commits such as "fix typo", or "undo changes" finding their way into the git log. Note that this should only be done before submitting the initial pull request.

Once a pull request is submitted the following happens:

  • The unit tests are executed automatically in clean Python environments via GitHub Actions. Please verify all tests still pass. If the tests pass on your development machine but fail in the pull request, you may have e.g. forgotten to add a dependency to pyproject.toml that you happened to already have installed on your machine.
  • Similarly, a build of the documentation from the code as it exists in the pull request is also automatically triggered. Please follow the link that appears in the pull request on GitHub and verify the documentation is as expected.
  • We will then review your submission, and hopefully be able to include it in pysmo!

What should be included#

A good contribution should contain code that is well written and documented, as well as provide meaningful test cases to ensure consistent and bug-free behaviour.

A lot of the pysmo documentation is generated from the docstrings within the .py files. In many instances this means that a contribution may consist of only two files: the code itself and an associated test file.

Example contribution#

In this subsection, we show an example contribution consisting of a simple function.

Creating the function#

We plan to submit a normalize function, which normalizes the seismogram with its absolute max value and returns the new normalized seismogram. We first create a new feature branch in our git repository for the development process. It is common practice to chose a branch name that is descriptive of the feature being implemented. Here we create and switch to the new branch in a single step:

$ git checkout -b func-normalize

Pysmo functions belong in the pysmo/functions.py file, so we add the following (highlighted) content:

pysmo/functions.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from pysmo import Seismogram, MiniSeismogram, Location
from pysmo.lib.functions import _azdist, _detrend, _normalize, _resample
from pysmo.lib.defaults import DEFAULT_ELLPS


def normalize(seismogram: Seismogram) -> MiniSeismogram:
    """Normalize a seismogram with its absolute max value

    Parameters:
        seismogram: Seismogram object.

    Returns:
        Normalized seismogram.

    Note:
        This function is also available as a method in the
        [MiniSeismogram][pysmo.classes.mini.MiniSeismogram]
        class. Thus, if you intend normalizing data of a
        MiniSeismogram object **in-place**, instead of
        writing:

        ```python
        my_seis.data = normalize(my_seis)
        ```

        you should instead use:

        ```python
        my_seis.normalize()
        ```

    Examples:
        >>> import numpy as np
        >>> from pysmo import SAC, normalize
        >>> original_seis = SAC.from_file('testfile.sac').seismogram
        >>> normalized_seis = normalize(original_seis)
        >>> assert np.max(normalized_seis.data) <= 1
        True
    """
    clone = MiniSeismogram.clone(seismogram, skip_data=True)
    clone.data = _normalize(seismogram)  # (1)!
    return clone


def detrend(seismogram: Seismogram) -> MiniSeismogram:
    """Remove linear and/or constant trends from a seismogram.
  1. The actual implementation of the normalize function lives in the file pysmo/lib/functions.py (imported in line 9), so that it's code can be shared with a corresponding method in the MiniSeismogram class.

Note

Notice that the code is making use of type hinting and pysmo types covered in previous sections. The docstring is formatted according to the google style.

To ensure that this newly created function can be imported correctly, it must be added to the pysmo __init__.py file in two places. First, it needs to be imported from the .py file itself:

pysmo/__init__.py
41
42
43
44
45
46
47
48
49
from pysmo.functions import (
    normalize,
    detrend,
    resample,
    plotseis,
    azimuth,
    backazimuth,
    distance,
)

Then it must be added to the __all__ list:

pysmo/__init__.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
__all__ = [
    "Location",
    "Hypocenter",
    "Seismogram",
    "Station",
    "Event",
    "SAC",
    "MiniSeismogram",
    "MiniLocation",
    "MiniStation",
    "MiniHypocenter",
    "MiniEvent",
    "normalize",
    "detrend",
    "resample",
    "plotseis",
    "azimuth",
    "backazimuth",
    "distance",
]

Creating a unit test#

We also need to create an associated test file to ensure that the function returns a correct output. In most instances, it actually makes sense to write the test case first (test-driven development).

The tests in pysmo are in the tests folder, where we keep the structure somewhat similar to the pysmo source in the pysmo folder. Hence we add the following content to the tests/test_functions.py file:

tests/test_functions.py
31
32
33
34
35
36
37
class TestSeismogramFunctions:
    def test_normalize(self, seismogram: Seismogram) -> None:
        """Normalize data with its absolute maximum value"""
        from pysmo import normalize

        normalized_seis = normalize(seismogram)
        assert np.max(normalized_seis.data) <= 1

Important

The filename may not be arbitrary here: if you create a new file, its name must begin with test!

Pysmo tests are written for, and executed with pytest. Test data such as the seismograms used in our example above are defined in the file tests/conftest.py. These fixtures provide a consistent (disposable) context to run tests with. Running

$ make tests

will run all tests in the tests folder. A single test file may be executed by running

$ poetry run pytest tests/test_functions.py

Formatting and linting#

Pysmo code is formatted with black. It can be applied by running make format. To see if anything else needs changing after that we run make lint.

Warning

If make lint doesn't pass in your working environment, the automatic tests on GitHub will also fail. Please make sure make lint runs without errors before submitting a pull request to the pysmo repository!

Pushing the code#

When we are happy with our code (and all tests pass without errors), we are ready to push our code to GitHub. It is always a good idea to run git status before doing anything else with git. This allows e.g. adding files that should not be tracked by git to the .gitignore file. The code can then be pushed to a new branch on GitHub (the branch func-normalize only exists on our development machine) as follows:

$ git add .
$ git commit
$ git push origin -u func-normalize

The new branch is now on GitHub, where we are offered the option to submit a pull request via the UI.

Hint

There are git UIs that make git tasks a lot easier (even for experienced developers). You may therefore consider using something like Lazygit or GitKraken.