Convenções para contribuir com este pacote

Nós modelamos as regras abaixo baseados nas seguintes documentações:

Comprimento da linha

Todo código Python deste pacote deve estar de acordo com as convenções estabelecidas no PEP8. Isto inclui ser aderente a 80 caracteres no comprimento da linha. Se for extremamente necessário quebrar esta regra, acrescente # noPEP8 à linha em questão para que ela seja ignorada nas verificações de sintaxe.

Note

Configure seu editor para trabalhar com uma linha de 79 colunas. Isto ajuda a leitura e poupa tempo do desenvolvedor.

Note

A regra do comprimento da linha se aplica não só aos arquivos com código Python, mas arquivos com extensão .rst ou .zcml também devem seguir a regra, porém não com tanto rigor.

Quebras de linha

Baseado em códigos (Pyramid, Requests, etc) que amamos seguir, nós aceitamos os dois estilos de quebra de linha abaixo para blocos de código:

  1. Quebre a linha seguinte adicionando indentação ao bloco.

    foo = do_something(
        very_long_argument='foo', another_very_long_argument='bar')
    
    # For functions the ): needs to be placed on the following line
    def some_func(
        very_long_argument='foo', another_very_long_argument='bar'
    ):
    
  2. Não se esqueça de adequar aos 80 caracteres por linha e se necessário quebre em mais linhas.

    foo = dict(
        very_long_argument='foo',
        another_very_long_argument='bar',
    )
    
    a_long_list = [
        "a_fairly_long_string",
        "quite_a_long_string_indeed",
        "an_exceptionally_long_string_of_characters",
    ]
    
  • Logo após o parêntese ou chave de abertura é proibido colocar argumentos, quando houver quebra de linha vislumbrando um argumento por linha.
  • A última linha de argumento precisa ter uma vírgula à direita (para facilitar o acréscimo de uma nova linha de argumento por um próximo desenvolvedor).
  • O parêntese de fechamento (ou “bracket”) precisa ter a mesma identação da primeira linha.
  • Cada linha deve conter um único argumento.
  • O mesmo estilo se aplica à dicionários, listas, return calls, etc.

Este pacote segue todas as regras acima, verifique na fonte para vê-los em ação.

Indentação

Para arquivos Python, nós seguimos as recomendações da PEP 8: Use 4 espaços por cada recuo de indentação.

For ZCML and XML (GenericSetup) files, we recommend the Zope Toolkit’s coding style on ZCML

Indentation of 2 characters to show nesting, 4 characters to list attributes
on separate lines. This distinction makes it easier to see the difference
between attributes and nested elements.

Quoting

For strings and such prefer using single quotes over double quotes. The reason is that sometimes you do need to write a bit of HTML in your python code, and HTML feels more natural with double quotes so you wrap HTML string into single quotes. And if you are using single quotes for this reason, then be consistent and use them everywhere.

There are two exceptions to this rule:

  • docstrings should always use double quotes (as per PEP-257).
  • if you want to use single quotes in your string, double quotes might make more sense so you don’t have to escape those single quotes.
# GOOD
print 'short'
print 'A longer string, but still using single quotes.'

# BAD
print "short"
print "A long string."

# EXCEPTIONS
print "I want to use a 'single quote' in my string."
"""This is a docstring."""

Docstrings style

Read and follow http://www.python.org/dev/peps/pep-0257/. There is one exception though: We reject BDFL’s recommendation about inserting a blank line between the last paragraph in a multi-line docstring and its closing quotes as it’s Emacs specific and two Emacs users here on the Beer & Wine Sprint both support our way.

The content of the docstring must be written in the active first-person form, e.g. “Calculate X from Y” or “Determine the exact foo of bar”.

def foo():
    """Single line docstring."""

def bar():
    """Multi-line docstring.

    With the additional lines indented with the beginning quote and a
    newline preceding the ending quote.
    """

If you wanna be extra nice, you are encouraged to document your method’s parameters and their return values in a reST field list syntax.

:param foo: blah blah
:type foo: string
:param bar: blah blah
:type bar: int
:returns: something

Check out the plone.api source for more usage examples. Also, see the following for examples on how to write good Sphinxy docstrings: http://stackoverflow.com/questions/4547849/good-examples-of-python-docstrings-for-sphinx.

Unit tests style

Read http://www.voidspace.org.uk/python/articles/unittest2.shtml to learn what is new in unittest2 and use it.

This is not true for in-line documentation tests. Those still use old unittest test-cases, so you cannot use assertIn and similar.

String formatting

As per http://docs.python.org/2/library/stdtypes.html#str.format, we should prefer the new style string formatting (.format()) over the old one (% ()).

Also use numbering, like so:

# GOOD
print "{0} is not {1}".format(1, 2)

and not like this:

# BAD
print "{} is not {}".format(1, 2)
print "%s is not %s" % (1, 2)

because Python 2.6 supports only explicitly numbered placeholders.

About imports

  1. Don’t use * to import everything from a module, because if you do, pyflakes cannot detect undefined names (W404).

  2. Don’t use commas to import multiple things on a single line. Some developers use IDEs (like Eclipse) or tools (such as mr.igor) that expect one import per line. Let’s be nice to them.

  3. Don’t use relative paths, again to be nice to people using certain IDEs and tools. Also Google Python Style Guide recommends against it.

    # GOOD
    from plone.app.testing import something
    from zope.component import getMultiAdapter
    from zope.component import getSiteManager
    

    instead of

    # BAD
    from plone.app.testing import *
    from zope.component import getMultiAdapter, getSiteManager
    
  4. Don’t catch ImportError to detect whether a package is available or not, as it might hide circular import errors. Instead, use pkg_resources.get_distribution and catch DistributionNotFound. More background at http://do3.cc/blog/2010/08/20/do-not-catch-import-errors,-use-pkg_resources/.

    # GOOD
    import pkg_resources
    
    try:
        pkg_resources.get_distribution('plone.dexterity')
    except pkg_resources.DistributionNotFound:
        HAS_DEXTERITY = False
    else:
        HAS_DEXTERITY = True
    

    instead of

    # BAD
    try:
        import plone.dexterity
        HAVE_DEXTERITY = True
    except ImportError:
        HAVE_DEXTERITY = False
    

Grouping and sorting

Since Plone has such a huge code base, we don’t want to lose developer time figuring out into which group some import goes (standard lib?, external package?, etc.). So we just sort everything alphabetically and insert one blank line between from foo import bar and import baz blocks. Conditional imports come last. Again, we do not distinguish between what is standard lib, external package or internal package in order to save time and avoid the hassle of explaining which is which.

As for sorting, it is recommended to use case-sensitive sorting. This means uppercase characters come first, so “Products.*” goes before “plone.*”.

# GOOD
from __future__ import division
from Acquisition import aq_inner
from Products.CMFCore.interfaces import ISiteRoot
from Products.CMFCore.WorkflowCore import WorkflowException
from plone.api import portal
from plone.api.exc import MissingParameterError

import pkg_resources
import random

try:
    pkg_resources.get_distribution('plone.dexterity')
except pkg_resources.DistributionNotFound:
    HAS_DEXTERITY = False
else:
    HAS_DEXTERITY = True

Declaring dependencies

All direct dependencies should be declared in install_requires or extras_require sections in setup.py. Dependencies, which are not needed for a production environment (like “develop” or “test” dependencies) or are optional (like “Archetypes” or “Dexterity” flavors of the same package) should go in extras_require. Remember to document how to enable specific features (and think of using zcml:condition statements, if you have such optional features).

Generally all direct dependencies (packages directly imported or used in ZCML) should be declared, even if they would already be pulled in by other dependencies. This explicitness reduces possible runtime errors and gives a good overview on the complexity of a package.

For example, if you depend on Products.CMFPlone and use getToolByName from Products.CMFCore, you should also declare the CMFCore dependency explicitly, even though it’s pulled in by Plone itself. If you use namespace packages from the Zope distribution like Products.Five you should explicitly declare Zope as dependency.

Inside each group of dependencies, lines should be sorted alphabetically.

Versioning scheme

For software versions, use a sequence-based versioning scheme, which is compatible with setuptools:

MAJOR.MINOR[.MICRO][STATUS]

The way, setuptools interprets versions is intuitive:

1.0 < 1.1dev < 1.1a1 < 1.1a2 < 1.1b < 1.1rc1 < 1.1 < 1.1.1

You can test it with setuptools:

>>> from pkg_resources import parse_version
>>> parse_version('1.0') < parse_version('1.1.dev')
... < parse_version('1.1.a1') < parse_version('1.1.a2')
... < parse_version('1.1.b') < parse_version('1.1.rc1')
... < parse_version('1.1') < parse_version('1.1.1')

Setuptools recommends to seperate parts with a dot. The website about semantic versioning is also worth a read.

Restructured Text versus Plain Text

Use the Restructured Text (.rst file extension) format instead of plain text files (.txt file extension) for all documentation, including doctest files. This way you get nice syntax highlighting and formating in recent text editors, on GitHub and with Sphinx.

Tracking changes

Feature-level changes to code are tracked inside CHANGES.rst. The title of the CHANGES.rst file should be Changelog. Example:

Changelog
=========

1.0.0-dev (Unreleased)
----------------------

- Added feature Z.
  [github_userid1]

- Removed Y.
  [github_userid2]


1.0.0-alpha.1 (2012-12-12)
--------------------------

- Fixed Bug X.
  [github_userid1]

Add an entry every time you add/remove a feature, fix a bug, etc. on top of the current development changes block.

Sphinx Documentation

Un-documented code is broken code.

For every feature you add to the codebase you should also add documentation for it to docs/.

After adding/modifying documentation, run make to re-generate your docs.

Publicly available documentation on http://api.plone.org is automatically generated from these source files, periodically. So when you push changes to master on GitHub you should soon be able to see them published on api.plone.org.

Read the reStructuredText Primer to brush up on your reST skills.

Example:

def add(a, b):
    """Calculate the sum of the two parameters.

    Also see the :func:`mod.path.my_func`, :meth:`mod.path.MyClass.method`
    and :attr:`mod.path.MY_CONSTANT` for more details.

    :param a: The first operand.
    :type a: :class:`mod.path.A`

    :param b: The second operand.
    :type b: :class:`mod.path.B`

    :rtype: int
    :return: The sum of the operands.
    :raise: `KeyError`, if the operands are not the correct type.
    """

Attributes are documented using the #: marker above the attribute. The documentation may span multiple lines.

#: Description of the constant value
MY_CONSTANT = 0xc0ffee

class Foobar(object):

    #: Description of the class variable which spans over
    #: multiple lines
    FOO = 1

Travis Continuous Integration

On every push to GitHub, Travis runs all tests and syntax validation checks and reports build outcome to the #sprint IRC channel and the person who committed the last change.

Travis is configured with the .travis.yml file located in the root of this package.

Git workflow & branching model

Our repository on GitHub has the following layout:

  • feature branches: all development for new features must be done in dedicated branches, normally one branch per feature,
  • master branch: when features get completed they are merged into the master branch; bugfixes are commited directly on the master branch,
  • tags: whenever we create a new release we tag the repository so we can later re-trace our steps, re-release versions, etc.

Release process for Plone packages

To keep the Plone software stack maintainable, the Python egg release process must be automated to high degree. This happens by enforcing Python packaging best practices and then making automated releases using the zest.releaser tool.

  • Anyone with necessary PyPi permissions must be able to make a new release by running the fullrelease command

… which includes …

  • All releases must be hosted on PyPi
  • All versions must be tagged at version control
  • Each package must have README.rst with links to the version control repository and issue tracker
  • CHANGES.txt (docs/HISTORY.txt in some packages) must be always up-to-date and must contain list of functional changes which may affect package users.
  • CHANGES.txt must contain release dates
  • README.rst and CHANGES.txt must be visible on PyPi
  • Released eggs must contain generated gettext .mo files, but these files must not be committed to the repository (files can be created with zest.pocompile addon)
  • .gitignore and MANIFEST.in must reflect the files going to egg (must include page template, po files)

More information

Setting up Git

Git is a very useful tool, especially when you configure it to your needs. Here are a couple of tips.

Git dotfiles

Plone developers have dotfiles similar to these: https://github.com/plone/plone.dotfiles.

Git Commit Message Style

Tim Pope’s post on Git commit message style is widely considered the gold standard:

Capitalized, short (50 chars or less) summary

More detailed explanatory text, if necessary.  Wrap it to about 72
characters or so.  In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body.  The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.

Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
or "Fixes bug."  This convention matches up with commit messages generated
by commands like git merge and git revert.

Further paragraphs come after blank lines.

- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded by a
  single space, with blank lines in between, but conventions vary here
- Use a hanging indent

Github flavored markdown is also useful in commit messages.