Repository: mercadona/rele
Pull request #46 (2019-06-12 10:26:48)

Add codecov
### :tophat: What?
Add codecov to the CI pipeline.

### :thinking: Why?
Why not?


codecov-io: # [Codecov](https://codecov.io/gh/mercadona/rele/pull/46?src=pr&el=h1) Report
> Merging [#46](https://codecov.io/gh/mercadona/rele/pull/46?src=pr&el=desc) into [master](https://codecov.io/gh/mercadona/rele/commit/911054bcb698b6aebc72806446f96d7284954a6f?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/mercadona/rele/pull/46/graphs/tree.svg?width=650&token=QcryolI2v9&height=150&src=pr)](https://codecov.io/gh/mercadona/rele/pull/46?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master      #46   +/-   ##
=======================================
  Coverage   59.67%   59.67%           
=======================================
  Files          14       14           
  Lines         372      372           
=======================================
  Hits          222      222           
  Misses        150      150
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/mercadona/rele/pull/46?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mercadona/rele/pull/46?src=pr&el=footer). Last update [911054b...49034e3](https://codecov.io/gh/mercadona/rele/pull/46?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.travis.yml b/.travis.yml
index 5badb08..6abc283 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,7 +21,8 @@ before_script:
   - psql -c 'create database travis_ci_test;' -U postgres
 
 script:
-  - make test
+  - make coverage
+  - codecov
 
 notifications:
   email: false
diff --git a/Makefile b/Makefile
index 1a4a7cc..1bf3b22 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-.PHONY: clean-pyc clean-build docs help
+.PHONY: clean-pyc clean-build release docs help
+.PHONY: lint test coverage test-codecov
 .DEFAULT_GOAL := help
 
 help:
@@ -22,11 +23,8 @@ lint: ## check style with flake8
 test: ## run tests quickly with the default Python
 	python runtests.py tests
 
-coverage: ## check code coverage quickly with the default Python
-	coverage run --source rele runtests.py tests
-	coverage report -m
-	coverage html
-	open htmlcov/index.html
+coverage: ## generates codecov report
+	python runtests.py tests --cov=rele
 
 release: clean install-deploy-requirements sdist ## package and upload a release
 	twine upload -u mercadonatech dist/*
diff --git a/README.md b/README.md
index 1d62dea..7b43004 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Relé makes integration with Google PubSub easier and is ready to integrate seam
 The [Publish-Subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern) and specifically the Google Cloud [PubSub library](https://pypi.org/project/google-cloud-pubsub/) are very powerful tools but you can easily cut your fingers on it. Relé makes integration seamless by providing Publisher, Subscriber and Worker classes with the following features:
 
 * A `publish` function:
-  * Singleton: Ensures there is no memory leak when instantiating a `PublisherClient` every time you publish will result in a memory leak because the transport is not closed by the Google library.
+  * Singleton: Avoids instantiating a `PublisherClient` every time you publish. Otherwise, it will result in a memory leak because the transport is not closed by the Google library.
 * A `sub` decorator to declare subscribers:
   * In-built acks
   * Automatic subscription topic naming
@@ -42,11 +42,14 @@ INSTALLED_APPS = [
 You'll also need to set up two variables with the Google Cloud credentials:
 `RELE_GC_CREDENTIALS` and `RELE_GC_PROJECT_ID`.
 
+You can use `RELE_SUB_PREFIX` to add a prefix to all your subscription names, for
+example, with your project name.
+
 NOTE: Ensure that [`CONN_MAX_AGE`](https://docs.djangoproject.com/en/2.2/ref/settings/#conn-max-age)
 is set to 0 in your worker. The Django default value is 0.
 
-In other words, the environment where you run `python manage.py runrele`,
-make sure `CONN_MAX_AGE` is not set explicitly.
+In other words, make sure `CONN_MAX_AGE` is not set explicitly in the environment where you run
+ `python manage.py runrele`.
 
 ## Usage
 
diff --git a/rele/management/commands/runrele.py b/rele/management/commands/runrele.py
index 44d9a8c..29fe7c7 100644
--- a/rele/management/commands/runrele.py
+++ b/rele/management/commands/runrele.py
@@ -52,6 +52,8 @@ class Command(BaseCommand):
             for attr_name in dir(sub_module):
                 attribute = getattr(sub_module, attr_name)
                 if isinstance(attribute, Subscription):
+                    if settings.RELE_PREFIX and not attribute.prefix:
+                        attribute.set_prefix(settings.RELE_SUB_PREFIX)
                     subscriptions.append(attribute)
         return subscriptions
 
diff --git a/rele/subscription.py b/rele/subscription.py
index 6d2cee1..5bc5c20 100644
--- a/rele/subscription.py
+++ b/rele/subscription.py
@@ -3,20 +3,29 @@ import logging
 import time
 
 from django import db
-from django.conf import settings
 
 logger = logging.getLogger(__name__)
 
 
 class Subscription:
 
-    def __init__(self, func, topic, suffix=None):
+    def __init__(self, func, topic, prefix='', suffix=''):
         self._func = func
         self.topic = topic
-        self.project_name = settings.BASE_DIR.split("/")[-1]
-        self.name = f'{self.project_name}-{topic}'
-        if suffix:
-            self.name += f'-{suffix}'
+        self._prefix = prefix
+        self._suffix = suffix
+
+    @property
+    def name(self):
+        name_parts = [self._prefix, self.topic, self._suffix]
+        return '-'.join(filter(lambda x: x, name_parts))
+
+    @property
+    def prefix(self):
+        return self._prefix
+
+    def set_prefix(self, prefix):
+        self._prefix = prefix
 
     def __call__(self, data, **kwargs):
         self._func(data, **kwargs)
@@ -66,7 +75,7 @@ class Callback:
 
     def _build_data_metrics(self, status, start_processing_time):
         result = {
-            'agent': self._subscription.project_name,
+            'agent': self._subscription.prefix or 'rele',
             'topic': self._subscription.topic,
             'status': status,
             'subscription': self._subscription.name,
@@ -80,7 +89,7 @@ class Callback:
         return result
 
 
-def sub(topic, suffix=None):
+def sub(topic, prefix=None, suffix=None):
     """Decorator function that makes declaring a PubSub Subscription simple.
 
     The Subscriber returned will automatically create and name
@@ -98,6 +107,10 @@ def sub(topic, suffix=None):
 
     Usage::
 
+        @sub(topic='lets-tell-to-alice', prefix='shop')
+        def bob_purpose(data, **kwargs):
+             pass
+
         @sub(topic='lets-tell-everyone', suffix='sub1')
         def purpose_1(data, **kwargs):
              pass
@@ -107,13 +120,15 @@ def sub(topic, suffix=None):
              pass
 
     :param topic: string The topic that is being subscribed to.
-    :param suffix: string An options suffix to the subscription name.
+    :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.
     :return: Subscription
     """
 
     def decorator(func):
-        return Subscription(func=func, topic=topic, suffix=suffix)
+        return Subscription(func=func, topic=topic, prefix=prefix, suffix=suffix)
 
     return decorator


Test patch:
diff --git a/requirements/test.txt b/requirements/test.txt
index 9559f8d..0b73df1 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1,4 +1,5 @@
-coverage==4.4.1
 flake8>=2.1.0
+pytest>=4.6.0
+coverage>=4.4.0
+pytest-cov>=2.7.0
 codecov>=2.0.0
-pytest
diff --git a/tests/settings.py b/tests/settings.py
index 8b4105e..900431a 100644
--- a/tests/settings.py
+++ b/tests/settings.py
@@ -31,6 +31,8 @@ RELE_GC_CREDENTIALS = service_account.Credentials.from_service_account_file(
     f'{BASE_DIR}/tests/dummy-pub-sub-credentials.json'
 )
 
+RELE_SUB_PREFIX = 'rele'
+
 logging_config.dictConfig({
     'version': 1,
     'disable_existing_loggers': False,
diff --git a/tests/test_subscription.py b/tests/test_subscription.py
index 23058f9..20ea3a8 100644
--- a/tests/test_subscription.py
+++ b/tests/test_subscription.py
@@ -9,12 +9,18 @@ from rele import Callback, Subscription, sub
 logger = logging.getLogger(__name__)
 
 
-@sub(topic='some-cool-topic')
+@sub(topic='some-cool-topic', prefix='rele')
 def sub_stub(data, **kwargs):
     logger.info(f'I am a task doing stuff with ID {data["id"]} '
                 f'({kwargs["lang"]})')
 
 
+@sub(topic='some-fancy-topic')
+def sub_fancy_stub(data, **kwargs):
+    logger.info(f'I used to have a prefix, but not anymore, only {data["id"]}'
+                f'id {kwargs["lang"]}')
+
+
 class TestSubscription:
 
     def test_subs_return_subscription_objects(self):
@@ -22,6 +28,11 @@ class TestSubscription:
         assert sub_stub.topic == 'some-cool-topic'
         assert sub_stub.name == 'rele-some-cool-topic'
 
+    def test_subs_without_prefix_return_subscription_objects(self):
+        assert isinstance(sub_fancy_stub, Subscription)
+        assert sub_fancy_stub.topic == 'some-fancy-topic'
+        assert sub_fancy_stub.name == 'some-fancy-topic'
+
     def test_executes_callback_when_called(self, caplog):
         res = sub_stub({'id': 123}, **{'lang': 'es'})
 
@@ -104,7 +115,7 @@ class TestCallback:
 
     def test_log_does_not_ack_called_message_when_execution_fails(
             self, caplog, message_wrapper):
-        @sub(topic='some-cool-topic')
+        @sub(topic='some-cool-topic', prefix='rele')
         def crashy_sub_stub(data, **kwargs):
             raise ValueError('I am an exception from a sub')
 
diff --git a/tests/test_worker.py b/tests/test_worker.py
index 607433d..b718c5e 100644
--- a/tests/test_worker.py
+++ b/tests/test_worker.py
@@ -7,7 +7,7 @@ from rele import Subscriber, Worker, sub
 from . import settings
 
 
-@sub(topic='some-cool-topic')
+@sub(topic='some-cool-topic', prefix='rele')
 def sub_stub(data, **kwargs):
     print(f'I am a task doing stuff.')