diff --git a/.github/CONTRIBUTING.rst b/.github/CONTRIBUTING.rst new file mode 100644 index 00000000..aa6a9aee --- /dev/null +++ b/.github/CONTRIBUTING.rst @@ -0,0 +1,216 @@ +Contributing to atomium +====================== + +Thank you for considering spending your time on improving this project! +You can help by raising an issue to report a bug or suggest a new +feature, or by creating a pull request to add a new feature yourself +(subject to approval). + +Raising an Issue +---------------- + +The `GitHub issue +tracker `__ is the place +to either report a bug, or propose a new feature that you'd like to see +added. There will be a style guide for the text of the issue when you +open the tracker - just delete the feature request section if you are +reporting a bug, and *vice versa*. + +Pull Requests +------------- + +Pull requests are GitHub's mechanism for allowing willing contributors +to add features or make fixes themselves, without waiting for me to get +around to it. + +How do Pull Requests work? +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The first step is to fork the atomium repository to your own GitHub +account. This will give you your own copy of the repository, where you +can make changes that don't affect the original. + +The second step is to make your desired changes. To do this you need to +clone your new copy of atomium to your local machine, and to do this +you simply enter ``clone https://github.com/YOUR_USERNAME/atomium`` at +your terminal, and you will have a local copy to work with. + +Make commits at sensible points while making your changes, with +informative commit messages in the present tense. + +Once you are finished, push your changes to your forked repository on +GitHub. You are now ready to make the pull request. + +Find the 'New Pull Request' button and click it, and you will be given +an overview of the pull request. The default branch to merge into is +master, which is fine - I will manually change this to whatever the +current branch being worked on is at the time. That way your feature (if +accepted) will appear in the next release (you will be credited). + +Look over the changes one last time, and if it's all good, click 'create +pull request'. It then gets sent to me to look over, and either accept +and merge, close, or request changes. + + +What does and doesn't make a good Pull Request? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An ideal pull request for atomium is one that either adds a function (or +set of functions which carry out a single piece of functionality), or +modifies the minimal amount of code to fix one bug. One pull request, +one feature. + +If your pull request modifies large parts of atomium, changes the way the +core API works for the user, or adds an entirely new class, there's a +good chance it won't be merged. + +If the pull request resolves a current, open issue, there's a good +chance it will be accepted. If in doubt, asking beforehand is always a +good idea! + +Requirements +~~~~~~~~~~~~ + +Style +^^^^^ + +- Two lines between functions, three lines between classes. + +- Lines strictly no more than 80 characters long. This is less strict + in test files but still try to keep them below 80 characters. + +- underscore\_naming\_convention + +- Docstrings do not have a line break between the triple quote marks + and the start of the string. + +Documentation +^^^^^^^^^^^^^ + +If a function or class doesn't have documentation, it doesn't exist. Not +to the user anyway. All functions should begin with a docstring which +summarises what the function does, lists the parameters it takes, +outlines any exceptions it can raise, and specifies what the function +returns. + +The text should be in RST format. For example: + +.. code:: + + def calculate_hypotenuse(side1, side2): + """Takes the lengths of two sides of a right-angled triangle, and uses the + Pythagorean theorem to calculate the hypotenuse length. + + :param float side1: The length of the first side. + :param float side2: The length of the second side. + :raises ValueError: if either side is negative. + :rtype: ``float``""" + + if side1 < 0 or side2 < 0: + raise ValueError("Sides can't be negative") + return math.sqrt((side1 ** 2) + (side2 ** 2)) + +If in doubt, just look at the other functions. + +Tests +^^^^^ + +If a function doesn't have tests, the function doesn't work. atomium has +integration tests and unit tests. + +Suppose the hypotenuse function above already exists, and you want to +add a function for getting the distance between two points, using this +function. You might add the following: + +.. code:: + + def distance_between(point1, point2): + """Takes two (x, y) points and returns the distance between them. + + :param list point1: The first point in the form ``[x, y]``. + :param list point2: The first point in the form ``[x, y]``. + :rtype: ``float``""" + + delta_x = point2[0] - point1[0] + delta_y = point2[1] - point1[1] + return calculate_hypotenuse(delta_x, delta_y) + +How should this be tested? + +Unit Tests +'''''''''' + +Unit tests test a function *in isolation*. In this case, the unit test +would check that the function works but it should not execute +``calculate_hypoteneuse``! The test might look like this: + +.. code:: + + from unittest import TestCase + from unittest.mock import patch + + class DistanceTests(TestCase): + + @patch("calculate_hypotenuse") + def test_can_get_distance_between_points(self, mock_hyp): + point1 = [0, 0] + point2 = [4, 3] + mock_hyp.return_value = 5 + distance = distance_between(point1, point2) + mock_hyp.assert_called_with(point1, point2) + self.assertEqual(distance, 5) + +The ``calculate_hypoteneuse`` function is patched with a mock object +here. We set its return value and just ensure that it was called, and +that what it returns is what our function returns. + +Note that this way if ``calculate_hypoteneuse`` is broken, the tests for +``distance_between`` will still pass - they are isolated. + +Unit tests live in ``tests/unit``. Each class/collection of functions +gets its own test file, each function gets its own test class, with +different test functions for each possible use of the function. + +Again, see existing tests for numerous examples. + +Integration Tests +''''''''''''''''' + +Integration tests check that the code works when called as the user +would call it. Nothing is mocked or patched - this is a test that all +the functions work together to do what the user wants. + +If your pull request is to add a function that works 'under the hood' +and which the user never uses, you don't need to add an integration test +(the existing tests will cover it). If you've added user-facing code, it +does need a few lines. Just fine somewhere suitable in one of the +``tests/integration`` files and add it in - don't worry too much about +putting it in the right place as I move things around pretty often +anyway. + +So in this case, you might just add the line: + +.. code:: + + self.assertEqual(distance_between([0, 0], [3, 4]), 5) + + +Final Checks +^^^^^^^^^^^^ + +All tests should be run before submitting the pull request. + +Unit tests are run with: + +.. code:: + + $ python -m unittest discover tests/unit + + +Integration tests are run with: + +.. code:: + + $ python -m unittest discover tests/integration + + diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..05915584 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,21 @@ +### For Bug Reports + +#### Expected behaviour + + +#### Actual behaviour + + +#### Example code to reproduce + + +#### Python Version/Operating System + + + +### For Feature Requests + +#### Description of Feature + + +#### Proposed Example Code \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..2a066850 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +### Overview of New Code/Feature + + +### Example Code + + +### Checklist + +- [ ] I have read [CONTRIBUTING.rst](https://github.com/samirelanduk/atomium/blob/master/.github/CONTRIBUTING.rst). +- [ ] I have documented any new functions. +- [ ] I have added unit tests for the new code. +- [ ] I have added an integration test (if appropriate). +- [ ] I have run all tests. +- [ ] I am requesting to merge into the appropriate branch. diff --git a/README.rst b/README.rst index cabb81dd..fb063870 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,15 @@ .. |travis| image:: https://api.travis-ci.org/samirelanduk/atomium.svg?branch=0.7 +.. _travis: https://travis-ci.org/samirelanduk/atomium/ + .. |coveralls| image:: https://coveralls.io/repos/github/samirelanduk/atomium/badge.svg?branch=0.7 +.. _coveralls: https://coveralls.io/github/samirelanduk/atomium/ + .. |pypi| image:: https://img.shields.io/pypi/pyversions/atomium.svg -|travis| |coveralls| |pypi| +|travis|_ |coveralls|_ |pypi| atomium ======= @@ -36,8 +40,8 @@ atomium can be installed using pip: ``$ pip3 install atomium`` -atomium is written for Python 3, and does not support Python 2. It is currently -tested on Python 3.3 and above. +atomium is written for Python 3, and does not support Python 2. It currently +requires Python 3.5 and above. If you get permission errors, try using ``sudo``: @@ -66,8 +70,8 @@ automatically when it installs atomium. Overview -------- -atomium allows you to open .xyz files, manipulate the model within, and save -them as new .xyz files. +atomium allows you to open .pdb and .xyz files, manipulate the model within, +and save them as new files. From .xyz ~~~~~~~~~ @@ -94,6 +98,10 @@ from the RCSB over the internet using the PDB code: >>> pdb = atomium.pdb_from_file("1LOL.pdb") >>> pdb2 = atomium.fetch("5HVD") + >>> pdb.deposition_date() + datetime.date(2002, 5, 6) + >>> pdb.resolution() + 1.9 >>> pdb2.model() @@ -142,6 +150,11 @@ gyration: ``AtomicStructure.atoms`` returns all matching elements as a ``set`` while ``AtomicStructure.atom`` returns the first matching atom. +For pairwise comparisons, structures also have the +``AtomicStructure.pairwise_atoms`` generator which will yield all +unique atom pairs in the structure. These can obviously get very big indeed - a +5000 atom PDB file would have about 12 million unique pairs. + The atoms themselves have properties for their coordinates and elements, and also for finding the distance between them: @@ -298,6 +311,19 @@ The ``Xyz`` or ``Pdb`` object itself can also be saved: Changelog --------- +Release 0.7.0 +~~~~~~~~~~~~~ + +`2 November 2017` + +* PDBs with multiple occupancy can now be parsed correctly. +* Added pairwise atom generator. +* PDB parser now extracts resolution. +* Further speed increased to PDB parser. +* Miscellaneous bug fixes. +* Implemented Continuous Integration. + + Release 0.6.0 ~~~~~~~~~~~~~ diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 7f4e617f..4d4428e0 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -1,6 +1,19 @@ Changelog --------- +Release 0.7.0 +~~~~~~~~~~~~~ + +`2 November 2017` + +* PDBs with multiple occupancy can now be parsed correctly. +* Added pairwise atom generator. +* PDB parser now extracts resolution. +* Further speed increased to PDB parser. +* Miscellaneous bug fixes. +* Implemented Continuous Integration. + + Release 0.6.0 ~~~~~~~~~~~~~ diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst new file mode 100644 index 00000000..bdfe3bd7 --- /dev/null +++ b/docs/source/contributing.rst @@ -0,0 +1,214 @@ +Contributing to atomium +======================= + +Thank you for considering spending your time on improving this project! +You can help by raising an issue to report a bug or suggest a new +feature, or by creating a pull request to add a new feature yourself +(subject to approval). + +Raising an Issue +---------------- + +The `GitHub issue +tracker `__ is the place +to either report a bug, or propose a new feature that you'd like to see +added. There will be a style guide for the text of the issue when you +open the tracker - just delete the feature request section if you are +reporting a bug, and *vice versa*. + +Pull Requests +------------- + +Pull requests are GitHub's mechanism for allowing willing contributors +to add features or make fixes themselves, without waiting for me to get +around to it. + +How do Pull Requests work? +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The first step is to fork the atomium repository to your own GitHub +account. This will give you your own copy of the repository, where you +can make changes that don't affect the original. + +The second step is to make your desired changes. To do this you need to +clone your new copy of atomium to your local machine, and to do this +you simply enter ``clone https://github.com/YOUR_USERNAME/atomium`` at +your terminal, and you will have a local copy to work with. + +Make commits at sensible points while making your changes, with +informative commit messages in the present tense. + +Once you are finished, push your changes to your forked repository on +GitHub. You are now ready to make the pull request. + +Find the 'New Pull Request' button and click it, and you will be given +an overview of the pull request. The default branch to merge into is +master, which is fine - I will manually change this to whatever the +current branch being worked on is at the time. That way your feature (if +accepted) will appear in the next release (you will be credited). + +Look over the changes one last time, and if it's all good, click 'create +pull request'. It then gets sent to me to look over, and either accept +and merge, close, or request changes. + + +What does and doesn't make a good Pull Request? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An ideal pull request for atomium is one that either adds a function (or +set of functions which carry out a single piece of functionality), or +modifies the minimal amount of code to fix one bug. One pull request, +one feature. + +If your pull request modifies large parts of atomium, changes the way the +core API works for the user, or adds an entirely new class, there's a +good chance it won't be merged. + +If the pull request resolves a current, open issue, there's a good +chance it will be accepted. If in doubt, asking beforehand is always a +good idea! + +Requirements +~~~~~~~~~~~~ + +Style +^^^^^ + +- Two lines between functions, three lines between classes. + +- Lines strictly no more than 80 characters long. This is less strict + in test files but still try to keep them below 80 characters. + +- underscore\_naming\_convention + +- Docstrings do not have a line break between the triple quote marks + and the start of the string. + +Documentation +^^^^^^^^^^^^^ + +If a function or class doesn't have documentation, it doesn't exist. Not +to the user anyway. All functions should begin with a docstring which +summarises what the function does, lists the parameters it takes, +outlines any exceptions it can raise, and specifies what the function +returns. + +The text should be in RST format. For example: + +.. code:: + + def calculate_hypotenuse(side1, side2): + """Takes the lengths of two sides of a right-angled triangle, and uses the + Pythagorean theorem to calculate the hypotenuse length. + + :param float side1: The length of the first side. + :param float side2: The length of the second side. + :raises ValueError: if either side is negative. + :rtype: ``float``""" + + if side1 < 0 or side2 < 0: + raise ValueError("Sides can't be negative") + return math.sqrt((side1 ** 2) + (side2 ** 2)) + +If in doubt, just look at the other functions. + +Tests +^^^^^ + +If a function doesn't have tests, the function doesn't work. atomium has +integration tests and unit tests. + +Suppose the hypotenuse function above already exists, and you want to +add a function for getting the distance between two points, using this +function. You might add the following: + +.. code:: + + def distance_between(point1, point2): + """Takes two (x, y) points and returns the distance between them. + + :param list point1: The first point in the form ``[x, y]``. + :param list point2: The first point in the form ``[x, y]``. + :rtype: ``float``""" + + delta_x = point2[0] - point1[0] + delta_y = point2[1] - point1[1] + return calculate_hypotenuse(delta_x, delta_y) + +How should this be tested? + +Unit Tests +'''''''''' + +Unit tests test a function *in isolation*. In this case, the unit test +would check that the function works but it should not execute +``calculate_hypoteneuse``! The test might look like this: + +.. code:: + + from unittest import TestCase + from unittest.mock import patch + + class DistanceTests(TestCase): + + @patch("calculate_hypotenuse") + def test_can_get_distance_between_points(self, mock_hyp): + point1 = [0, 0] + point2 = [4, 3] + mock_hyp.return_value = 5 + distance = distance_between(point1, point2) + mock_hyp.assert_called_with(point1, point2) + self.assertEqual(distance, 5) + +The ``calculate_hypoteneuse`` function is patched with a mock object +here. We set its return value and just ensure that it was called, and +that what it returns is what our function returns. + +Note that this way if ``calculate_hypoteneuse`` is broken, the tests for +``distance_between`` will still pass - they are isolated. + +Unit tests live in ``tests/unit``. Each class/collection of functions +gets its own test file, each function gets its own test class, with +different test functions for each possible use of the function. + +Again, see existing tests for numerous examples. + +Integration Tests +''''''''''''''''' + +Integration tests check that the code works when called as the user +would call it. Nothing is mocked or patched - this is a test that all +the functions work together to do what the user wants. + +If your pull request is to add a function that works 'under the hood' +and which the user never uses, you don't need to add an integration test +(the existing tests will cover it). If you've added user-facing code, it +does need a few lines. Just fine somewhere suitable in one of the +``tests/integration`` files and add it in - don't worry too much about +putting it in the right place as I move things around pretty often +anyway. + +So in this case, you might just add the line: + +.. code:: + + self.assertEqual(distance_between([0, 0], [3, 4]), 5) + + +Final Checks +^^^^^^^^^^^^ + +All tests should be run before submitting the pull request. + +Unit tests are run with: + +.. code:: + + $ python -m unittest discover tests/unit + + +Integration tests are run with: + +.. code:: + + $ python -m unittest discover tests/integration diff --git a/docs/source/index.rst b/docs/source/index.rst index 2b63ee8e..d4cde1d3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,4 +23,5 @@ Table of Contents installing overview api + contributing changelog diff --git a/docs/source/installing.rst b/docs/source/installing.rst index 53ca9535..c1cd4a61 100644 --- a/docs/source/installing.rst +++ b/docs/source/installing.rst @@ -8,8 +8,8 @@ atomium can be installed using pip: ``$ pip3 install atomium`` -atomium is written for Python 3, and does not support Python 2. It is currently -tested on Python 3.3 and above. +atomium is written for Python 3, and does not support Python 2. It currently +requires Python 3.5 and above. If you get permission errors, try using ``sudo``: diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 395f0548..aa10b0ff 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -1,8 +1,8 @@ Overview -------- -atomium allows you to open .xyz files, manipulate the model within, and save -them as new .xyz files. +atomium allows you to open .pdb and .xyz files, manipulate the model within, +and save them as new files. From .xyz ~~~~~~~~~ @@ -29,6 +29,10 @@ from the RCSB over the internet using the PDB code: >>> pdb = atomium.pdb_from_file("1LOL.pdb") >>> pdb2 = atomium.fetch("5HVD") + >>> pdb.deposition_date() + datetime.date(2002, 5, 6) + >>> pdb.resolution() + 1.9 >>> pdb2.model() @@ -77,6 +81,11 @@ gyration: :py:meth:`~.AtomicStructure.atoms` returns all matching elements as a ``set`` while :py:meth:`~.AtomicStructure.atom` returns the first matching atom. +For pairwise comparisons, structures also have the +:py:meth:`~.AtomicStructure.pairwise_atoms` generator which will yield all +unique atom pairs in the structure. These can obviously get very big indeed - a +5000 atom PDB file would have about 12 million unique pairs. + The atoms themselves have properties for their coordinates and elements, and also for finding the distance between them: diff --git a/setup.py b/setup.py index 4746b7ce..10d3f45b 100644 --- a/setup.py +++ b/setup.py @@ -14,10 +14,6 @@ "License :: OSI Approved :: MIT License", "Topic :: Scientific/Engineering :: Chemistry", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.1", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", ],