Note: While this section describes an ideal style, we know that parts of MetaBrainz projects do not follow these guidelines exactly. We are pragmatic in our application of the guidelines and try our best to follow them, but there will be deviations. When in doubt, follow the guidelines rather than copying existing code.
As most of the other projects written in Python, we use PEP 8. We change some of the recomendations:
- Maximum line length (79 characters). The general limit we have is somewhere around 120-130.
Recommended video: "Beyond PEP 8 -- Best practices for beautiful intelligible code" by Raymond Hettinger at PyCon 2015, which talks about the famous P versus NP problem.
If the project has a pylintrc
file, you can use pylint to check for potential style violations:
pylint --rcfile=pylintrc
The general idea is to make the code within a project consistent and easy to interpret (for humans).
Documentation helps us understand what a function does without having to read its implementation.
Include docstrings in functions. This should say what the function does, and its inputs and outputs or possible failure states (exceptions).
If a function is very short (say, less than 5 lines) and its name documents what it does, a docstring may not be necessary.
Unless otherwise stated, we use "Google-style" docstrings: https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings.
An example of a docstring for a regular method:
def frob(foo, bar=None):
"""Frob a foo, with an optional bar operation.
An optional, more detailed description of this function if needed.
Args:
foo: The foo to be frobbed.
bar: If set, a callback to a bar operation to be applied after
the foo is frobbed.
Returns:
The new foo, after frobbing and baring.
Raises:
ValueError if the foo cannot be frobbed.
Take note that sentences always start with a capital letter and end with a .
For flask application we use Sphinx with autohttp.flask to generate documentation for webserver views. (See an example for AcousticBrainz at http://acousticbrainz.readthedocs.io/)
View docstrings should be formatted to include query format and an example of the response:
"""Get low-level data for many recordings at once.
**Example response**:
.. sourcecode:: json
{"mbid1": {"some": "json"}}
:query recording_ids: *Required.* A list of recording MBIDs to retrieve
Takes the form `mbid[:offset];mbid[:offset]`. Offsets are optional, and should
be >= 0
:resheader Content-Type: *application/json*
"""
For projects which use sqlalchemy-core, we use sqlalchemy.text
to write prepared statements. SQL keywords
should be in upper-case and right aligned. For example:
query = sqlalchemy.text("""
SELECT id,
display_name,
email,
created,
musicbrainz_id,
show_gravatar
FROM "user"
WHERE musicbrainz_id IN :musicbrainz_usernames
""")
result = connection.execute(query, {'musicbrainz_usernames': tuple(usernames)})
You should write tests for all new code that you write. If you update code, make sure that tests still pass, and write/remove tests as necessary for any change in functionality.
Tests for a module (package/module.py
) go in a test
package - package/test/test_module.py
.
Try and isolate tests as much as possible. If you have a utility function which transforms or validates an input, you can test this in isolation.
Web server tests don't need to write data to the database. Instead, you can use a mock to verify that the call to the database was made:
@mock.patch("db.data.load_high_level")
def test_hl_numerical_offset(self, hl):
hl.return_value = {}
resp = self.client.get("/api/v1/%s/high-level?n=3" % self.uuid)
self.assertEqual(200, resp.status_code)
hl.assert_called_with(self.uuid, 3)