summaryrefslogtreecommitdiff
path: root/docs/source/internal/writing-code.rst
blob: daf1d57234359f5d19cd81d0dfbafc8e3e49397b (plain)
1
2
3
4
5
6
7
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
.. _writing-code:

=========================
 Writing Code for Flake8
=========================

The maintainers of |Flake8| unsurprisingly have some opinions about the style
of code maintained in the project.

At the time of this writing, |Flake8| enables all of PyCodeStyle's checks, all
of PyFlakes' checks, and sets a maximum complexity value (for McCabe) of 10.
On top of that, we enforce PEP-0257 style doc-strings via PyDocStyle
(disabling only D203) and Google's import order style using
flake8-import-order.

The last two are a little unusual, so we provide examples below.


PEP-0257 style doc-strings
==========================

|Flake8| attempts to document both internal interfaces as well as our API and
doc-strings provide a very convenient way to do so. Even if a function, class,
or method isn't included specifically in our documentation having a doc-string
is still preferred. Further, |Flake8| has some style preferences that are not
checked by PyDocStyle.

For example, while most people will never read the doc-string for
:func:`flake8.main.git.hook` that doc-string still provides value to the
maintainers and future collaborators. They (very explicitly) describe the
purpose of the function, a little of what it does, and what parameters it
accepts as well as what it returns.

.. code-block:: python

    # src/flake8/main/git.py
    def hook(lazy=False, strict=False):
        """Execute Flake8 on the files in git's index.

        Determine which files are about to be committed and run Flake8 over them
        to check for violations.

        :param bool lazy:
            Find files not added to the index prior to committing. This is useful
            if you frequently use ``git commit -a`` for example. This defaults to
            False since it will otherwise include files not in the index.
        :param bool strict:
            If True, return the total number of errors/violations found by Flake8.
            This will cause the hook to fail.
        :returns:
            Total number of errors found during the run.
        :rtype:
            int
        """
        # NOTE(sigmavirus24): Delay import of application until we need it.
        from flake8.main import application
        app = application.Application()
        with make_temporary_directory() as tempdir:
            filepaths = list(copy_indexed_files_to(tempdir, lazy))
            app.initialize(['.'])
            app.options.exclude = update_excludes(app.options.exclude, tempdir)
            app.run_checks(filepaths)

        app.report_errors()
        if strict:
            return app.result_count
        return 0

Note that because the parameters ``hook`` and ``strict`` are simply boolean
parameters, we inline the type declaration for those parameters, e.g.,

.. code-block:: restructuredtext

    :param bool lazy:

Also note that we begin the description of the parameter on a new-line and
indented 4 spaces.

On the other hand, we also separate the parameter type declaration in some
places where the name is a little longer, e.g.,

.. code-block:: python

    # src/flake8/formatting/base.py
    def format(self, error):
        """Format an error reported by Flake8.

        This method **must** be implemented by subclasses.

        :param error:
            This will be an instance of :class:`~flake8.style_guide.Error`.
        :type error:
            flake8.style_guide.Error
        :returns:
            The formatted error string.
        :rtype:
            str
        """

Here we've separated ``:param error:`` and ``:type error:``.

Following the above examples and guidelines should help you write doc-strings
that are stylistically correct for |Flake8|.


Imports
=======

|Flake8| follows the import guidelines that Google published in their Python
Style Guide. In short this includes:

- Only importing modules

- Grouping imports into

  * standard library imports

  * third-party dependency imports

  * local application imports

- Ordering imports alphabetically

In practice this would look something like:

.. code-block:: python

    import configparser
    import logging
    from os import path

    import requests

    from flake8 import exceptions
    from flake8.formatting import base

As a result, of the above, we do not:

- Import objects into a namespace to make them accessible from that namespace

- Import only the objects we're using

- Add comments explaining that an import is a standard library module or
  something else


Other Stylistic Preferences
===========================

Finally, |Flake8| has a few other stylistic preferences that it does not
presently enforce automatically.

Multi-line Function/Method Calls
--------------------------------

When you find yourself having to split a call to a function or method up
across multiple lines, insert a new-line after the opening parenthesis, e.g.,

.. code-block:: python

    # src/flake8/main/options.py
    add_option(
        '-v', '--verbose', default=0, action='count',
        parse_from_config=True,
        help='Print more information about what is happening in flake8.'
             ' This option is repeatable and will increase verbosity each '
             'time it is repeated.',
    )

    # src/flake8/formatting/base.py
    def show_statistics(self, statistics):
        """Format and print the statistics."""
        for error_code in statistics.error_codes():
            stats_for_error_code = statistics.statistics_for(error_code)
            statistic = next(stats_for_error_code)
            count = statistic.count
            count += sum(stat.count for stat in stats_for_error_code)
            self._write(f'{count:<5} {error_code} {statistic.message}')

In the first example, we put a few of the parameters all on one line, and then
added the last two on their own. In the second example, each parameter has its
own line. This particular rule is a little subjective. The general idea is
that putting one parameter per-line is preferred, but sometimes it's
reasonable and understandable to group a few together on one line.

Comments
--------

If you're adding an important comment, be sure to sign it. In |Flake8| we
generally sign comments by preceding them with ``NOTE(<name>)``. For example,

.. code-block:: python

    # NOTE(sigmavirus24): The format strings are a little confusing, even
    # to me, so here's a quick explanation:
    # We specify the named value first followed by a ':' to indicate we're
    # formatting the value.
    # Next we use '<' to indicate we want the value left aligned.
    # Then '10' is the width of the area.
    # For floats, finally, we only want only want at most 3 digits after
    # the decimal point to be displayed. This is the precision and it
    # can not be specified for integers which is why we need two separate
    # format strings.
    float_format = '{value:<10.3} {statistic}'.format
    int_format = '{value:<10} {statistic}'.format

Ian is well known across most websites as ``sigmavirus24`` so he signs his
comments that way.

Verbs Belong in Function Names
------------------------------

|Flake8| prefers that functions have verbs in them. If you're writing a
function that returns a generator of files then ``generate_files`` will always
be preferable to ``make_files`` or ``files``.