|
@@ -7,6 +7,155 @@
|
|
|
.. contents::
|
|
|
:local:
|
|
|
|
|
|
+Philosophy
|
|
|
+==========
|
|
|
+
|
|
|
+The API>RCP Precedence Rule
|
|
|
+---------------------------
|
|
|
+
|
|
|
+- The API is more important than Readability
|
|
|
+- Readability is more important than Convention
|
|
|
+- Convention is more important than Performance
|
|
|
+ - ...unless the code is a proven hotspot.
|
|
|
+
|
|
|
+More important than anything else is the end-user API.
|
|
|
+Conventions must step aside, and any suffering is always alleviated
|
|
|
+if the end result is a better API.
|
|
|
+
|
|
|
+Conventions and Idioms Used
|
|
|
+===========================
|
|
|
+
|
|
|
+Classes
|
|
|
+-------
|
|
|
+
|
|
|
+Naming
|
|
|
+~~~~~~
|
|
|
+
|
|
|
+- Follows :pep:`8`.
|
|
|
+
|
|
|
+- Class names must be CamelCase.
|
|
|
+- but not if they are verbs, verbs shall be lower_case:
|
|
|
+
|
|
|
+ .. code-block:: python
|
|
|
+
|
|
|
+ class TestFrobulator(Case): # BAD
|
|
|
+ ...
|
|
|
+
|
|
|
+ class test_Frobulator(Case): # GOOD
|
|
|
+ ...
|
|
|
+
|
|
|
+ class CreateContext(object): # BAD
|
|
|
+ ...
|
|
|
+
|
|
|
+ class create_context(object): # GOOD
|
|
|
+ ...
|
|
|
+
|
|
|
+ .. note::
|
|
|
+
|
|
|
+ Sometimes it makes sense to have a class mask as a function,
|
|
|
+ and there is precedence for this in the stdlib (e.g.
|
|
|
+ ``contextmanager``). Celery examples include the task decorator,
|
|
|
+ ``subtask``, ``chord``, ``inspect``, and ``promise``.
|
|
|
+
|
|
|
+- Factory functions and methods must be CamelCase (excluding verbs):
|
|
|
+
|
|
|
+ .. code-block:: python
|
|
|
+
|
|
|
+ class Celery(object):
|
|
|
+
|
|
|
+ def consumer_factory(self): # BAD
|
|
|
+ ...
|
|
|
+
|
|
|
+ def Consumer(self): # GOOD
|
|
|
+
|
|
|
+Default values
|
|
|
+~~~~~~~~~~~~~~
|
|
|
+
|
|
|
+Class attributes serve as default values for the instance,
|
|
|
+as this means that they can be set by either instantiation or inheritance.
|
|
|
+
|
|
|
+**Example:**
|
|
|
+
|
|
|
+.. code-block:: python
|
|
|
+
|
|
|
+ class Producer(object):
|
|
|
+ active = True
|
|
|
+ serializer = "json"
|
|
|
+
|
|
|
+ def __init__(self, serializer=None):
|
|
|
+ self.serializer = serializer or None
|
|
|
+
|
|
|
+ # must check for None when value can be false-y
|
|
|
+ self.active = active if active is not None else self.active
|
|
|
+
|
|
|
+A subclass can change the default value:
|
|
|
+
|
|
|
+.. code-block:: python
|
|
|
+
|
|
|
+ TaskProducer(Producer):
|
|
|
+ serializer = "pickle"
|
|
|
+
|
|
|
+and the value can be set at instantiation:
|
|
|
+
|
|
|
+.. code-block:: python
|
|
|
+
|
|
|
+ >>> producer = TaskProducer(serializer="msgpack")
|
|
|
+
|
|
|
+Exceptions
|
|
|
+~~~~~~~~~~
|
|
|
+
|
|
|
+Custom exceptions raised by an objects methods and properties
|
|
|
+should be available as an attribute and documented in the
|
|
|
+method/property that throw.
|
|
|
+
|
|
|
+This way a user doesn't have to find out where to import the
|
|
|
+exception from, but rather use ``help(obj)`` and access
|
|
|
+the exception class from the instance directly.
|
|
|
+
|
|
|
+**Example**:
|
|
|
+
|
|
|
+.. code-block:: python
|
|
|
+
|
|
|
+ class Empty(Exception):
|
|
|
+ pass
|
|
|
+
|
|
|
+ class Queue(object):
|
|
|
+ Empty = Empty
|
|
|
+
|
|
|
+ def get(self):
|
|
|
+ """Get the next item from the queue.
|
|
|
+
|
|
|
+ :raises Queue.Empty: if there are no more items left.
|
|
|
+
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ return self.queue.popleft()
|
|
|
+ except IndexError:
|
|
|
+ raise self.Empty()
|
|
|
+
|
|
|
+Composites
|
|
|
+~~~~~~~~~~
|
|
|
+
|
|
|
+Similarly to exceptions, composite classes used should be override-able by
|
|
|
+inheritance and/or instantiation. Common sense should be used when
|
|
|
+selecting what classes to include, but often it's better to add one
|
|
|
+too many as predicting what users need to override is hard (this has
|
|
|
+saved us from many a monkey patch).
|
|
|
+
|
|
|
+**Example**:
|
|
|
+
|
|
|
+.. code-block:: python
|
|
|
+
|
|
|
+ class Worker(object):
|
|
|
+ Consumer = Consumer
|
|
|
+
|
|
|
+ def __init__(self, connection, consumer_cls=None):
|
|
|
+ self.Consumer = consumer_cls or self.Consumer
|
|
|
+
|
|
|
+ def do_work(self):
|
|
|
+ with self.Consumer(self.connection) as consumer:
|
|
|
+ self.connection.drain_events()
|
|
|
+
|
|
|
Applications vs. "single mode"
|
|
|
==============================
|
|
|
|
|
@@ -20,7 +169,7 @@ for using Celery with frameworks that doesn't have this limitation.
|
|
|
|
|
|
Therefore the app concept was introduced. When using apps you use 'celery'
|
|
|
objects instead of importing things from celery submodules, this sadly
|
|
|
-also means that Celery essentially has two APIs.
|
|
|
+also means that Celery essentially has two API's.
|
|
|
|
|
|
Here's an example using Celery in single-mode:
|
|
|
|
|
@@ -144,4 +293,3 @@ Module Overview
|
|
|
- celery.contrib
|
|
|
|
|
|
Additional public code that doesn't fit into any other namespace.
|
|
|
-
|