~cnx/palace

ref: e156f5822754abe7c17275b6e2bbe120aafee368 palace/docs/source/contributing.rst -rw-r--r-- 12.3 KiB
e156f582Nguyễn Gia Phong Use PyData Sphinx theme 1 year, 8 days ago
                                                                                
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
Getting Involved
================

.. currentmodule:: palace

First of all, thank you for using and contributing to palace!  We welcome
all forms of contribution, and `the mo the merier`_.  By saying this, we also
mean that we much prefer receiving many small and self-contained bug reports,
feature requests and patches than a giant one.  There is no limit for
the number of contributions one may or should make.  While it may seem
appealing to be able to dump all thoughts and feelings into one ticket,
it would be more difficult for us to keep track of the progress.

Reporting a Bug
---------------

Before filing a bug report, please make sure that the bug has not been
already reported by searching our `bug tracker`_.

To facilitate the debugging process, a bug report should at least contain
the following information:

#. The platform, the CPython version and the compiler used to build it.
   These can be obtained from :py:func:`platform.platform`,
   :py:func:`platform.python_version` and :py:func:`platform.python_compiler`,
   respectively.
#. The version of palace and how you installed it.
   The earlier is usually provided by ``pip show palace``.
#. Detailed instructions on how to reproduce the bug,
   for example a short Python script would be appreciated.

Requesting a Feature
--------------------

Prior to filing a feature request, please make sure that the feature
has not been already requested by searching our `mailing list`_.

Please only ask for features that you (or an incapacitated friend
you can personally talk to) require.  Do not request features because
they seem like a good idea.  If they are really useful, they will be
requested by someone who requires them.

Submitting a Patch
------------------

We accept all kinds of patches, from documentation and CI/CD setup
to bug fixes, feature implementations and tests.  Contributors must
have legal permission to distribute the code and it must be available
under `LGPLv3+`_.  Furthermore, each contributor retains the copyrights
of their patch, to ensure that the licence can never be revoked
even if others wants to.  It is advisable that the author lists
per legal name under the copyright header of each source file
perse modify, like so::

   Copyright (C) 2038  Foo Bar

The contribution process consists of the following steps:

#. Clone the upstream repository and configure the local one::

      git clone https://git.sr.ht/~cnx/palace
      cd palace
      git config sendemail.to "~cnx/palace@lists.sr.ht"

#. Start working on your patch and make sure your code complies with
   the `Style Guidelines`_ and passes the test suit run by tox_.
#. Add relevant tests to the patch and work on it until they all pass.
   In case one is only modifying tests, perse may install palace using
   ``CYTHON_TRACE=1 pip install .`` then run pytest_ directly to avoid
   having to build the extension module multiple times.
#. Update the copyright notices of the files you modified.
#. Add_ and commit_ with `a great message`_.
#. `Send the patch and response to feedback. <git-send-email>`_

In any case, thank you very much for your contributions!

Making a Release
----------------

While this is meant for developers doing a palace release, contributors wishing
to improve the CI/CD may find it helpful.

#. Under the local repository, checkout the ``main`` branch
   and sync with the one on SourceHut using ``git pull``.
#. Bump the version in ``setup.cfg`` and ``docs/source/conf.py``,
   tag_ the commit and push it to SourceHut.  In the release note, make sure
   to include all user-facing changes since the previous release.  This will
   trigger the CD services to build the wheels and publish them to PyPI_.
#. Create a source distribution by running ``setup.py sdist``.
   The distribution generated by this command is now referred to as ``sdist``.
#. Using twine_, upload the ``sdist`` to PyPI via ``twine upload $sdist``.
#. Wait for the wheel for your platform to arrive to PyPI and install it.
   Play around with it for a little to make sure that everything is OK.
#. Announce to the `mailing list`_.  With fear and trembling.

Coding Standards
----------------

Philosophy
^^^^^^^^^^

In order to write safe, efficient, easy-to-use and extendable Python,
the following principals should be followed.

.. _impl-idiom:

The Impl Idiom
""""""""""""""

*Not to be confused with* `the pimpl idiom`_.

For memory-safety, whenever possible, we rely on Cython for allocation and
deallocation of C++ objects.  To do this, the nullary constructor needs to be
(re-)declared in Cython, e.g.

.. code-block:: cython

   cdef extern from 'foobar.h' namespace 'foobar':
       cdef cppclass Foo:
           Foo()
           float meth(size_t crack) except +
           ...

The Cython extension type can then be declared as follows

.. code-block:: cython

   cdef class Bar:
       cdef Foo impl

       def __init__(self, *args, **kwargs):
           self.impl = ...

       @staticmethod
       def from_baz(baz: Baz) -> Bar:
           bar = Bar.__new__(Bar)
           bar.impl = ...
           return bar

       def meth(self, crack: int) -> float:
           return self.impl.meth(crack)

The Modern Python
"""""""""""""""""

One of the goal of palace is to create a Pythonic, i.e. intuitive and concise,
interface.  To achieve this, we try to make use of some modern Python features,
which not only allow users to adopt palace with ease, but also make their
programs more readable and less error-prone.

.. _getter-setter:

Property Attributes
'''''''''''''''''''

A large proportion of alure API are getters/setter methods.  In Python,
it is a good practice to use property_ to abstract these calls, and thus make
the interface more natural with attribute-like referencing and assignments.

Due to implementation details, Cython has to hijack the ``@property`` decorator
to make it work for read-write properties.  Unfortunately, the Cython-generated
descriptors do not play very well with other builtin decorators, thus in some
cases, it is recommended to alias the call to ``property`` as follows

.. code-block:: python

   getter = property
   setter = lambda fset: property(fset=fset, doc=fset.__doc__)

Then ``@getter`` and ``@setter`` can be used to decorate read-only and
write-only properties, respectively, without any trouble even if other
decorators are used for the same extension type method.

Context Managers
''''''''''''''''

The alure API defines many objects that need manual tear-down in
a particular order.  Instead of trying to be clever and perform automatic
clean-ups at garbage collection, we should put the user in control.
To quote *The Zen of Python*,

   | If the implementation is hard to explain, it's a bad idea.
   | If the implementation is easy to explain, it may be a good idea.

With that being said, it does not mean we do not provide any level of
abstraction.  A simplified case in point would be

.. code-block:: cython

   cdef class Device:
       cdef alure.Device impl

       def __init__(self, name: str = '') -> None:
           self.impl = devmgr.open_playback(name)

       def __enter__(self) -> Device:
           return self

       def __exit__(self, *exc) -> Optional[bool]:
           self.close()

       def close(self) -> None:
           self.impl.close()

Now if the ``with`` statement is used, it will make sure the device
will be closed, regardless of whatever may happen within the inner block

.. code-block:: python

   with Device() as dev:
       ...

as it is equivalent to

.. code-block:: python

   dev = Device()
   try:
       ...
   finally:
       dev.close()

Other than closure/destruction of objects, typical uses of `context managers`__
also include saving and restoring various kinds of global state (as seen in
:py:class:`Context`), locking and unlocking resources, etc.

__ https://docs.python.org/3/reference/datamodel.html#context-managers

The Double Reference
''''''''''''''''''''

While wrapping C++ interfaces, :ref:`the impl idiom <impl-idiom>` might not
be adequate, since the derived Python methods need to be callable from C++.
Luckily, Cython can handle Python objects within C++ classes just fine,
although we'll need to handle the reference count ourselves, e.g.

.. code-block:: cython

   cdef cppclass CppDecoder(alure.BaseDecoder):
       Decoder pyo

       __init__(Decoder decoder):
           this.pyo = decoder
           Py_INCREF(pyo)

       __dealloc__():
           Py_DECREF(pyo)

       bool seek(uint64_t pos):
           return pyo.seek(pos)

With this being done, we can now write the wrapper as simply as

.. code-block:: cython

   cdef class BaseDecoder:
       cdef shared_ptr[alure.Decoder] pimpl

       def __cinit__(self, *args, **kwargs) -> None:
           self.pimpl = shared_ptr[alure.Decoder](new CppDecoder(self))

       def seek(pos: int) -> bool:
           ...

Because ``__cinit__`` is called by ``__new__``, any Python class derived
from ``BaseDecoder`` will be exposed to C++ as an attribute of ``CppDecoder``.
Effectively, this means the users can have the alure API calling their
inherited Python object as naturally as if palace is implemented in pure Python.

In practice, :py:class:`BaseDecoder` will also need to take into account
other guarding mechanisms like :py:class:`abc.ABC`.  Due to Cython limitations,
implementation as a pure Python class and :ref:`aliasing <getter-setter>` of
``@getter``/``@setter`` should be considered.

Style Guidelines
^^^^^^^^^^^^^^^^

Python and Cython
"""""""""""""""""

Generally, palace follows :pep:`8` and :pep:`257`,
with the following preferences and exceptions:

* Hanging indentation is *always* preferred,
  where continuation lines are indented by 4 spaces.
* Comments and one-line docstrings are limited to column 79
  instead of 72 like for multi-line docstrings.
* Cython extern declarations need not follow the 79-character limit.
* Break long lines before a binary operator.
* Use form feeds sparingly to break long modules
  into pages of relating functions and classes.
* Prefer single-quoted strings over double-quoted strings,
  unless the string contains single quote characters.
* Avoid trailing commas at all costs.
* Line breaks within comments and docstrings should not cut a phrase in half.
* Everything deserves a docstring.  Palace follows numpydoc_ which support
  documenting attributes as well as constants and module-level variables.
  In additional to docstrings, type annotations should be employed
  for all public names.
* Use numpydoc_ markups moderately to keep docstrings readable as plain text.

C++
"""

C++ codes should follow GNU style, which is best documented at Octave_.

reStructuredText
""""""""""""""""

Overall, palace's documentation follows CPython documenting_ style guide,
with a few additional preferences.

In order for reStructuredText to be rendered correctly, the body of
constructs beginning with a marker (lists, hyperlink targets, comments, etc.)
must be aligned relative to the marker.  For this reason, it is convenient
to set your editor indentation level to 3 spaces, since most constructs
starts with two dots and a space.  However, be aware of that bullet items
require 2-space alignment and other exceptions.

Limit all lines to a maximum of 80 characters.  Similar to comments
and docstrings, phrases should not be broken in the middle.
The source code of this guide itself is a good example on how line breaks
should be handled.  Additionally, two spaces should also be used
after a sentence-ending period in multi-sentence paragraph,
except after the final sentence.

.. _the mo the merier:
   https://www.phrases.org.uk/meanings/the-more-the-merrier.html
.. _bug tracker: https://todo.sr.ht/~cnx/palace
.. _mailing list: https://lists.sr.ht/~cnx/palace
.. _LGPLv3+: https://www.gnu.org/licenses/lgpl-3.0.en.html
.. _tox: https://tox.readthedocs.io
.. _pytest: https://docs.pytest.org
.. _Add: https://git-scm.com/docs/git-add
.. _commit: https://git-scm.com/docs/git-commit
.. _a great message: https://chris.beams.io/posts/git-commit/#seven-rules
.. _git-send-email: https://git-send-email.io
.. _tag: https://git-scm.com/docs/git-tag
.. _PyPI: https://pypi.org
.. _twine: https://twine.readthedocs.io
.. _the pimpl idiom: https://wiki.c2.com/?PimplIdiom
.. _property:  https://docs.python.org/3/library/functions.html#property
.. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html
.. _Octave: https://wiki.octave.org/C%2B%2B_style_guide
.. _documenting: https://devguide.python.org/documenting/#style-guide