Repository: mercadona/rele
Pull request #67 (2019-06-14 15:17:13)

Update quickstart documentation
### :tophat: What?

- Reflect new relé settings in a Django project
- Add settings import in code snippet

### :thinking: Why?

Documentation doesn't reflect the current status of relé and needed to be updated.


Patch:
diff --git a/Makefile b/Makefile
index 99ec70d..9bb9c26 100644
--- a/Makefile
+++ b/Makefile
@@ -47,6 +47,6 @@ install-docs-requirements:  ## install requirements for documentation
 
 install-dev-requirements: install-requirements install-test-requirements install-docs-requirements
 
-build-html-doc:
+build-html-doc: ## builds the project documentation in HTML format
 	DJANGO_SETTINGS_MODULE=tests.settings make html -C docs
 	open docs/_build/html/index.html
diff --git a/docs/quickstart/rele_and_emulator.rst b/docs/quickstart/rele_and_emulator.rst
index a572f34..5abccd5 100644
--- a/docs/quickstart/rele_and_emulator.rst
+++ b/docs/quickstart/rele_and_emulator.rst
@@ -21,12 +21,22 @@ To be able to do that we can follow the steps below:
     export PUBSUB_EMULATOR_HOST=localhost:8085
 
 
-3) Set rele environment variables on the project settings.
+3) Set rele settings in the Django project.
 
 .. code:: python
 
-    RELE_GC_PROJECT_ID = os.environ.get('RELE_GC_PROJECT_ID', 'dummy-project-id')
-    RELE_GC_CREDENTIALS = os.environ.get('RELE_GC_CREDENTIALS')
+    # my_django_project/settings.py
+
+    RELE = {
+        'APP_NAME': 'my-awesome-app',
+        'SUB_PREFIX': 'test',
+        'GC_PROJECT_ID': 'my-awesome-project',
+        'GC_CREDENTIALS': 'my-credentials',
+        'MIDDLEWARE': [
+            'rele.contrib.LoggingMiddleware',
+            'rele.contrib.DjangoDBMiddleware',
+        ],
+    }
 
 
 In case it's necessary to create a topic manually we can add it using the django shell.
@@ -37,8 +47,9 @@ In case it's necessary to create a topic manually we can add it using the django
 
 .. code:: python
 
+    from django.conf import settings
     from google.cloud import pubsub_v1
 
     publisher_client = pubsub_v1.PublisherClient()
-    topic_path = publisher_client.topic_path(settings.RELE_GC_PROJECT_ID, 'topic_name')
+    topic_path = publisher_client.topic_path(settings.RELE.get('GC_PROJECT_ID'), 'topic_name')
     publisher_client.create_topic(topic_path)
diff --git a/rele/subscription.py b/rele/subscription.py
index d288934..2eb5868 100644
--- a/rele/subscription.py
+++ b/rele/subscription.py
@@ -11,11 +11,12 @@ class Subscription:
     """The Subscription class
 
     """
-    def __init__(self, func, topic, prefix='', suffix=''):
+    def __init__(self, func, topic, prefix='', suffix='', filter_by=None):
         self._func = func
         self.topic = topic
         self._prefix = prefix
         self._suffix = suffix
+        self._filter_by = filter_by or (lambda **_: True)
 
     @property
     def name(self):
@@ -33,7 +34,10 @@ class Subscription:
         if 'published_at' in kwargs:
             kwargs['published_at'] = float(kwargs['published_at'])
 
-        return self._func(data, **kwargs)
+        if self._filter_by(**kwargs):
+            return self._func(data, **kwargs)
+
+        return None
 
     def __str__(self):
         return f'{self.name} - {self._func.__name__}'
@@ -65,7 +69,7 @@ class Callback:
             run_middleware_hook('post_process_message')
 
 
-def sub(topic, prefix=None, suffix=None):
+def sub(topic, prefix=None, suffix=None, filter_by=None):
     """Decorator function that makes declaring a PubSub Subscription simple.
 
     The Subscriber returned will automatically create and name
@@ -96,21 +100,28 @@ def sub(topic, prefix=None, suffix=None):
         def purpose_2(data, **kwargs):
              pass
 
+        @sub(topic='photo-updated',
+             filter_by=lambda **attrs: attrs.get('type') == 'landscape')
+        def sub_process_landscape_photos(data, **kwargs):
+            pass
+
     :param topic: string The topic that is being subscribed to.
     :param prefix: string An optional prefix to the subscription name.
                    Useful to namespace your subscription with your project name
     :param suffix: string An optional suffix to the subscription name.
                    Useful when you have two subscribers in the same project
                    that are subscribed to the same topic.
+    :param filter_by: function An optional function that
+                      filters the messages to be processed
+                      by the sub regarding their attributes.
     :return: :class:`~rele.subscription.Subscription`
     """
 
     def decorator(func):
-        return Subscription(
-            func=func,
-            topic=topic,
-            prefix=prefix,
-            suffix=suffix
-        )
+        return Subscription(func=func,
+                            topic=topic,
+                            prefix=prefix,
+                            suffix=suffix,
+                            filter_by=filter_by)
 
     return decorator


Test patch:
diff --git a/tests/test_subscription.py b/tests/test_subscription.py
index 60d928f..c8ea9ea 100644
--- a/tests/test_subscription.py
+++ b/tests/test_subscription.py
@@ -30,6 +30,15 @@ def sub_published_time_type(data, **kwargs):
     logger.info(f'{type(kwargs["published_at"])}')
 
 
+def landscape_filter(**kwargs):
+    return kwargs.get('type') == 'landscape'
+
+
+@sub(topic='photo-updated', filter_by=landscape_filter)
+def sub_process_landscape_photos(data, **kwargs):
+    return f'Received a photo of type {kwargs.get("type")}'
+
+
 class TestSubscription:
 
     def test_subs_return_subscription_objects(self):
@@ -49,6 +58,19 @@ class TestSubscription:
         log2 = caplog.records[0]
         assert log2.message == 'I am a task doing stuff with ID 123 (es)'
 
+    def test_sub_executes_when_message_attributes_match_criteria(self, caplog):
+        data = {'name': 'my_new_photo.jpeg'}
+        response = sub_process_landscape_photos(data, type='landscape')
+
+        assert response == 'Received a photo of type landscape'
+
+    def test_sub_does_not_execute_when_message_attributes_dont_match_criteria(
+            self, caplog):
+        data = {'name': 'my_new_photo.jpeg'}
+        response = sub_process_landscape_photos(data, type='')
+
+        assert response is None
+
 
 class TestCallback: