Skip to content

Commit

Permalink
tox and circleci config fix, new test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
pawelzny committed Mar 19, 2018
1 parent f5f0b85 commit 10cf990
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 94 deletions.
26 changes: 13 additions & 13 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ jobs:
- run:
name: install dependencies
command: |
pip install pipenv;
pipenv install;
pipenv install --dev;
pip install pipenv
pipenv install --python `which pypy3`
pipenv install --python `which pypy3` --dev
- save_cache:
paths:
- ./repo
Expand All @@ -38,9 +38,9 @@ jobs:
- run:
name: install dependencies
command: |
pip install pipenv;
pipenv install;
pipenv install --dev;
pip install pipenv
pipenv install --three
pipenv install --three --dev
- save_cache:
paths:
- ./repo
Expand All @@ -61,9 +61,9 @@ jobs:
- run:
name: install dependencies
command: |
pip install pipenv;
pipenv install;
pipenv install --dev;
pip install pipenv
pipenv install --three
pipenv install --three --dev
- save_cache:
paths:
- ./repo
Expand All @@ -84,16 +84,16 @@ jobs:
- run:
name: install dependencies
command: |
pip install pipenv;
pipenv install;
pipenv install --dev;
pip install pipenv
pipenv install --three
pipenv install --three --dev
- save_cache:
paths:
- ./repo
key: v1-dependencies-{{ checksum "Pipfile.lock" }}
- run:
name: run tests
command: pipenv run flake8 eeee tests
command: pipenv run flake8 cl tests
workflows:
version: 2
test_all:
Expand Down
12 changes: 9 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ install: ## install dependencies
install-dev: install ## install dev dependencies
pipenv install --dev

clean: clean-build clean-pyc
clean: clean-build clean-pyc clean-cache

clean-build: ## remove build artifacts
rm -fr build/
Expand All @@ -22,18 +22,24 @@ clean-pyc: ## remove Python file artifacts
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +

clean-cache: ## remove .cache and .pytest_cache
rm -rf .cache
rm -rf .pytest_cache

lint: ## check style with flake8
pipenv run flake8 eeee tests
pipenv run flake8

test: ## run tests quickly with the default Python
pipenv run pytest

test-all: ## run tests on every Python version with tox
rm -rf .tox
pipenv run tox --skip-missing-interpreters

coverage: ## check code coverage quickly with the default Python
rm -rf htmlcov
pipenv run coverage run --source eeee -m pytest
pipenv run coverage erase
pipenv run coverage run -m pytest
pipenv run coverage report -m
pipenv run coverage html

Expand Down
4 changes: 2 additions & 2 deletions docs/_templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
{% block extrabody %}
{{ super() }}
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-55876783-6"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-55876783-8"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', 'UA-55876783-6');
gtag('config', 'UA-55876783-8');
</script>
{% endblock %}
104 changes: 73 additions & 31 deletions eeee/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Event:
:param name: Optional Event name. If empty will be assigned to name of Class.
:type name: eeee.event.Event, str
:raises: eeee.exceptions.NamingError
"""

RETURN_EXCEPTIONS = False
Expand Down Expand Up @@ -119,16 +120,16 @@ async def publish(self, message: Any, publisher: Union["Publisher", str] = None)
:type publisher: eeee.event.Publisher, str
:return: List of results from subscribed handlers or None if event is disabled.
"""
if self.is_enable:
if publisher is not None:
publisher = Publisher(publisher)
coros = []
for ps in self.pub_sub:
if ps.publisher is None or (publisher is not None and ps.publisher == publisher):
coros.append(ps.subscriber(message, publisher, event=self.name))
if coros:
return await asyncio.gather(*coros, return_exceptions=self.RETURN_EXCEPTIONS)
return None
if not self.is_enable:
return None

publisher = Publisher(publisher) if publisher else publisher

coros = []
for ps in self.pub_sub:
if ps.publisher is None or (publisher is not None and ps.publisher == publisher):
coros.append(ps.subscriber(message, publisher, event=self.name))
return await asyncio.gather(*coros, return_exceptions=self.RETURN_EXCEPTIONS)

def subscribe(self, publisher: Union["Publisher", str] = None):
"""Subscribe decorator integrated within Event object.
Expand Down Expand Up @@ -279,10 +280,11 @@ def __eq__(self, other):
return False

@property
def id(self):
def id(self) -> str:
"""Publisher identification.
:return: string ID
:rtype: str
"""
return self.__id

Expand Down Expand Up @@ -323,24 +325,11 @@ class Subscriber:
"""

def __init__(self, handler: Union["Subscriber", callable]):
if isinstance(handler, self.__class__):
self.handler = handler.handler
self.name = handler.name
elif callable(handler):
try:
self.name = handler.__name__
self.handler = handler
is_coro = iscoroutinefunction(handler)
except AttributeError:
# assume instance of callable class
self.name = handler.__class__.__name__
self.handler = handler
is_coro = iscoroutinefunction(handler.__call__)

if not is_coro:
raise exceptions.NotCoroutineError
else:
raise exceptions.NotCallableError
self.name, self.handler = _parse_handler(handler)

# handler validation
_is_callable(self.handler)
_is_coro(self.handler)

self.__template = str(self.__class__) + '{name}</class>'
self.__id = self.__template.format(name=self.name)
Expand All @@ -352,13 +341,66 @@ def __eq__(self, other):
return self.id == self.__template.format(name=other)
return False

async def __call__(self, *args, **kwargs):
return await self.handler(*args, **kwargs)
async def __call__(self, message, publisher, event):
return await self.handler(message=message, publisher=publisher, event=event)

@property
def id(self):
"""Subscriber identification.
:return: string ID
:rtype: str
"""
return self.__id


def _parse_handler(handler: Union[callable, object, Subscriber]):
"""Parse handler name and body.
Function accept functions, callable object and instance of Subscriber.
If Subscriber has been given, parser will extract its name and handler.
:param handler:
:return:
"""
if isinstance(handler, Subscriber):
return handler.name, handler.handler

try:
name = handler.__name__
except AttributeError:
# assume instance of callable object
name = handler.__class__.__name__

return name, handler


def _is_callable(handler: Union[callable, object]):
"""Check if handler is callable.
:param handler: Function or callable object.
:type handler: callable, object
:raises: eeee.exception.NotCallableError
:return: None
"""
if not callable(handler):
raise exceptions.NotCallableError


def _is_coro(handler: Union[callable, object]):
"""Check if handler is coroutine.
Class with async __call__ method is considered a coroutine.
:param handler: Function or callable object.
:type handler: callable, object
:raises: eeee.exceptions.NotCoroutineError
:return: None
"""
try:
is_coro = iscoroutinefunction(handler) or iscoroutinefunction(handler.__call__)
except AttributeError:
is_coro = False

if not is_coro:
raise exceptions.NotCoroutineError
26 changes: 15 additions & 11 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ tag = True

[flake8]
max-line-length = 100
max-complexity = 10
max-complexity = 4
exclude =
.git,
.idea,
.tox,
__pycache__,
docs,
build,
dist,
node_modules,
.cache
.circleci
*_backup.*,
setup.py,
.git
.idea
.pytest_cache
.tox
__pycache__
docs
build
dist
htmlcov
node_modules
*.egg-info
*_backup.*
setup.py

[tool:pytest]
addopts = --verbose
Expand Down
17 changes: 16 additions & 1 deletion tests/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
import unittest

from eeee import Event
from eeee import Event, exceptions

__author__ = 'Paweł Zadrożny'
__copyright__ = 'Copyright (c) 2018, Pawelzny'
Expand Down Expand Up @@ -41,3 +41,18 @@ def test_toggle(self):
def test_new_event_without_name(self):
event = Event()
self.assertEqual(event.name, 'Event')

def test_get_name_from_other_event(self):
optimus = Event('Optimus Prime')
clone = Event(optimus)

# name has been cloned
self.assertEqual(optimus.name, clone.name)

# but are not the same
self.assertNotEqual(optimus, clone)
self.assertIsNot(optimus, clone)

def test_raise_on_bad_naming(self):
with self.assertRaises(exceptions.NamingError):
Event({'name': 'test'})
2 changes: 1 addition & 1 deletion tests/test_pub_sub.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def test_publish_to_empty_event(self):
with Loop(event.publish('enter the void')) as loop:
result = loop.run_until_complete()

self.assertIsNone(result)
self.assertListEqual(result, [])

def test_publish_on_disabled_event(self):
event = Event('disabled')
Expand Down
13 changes: 12 additions & 1 deletion tests/test_subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,18 @@ def handler():
with self.assertRaises(exceptions.NotCoroutineError):
Subscriber(handler)

def test_raise_handler_error(self):
def test_handler_of_wrong_type(self):
with self.assertRaises(exceptions.HandlerError):
Subscriber(tuple)

def test_not_callable_object_handler(self):
class NotCallable:
name = 'test'

with self.assertRaises(exceptions.HandlerError):
Subscriber(NotCallable())

def test_raise_not_error(self):
with self.assertRaises(exceptions.NotCallableError):
Subscriber('foo')

Expand Down
Loading

0 comments on commit 10cf990

Please sign in to comment.