From d5132a2ca3a75c769debb53bda8b2b9e304501ee Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Thu, 2 Jul 2020 15:31:50 -0300 Subject: [PATCH] Filtrate with regex (#9) Create new feature to allow regex in filtrate decorator. --- .editorconfig | 44 ++++++++++++++ LICENSE | 2 +- Pipfile | 1 + examples/record_registers_in_redis.py | 1 + jaspion/sketch.py | 2 +- jaspion/utils.py | 16 +++-- pytest.ini | 3 + setup.py | 2 +- tests/test_utils.py | 86 +++++++++++++++++++++++++++ 9 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 .editorconfig create mode 100644 pytest.ini create mode 100644 tests/test_utils.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..de857bb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,44 @@ +# https://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 + +# Docstrings and comments use max_line_length = 79 +[*.py] +max_line_length = 119 + +# Use 2 spaces for the HTML files +[*.html] +indent_size = 2 + +# The JSON files contain newlines inconsistently +[*.json] +indent_size = 2 +insert_final_newline = ignore + +[**/admin/js/vendor/**] +indent_style = ignore +indent_size = ignore + +# Minified JavaScript files shouldn't be changed +[**.min.js] +indent_style = ignore +insert_final_newline = ignore + +# Makefiles always use tabs for indentation +[Makefile] +indent_style = tab + +# Batch files use tabs for indentation +[*.bat] +indent_style = tab + +[docs/**.txt] +max_line_length = 79 \ No newline at end of file diff --git a/LICENSE b/LICENSE index a24b262..decec4d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2018 Vitor Hugo de Oliveira Vargas +# Copyright 2018 Vitor Hugo de Oliveira Vargas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/Pipfile b/Pipfile index 9f826a0..1c195b6 100644 --- a/Pipfile +++ b/Pipfile @@ -10,6 +10,7 @@ twine = "*" travis = "*" flake8 = "*" black = "*" +pytest = "*" [packages] greenswitch = "*" diff --git a/examples/record_registers_in_redis.py b/examples/record_registers_in_redis.py index 6bc6d6b..8604887 100644 --- a/examples/record_registers_in_redis.py +++ b/examples/record_registers_in_redis.py @@ -12,6 +12,7 @@ app = Jaspion(**freeswitch) conn = Redis(**redis) + # Save all register in redis with expires of sip message. @app.handle("sofia::register") def register(event): diff --git a/jaspion/sketch.py b/jaspion/sketch.py index a89a865..c9d67f4 100644 --- a/jaspion/sketch.py +++ b/jaspion/sketch.py @@ -1,5 +1,5 @@ -from collections.abc import MutableMapping from collections.abc import MutableSequence +from collections.abc import MutableMapping from collections.abc import Callable import functools import reprlib diff --git a/jaspion/utils.py b/jaspion/utils.py index a4fcddf..b637545 100644 --- a/jaspion/utils.py +++ b/jaspion/utils.py @@ -1,10 +1,11 @@ import functools import typing +import re -def filtrate(key: str, value: str = None): - """Method that allows to filter the events according - to a set 'key', 'value'. +def filtrate(key: str, value: str = None, regex: bool = False): + """ + Method that allows to filter the events accordingto a set 'key', 'value'. Parameters ---------- @@ -12,6 +13,8 @@ def filtrate(key: str, value: str = None): Key to be searched in the event. - value: optional Value needed in the last key. + - regex: optional + Tells whether 'value' is a regular expression. """ def decorator(function: typing.Callable): @@ -19,9 +22,12 @@ def decorator(function: typing.Callable): def wrapper(message): if isinstance(message, dict): if key in message: - if key is None: + content = message[key] + if value is None: + return function(message) + if not regex and content == value: return function(message) - if message[key] == value: + if regex and re.match(value, content): return function(message) return wrapper diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..9b92e7b --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +log_cli=True +console_output_style = count diff --git a/setup.py b/setup.py index d3af9fd..8ecd9fd 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name="Jaspion", - version="0.3.5.5", + version="0.3.6.0", description="FreeSwitch Event Handler based in Flask.", include_package_data=True, license="MIT", diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..911d45b --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,86 @@ +from jaspion.utils import filtrate + +import pytest + + +class Callback: + """ + Class used to represent a callback. + """ + def __init__(self): + self.control = False + + def __call__(self, *args, **kwargs): + self.control = True + + +def test_filtrate_is_a_callable(): + """Verify if 'filtrate' is a callable.""" + assert callable(filtrate) + + +def test_filtrate_require_a_single_argument(): + """Verify if 'filtrate' is a callable.""" + msg = "filtrate() missing 1 required positional argument: 'key'" + with pytest.raises(TypeError) as exc: + filtrate() # pylint: disable=no-value-for-parameter + assert msg in str(exc) + + +def test_filtrate_return_a_callable(): + """Ensures that the filtrate returns a callable when invoked correctly.""" + result = filtrate('key') + assert callable(result) + + +tests = [ + {'decorator': ['key'], 'response': True, 'event': {'key': 'value'}}, + { + 'decorator': ['key'], + 'response': False, + 'event': { + 'invalid_key': 'value' + } + }, + { + 'decorator': ['key', 'value'], + 'response': True, + 'event': { + 'key': 'value' + } + }, + { + 'decorator': ['key', 'value'], + 'response': False, + 'event': { + 'key': 'another_value' + } + }, + { + 'decorator': ['key', '^[a-z]{5}$', True], + 'response': True, + 'event': { + 'key': 'value' + } + }, + { + 'decorator': ['key', '^[a-z]{3}$', True], + 'response': False, + 'event': { + 'key': 'value' + } + } +] + + +@pytest.mark.parametrize('content', tests) +def test_decorator_behavior(content): + """Validates decorator behavior.""" + handler = Callback() + decorator = filtrate(*content['decorator']) + new_handler = decorator(handler) + event = content['event'] + + new_handler(event) + + assert content['response'] == handler.control