Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
You can contribute in many ways:
Report bugs at https://github.com/glotaran/pyglotaran/issues.
If you are reporting a bug, please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
Look through the GitHub issues for bugs. Anything tagged with "bug" and "help wanted" is open to whoever wants to implement it.
Look through the GitHub issues for features. Anything tagged with "enhancement" and "help wanted" is open to whoever wants to implement it.
pyglotaran could always use more documentation, whether as part of the official pyglotaran docs, in docstrings, or even on the web in blog posts, articles, and such. If you are writing docstrings please use the NumPyDoc style to write them.
The best way to send feedback is to file an issue at https://github.com/glotaran/pyglotaran/issues.
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that contributions are welcome :)
Ready to contribute? Here's how to set up pyglotaran
for local development.
Fork the
pyglotaran
repo on GitHub.Clone your fork locally:
$ git clone https://github.com/<your_name_here>/pyglotaran.git
Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:
$ mkvirtualenv pyglotaran (pyglotaran)$ cd pyglotaran (pyglotaran)$ python -m pip install -r requirements_dev.txt (pyglotaran)$ pip install -e . --process-dependency-links
Install the
pre-commit
hooks, to automatically format and check your code:$ pre-commit install
Create a branch for local development:
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:
$ pre-commit run -a $ py.test
Or to run all at once:
$ tox
Commit your changes and push your branch to GitHub:
$ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature
Submit a pull request through the GitHub website.
Add the change referring the pull request (
(#<PR_nr>)
) tochangelog.md
. If you are in doubt in which section your pull request belongs, just ask a maintainer what they think where it belongs.
Note
By default pull requests will use the template located at .github/PULL_REQUEST_TEMPLATE.md
.
But we also provide custom tailored templates located inside of .github/PULL_REQUEST_TEMPLATE
.
Sadly the GitHub Web Interface doesn't provide an easy way to select them as it does for issue templates
(see this comment for more details).
To use them you need to add the following query parameters to the url when creating the pull request and hit enter:
- ✨ Feature PR:
?expand=1&template=feature_PR.md
- 🩹 Bug Fix PR:
?expand=1&template=bug_fix_PR
- 📚 Documentation PR:
?expand=1&template=docs_PR.md
Before you submit a pull request, check that it meets these guidelines:
- The pull request should include tests.
- If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring.
- The pull request should work for Python 3.8 and 3.9
Check your Github Actions
https://github.com/<your_name_here>/pyglotaran/actions
and make sure that the tests pass for all supported Python versions.
We use numpy style docstrings, which can also be autogenerated from function/method signatures by extensions for your editor.
Some extensions for popular editors are:
Note
If your pull request improves the docstring coverage (check pre-commit run -a interrogate
),
please raise the value of the interrogate setting fail-under
in
pyproject.toml.
That way the next person will improve the docstring coverage as well and
everyone can enjoy a better documentation.
Warning
As soon as all our docstrings are in proper shape we will enforce that it stays that way. If you want to check if your docstrings are fine you can use pydocstyle and darglint.
To run a subset of tests:
$ py.test tests.test_pyglotaran
Only maintainers are allowed to decide about deprecations, thus you should first open an issue and check back with them if they are ok with deprecating something.
To make deprecations as robust as possible and give users all needed information to adjust their code, we provide helper functions inside the module :mod:`glotaran.deprecation`.
.. currentmodule:: glotaran.deprecation.deprecation_utils
The functions you most likely want to use are
- :func:`deprecate` for functions, methods and classes
- :func:`warn_deprecated` for call arguments
- :func:`deprecate_module_attribute` for module attributes
- :func:`deprecate_submodule` for modules
- :func:`deprecate_dict_entry` for dict entries
- :func:`raise_deprecation_error` if the original behavior cannot be maintained
Those functions not only make it easier to deprecate something, but they also check that that deprecations will be removed when they are due and that at least the imports in the warning work. Thus all deprecations need to be tested.
Tests for deprecations should be placed in glotaran/deprecation/modules/test
which also
provides the test helper functions deprecation_warning_on_call_test_helper
and
changed_import_test_warn
.
Since the tests for deprecation are mainly for maintainability and not to test the
functionality (those tests should be in the appropriate place)
deprecation_warning_on_call_test_helper
will by default just test that a
GlotaranApiDeprecationWarning
was raised and ignore all raise Exception
s.
An exception to this rule is when adding back removed functionality
(which shouldn't happen in the first place but might), which should be
implemented in a file under glotaran/deprecation/modules
and filenames should be like the
relative import path from glotaran root, but with _
instead of .
.
E.g. glotaran.analysis.scheme
would map to analysis_scheme.py
The only exceptions to this rule are the root __init__.py
which
is named glotaran_root.py
and testing changed imports which should
be placed in test_changed_imports.py
.
Deprecating a function, method or class is as easy as adding the deprecate
decorator to it. Other decorators (e.g. @staticmethod
or @classmethod
)
should be placed both deprecate
in order to work.
from glotaran.deprecation import deprecate
@deprecate(
deprecated_qual_name_usage="glotaran.some_module.function_to_deprecate(filename)",
new_qual_name_usage='glotaran.some_module.new_function(filename, format_name="legacy")',
to_be_removed_in_version="0.6.0",
)
def function_to_deprecate(*args, **kwargs):
...
When deprecating a call argument you should use warn_deprecated
and set
the argument to deprecate to a default value (e.g. "deprecated"
) to check against.
Note that for this use case we need to set check_qual_names=(False, False)
which
will deactivate the import testing.
This might not always be possible, e.g. if the argument is positional only,
so it might make more sense to deprecate the whole callable, just discuss what to
do with our trusted maintainers.
from glotaran.deprecation import deprecate
def function_to_deprecate(args1, new_arg="new_default_behavior", deprecated_arg="deprecated", **kwargs):
if deprecated_arg != "deprecated":
warn_deprecated(
deprecated_qual_name_usage="deprecated_arg",
new_qual_name_usage='new_arg="legacy"',
to_be_removed_in_version="0.6.0",
check_qual_names=(False, False)
)
new_arg = "legacy"
...
Sometimes it might be necessary to remove an attribute (function, class, or constant)
from a module to prevent circular imports or just to streamline the API.
In those cases you would use deprecate_module_attribute
inside a module __getattr__
function definition. This will import the attribute from the new location and return it when
an import or use is requested.
def __getattr__(attribute_name: str):
from glotaran.deprecation import deprecate_module_attribute
if attribute_name == "deprecated_attribute":
return deprecate_module_attribute(
deprecated_qual_name="glotaran.old_package.deprecated_attribute",
new_qual_name="glotaran.new_package.new_attribute_name",
to_be_removed_in_version="0.6.0",
)
raise AttributeError(f"module {__name__} has no attribute {attribute_name}")
For a better logical structure, it might be needed to move modules to a different
location in the project. In those cases, you would use deprecate_submodule
,
which imports the module from the new location, add it to sys.modules
and
as an attribute to the parent package.
from glotaran.deprecation import deprecate_submodule
module_name = deprecate_submodule(
deprecated_module_name="glotaran.old_package.module_name",
new_module_name="glotaran.new_package.new_module_name",
to_be_removed_in_version="0.6.0",
)
The possible dict deprecation actions are:
- Swapping of keys
{"foo": 1} -> {"bar": 1}
(done viaswap_keys=("foo", "bar")
) - Replacing of matching values
{"foo": 1} -> {"foo": 2}
(done viareplace_rules=({"foo": 1}, {"foo": 2})
) - Replacing of matching values and swapping of keys
{"foo": 1} -> {"bar": 2}
(done viareplace_rules=({"foo": 1}, {"bar": 2})
)
For full examples have a look at the examples from the docstring (:func:`deprecate_dict_entry`).
In some cases deprecations cannot have a replacement with the original behavior maintained. This will be mostly the case when at this point in time and in the object hierarchy there isn't enough information available to calculate the appropriate values. Rather than using a 'dummy' value not to break the API, which could cause undefined behavior down the line, those cases should throw an error which informs the users about the new usage. In general this should only be used if it is unavoidable due to massive refactoring of the internal structure and tried to avoid by any means in a reasonable context.
If you have one of those rare cases you can use :func:`raise_deprecation_error`.
To test the consistency of results locally you need to clone the pyglotaran-examples and run them:
$ git clone https://github.com/glotaran/pyglotaran-examples
$ cd pyglotaran-examples
$ python scripts/run_examples.py run-all --headless
Note
Make sure you got the the latest version (git pull
) and are
on the correct branch for both pyglotaran
and pyglotaran-examples
.
The results from the examples will be saved in you home folder under pyglotaran_examples_results
.
Those results than will be compared to the 'gold standard' defined by the maintainers.
To test the result consistency run:
$ pytest .github/test_result_consistency.py
If needed this will clone the 'gold standard' results
to the folder comparison-results
, update them and test your current results against them.
A reminder for the maintainers on how to deploy.
Make sure all your changes are committed (including an entry in changelog.md
),
the version number only needs to be changed in glotaran/__init__.py
.
Then make a new release on GitHub and
give the tag a proper name, e.g. v0.3.0
since it might be included in a citation.
Github Actions will then deploy to PyPI if the tests pass.