Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from myoung34/webhooks
Browse files Browse the repository at this point in the history
Add support for webhooks
  • Loading branch information
myoung34 authored Feb 4, 2020
2 parents 55094af + be9a69d commit 48ed7b7
Show file tree
Hide file tree
Showing 21 changed files with 584 additions and 57 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: install pre-reqs
run: sudo apt-get install libbluetooth-dev && pip install -r requirements.txt
run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install -r requirements.txt
- name: isort
run: isort -c -rc tilty -sp .
- name: pylint
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: install pre-reqs
run: sudo apt-get install libbluetooth-dev && pip install -r requirements.txt
run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install -r requirements.txt
- name: isort
run: isort -c -rc tilty -sp .
- name: pylint
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: install pre-reqs
run: sudo apt-get install libbluetooth-dev && pip install -r requirements.txt
run: sudo apt-get update && sudo apt-get install libbluetooth-dev && pip install -r requirements.txt
- name: isort
run: isort -c -rc tilty -sp .
- name: pylint
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ __pycache__
*.xml
.coverage
*.sw[o-p]
config.ini
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ RUN python setup.py install
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["-r", "--config-file", "config.ini"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ install:
rm -rf venv

gen_requirements:
poetry run pip freeze > requirements.txt
poetry run pip freeze | grep -Ev 'github.*myoung34.tilty' > requirements.txt

test:
tox
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ As it progresses it will have pluggablel emitters such as:
### As a cli ###

```
$ cat <<EOF >config.ini
[general]
sleep_interval = 10
[webhook]
url = http://www.foo.com
payload_template = {"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "timestamp": "{{ timestamp }}"}
method = GET
EOF
$ tilty
```

Expand Down Expand Up @@ -57,3 +66,13 @@ $ docker run -it --net=host myoung34/tilty:latest-arm # for ARM
$ git clone https://github.com/myoung34/tilty
$ pip install -e .
```

## Development ##

```
$ docker run -it -v $(pwd):/src -w /src --entrypoint /bin/sh python:3.7-alpine
$ apk add -U openssl-dev alpine-sdk libffi-dev python3-dev py3-bluez bluez-dev
$ pip3 install poetry
$ poetry install
$ poetry run tox
```
2 changes: 1 addition & 1 deletion entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/sh
exec tilty -r
exec tilty "${@}"
417 changes: 381 additions & 36 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ authors = ["Marcus Young <[email protected]>"]
license = "MIT"

[tool.poetry.dependencies]
python = ">=3.6,<3.8"
click = "^7.0"
pybluez = "^0.22.0"
requests = "^2.22"
jinja2 = "^2.11.1"

[tool.poetry.dev-dependencies]
flake8 = "^3.7"
Expand Down Expand Up @@ -37,6 +40,3 @@ whitelist_externals = make
bash
pylint
"""
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
9 changes: 8 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
astroid==2.3.3
attrs==19.3.0
certifi==2019.11.28
chardet==3.0.4
Click==7.0
coverage==5.0
entrypoints==0.3
filelock==3.0.12
flake8==3.7.9
idna==2.8
importlib-metadata==1.3.0
isort==4.3.21
Jinja2==2.11.1
lazy-object-proxy==1.4.3
MarkupSafe==1.1.1
mccabe==0.6.1
more-itertools==8.0.2
packaging==19.2
pluggy==0.13.1
py==1.8.0
pybluez==0.22
PyBluez==0.22
pycodestyle==2.5.0
pyflakes==2.1.1
pylint==2.4.4
pyparsing==2.4.5
pytest==5.3.2
pytest-cov==2.8.1
requests==2.22.0
six==1.13.0
toml==0.10.0
tox==3.14.2
typed-ast==1.4.0
urllib3==1.25.7
virtualenv==16.7.8
wcwidth==0.1.7
wrapt==1.11.2
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
author='Marcus Young',
author_email='[email protected]',
py_modules=['tilty', 'blescan'],
version='0.0.3',
version='0.1.0',
packages=find_packages(exclude=['tests*']),
install_requires=[
'Click',
'Jinja2',
'pybluez',
'requests',
],
entry_points={
'console_scripts': [
Expand Down
15 changes: 15 additions & 0 deletions tests/mock_config_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
class MockConfigParser:
def __init__(self):
pass

def __getitem__(self, key):
return {
'url': 'http://www.google.com',
'headers': {'Content-Type': 'application/json'},
'payload_template': '{"color": "{{ color }}", "gravity": {{ gravity }}, "temp": {{ temp }}, "timestamp": "{{ timestamp }}"}',
'method': 'GET'
}

def has_section(*args, **kwargs):
return True
Empty file added tests/mock_keys.py
Empty file.
6 changes: 2 additions & 4 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_cli_no_params_no_valid_data(
runner = CliRunner()
result = runner.invoke(cli.run, [])
assert result.exit_code == 0
assert result.output == 'Scanning for Tilt data...\n' # noqa
assert result.output == 'Scanning for Tilt data...\n\n' # noqa


@mock.patch('tilty.blescan.parse_events', return_value=[]) # noqa
Expand All @@ -38,17 +38,15 @@ def test_cli_no_params_no_data(
runner = CliRunner()
result = runner.invoke(cli.run, [])
assert result.exit_code == 0
assert result.output == 'Scanning for Tilt data...\n' # noqa
assert result.output == 'Scanning for Tilt data...\n\n' # noqa

@mock.patch('tilty.blescan.parse_events', return_value=[{'uuid': 'a495bb30c5b14b44b5121370f02d74de', 'major': 2, 'minor': 1}]) # noqa
@mock.patch('tilty.tilt_device.datetime') # noqa
@mock.patch('tilty.blescan.hci_le_set_scan_parameters') # noqa
@mock.patch('tilty.blescan.hci_enable_le_scan') # noqa
def test_cli_no_params_success(
bt_enable_scan,
bt_set_scan,
bt_events,
dt,
):
runner = CliRunner()
result = runner.invoke(cli.run, [])
Expand Down
26 changes: 25 additions & 1 deletion tests/test_tilty.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from unittest import mock

from tilty import tilt_device
from tilty import tilt_device, tilty
from mock_config_parser import MockConfigParser


@mock.patch('tilty.blescan.parse_events', return_value=[{'uuid': 'foo', 'major': 2, 'minor': 1}]) # noqa
Expand All @@ -10,3 +11,26 @@ def test_scan_for_tilt_data(
):
t = tilt_device.TiltDevice()
t.scan_for_tilt_data()



@mock.patch('tilty.emitters.webhook.Webhook')
def test_scan_for_tilt_data(
mock_webhook,
):
config = MockConfigParser()
tilty.emit(
config,
{'color': 'black', 'gravity': 1, 'temp': 32, 'timestamp': 155558888}
)
assert mock_webhook.mock_calls == [
mock.call(
config={
'url': 'http://www.google.com',
'headers': {'Content-Type': 'application/json'},
'method': 'GET',
'payload': {'color': 'black', 'gravity': 1, 'temp': 32, 'timestamp': '155558888'}
}
),
mock.call().emit()
]
36 changes: 36 additions & 0 deletions tests/test_webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from unittest import mock

import pytest

from tilty.emitters import webhook


@mock.patch('tilty.emitters.webhook.METHODS')
def test_webhook_get(
mock_requests,
):
config = {
'url': 'http://www.google.com',
'headers': {'Content-Type': 'application/json'},
'payload': {'b': 'b1'}, 'method': 'GET'
}
webhook.Webhook(config=config).emit()
assert mock_requests.mock_calls == [
mock.call.get('GET'),
mock.call.get()(
json={'b': 'b1'},
headers={'Content-Type': 'application/json'},
url='http://www.google.com'
)
]


def test_webhook_invalid_method():
config = {
'url': 'http://www.google.com',
'headers': {'Content-Type': 'application/json'},
'payload': {'b': 'b1'}, 'method': 'bad'
}
with pytest.raises(TypeError):
webhook.Webhook(config=config).emit()
25 changes: 19 additions & 6 deletions tilty/cli.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
# -*- coding: utf-8 -*-
""" Main Click methods """

import configparser
import logging
from time import sleep

import click

from tilty import tilt_device
from tilty.tilty import emit

LOGGER = logging.getLogger()
LOGGER.setLevel(logging.INFO)

CONFIG = configparser.ConfigParser()


@click.command()
@click.option(
Expand All @@ -19,19 +23,28 @@
is_flag=True,
help="Keep running until SIGTERM",
)
def run(keep_running):
@click.option(
'--config-file',
'-c',
default='config.ini',
help="configuration file path",
)
def run(
keep_running,
config_file='config.ini',
):
""" main cli entrypoint
"""
CONFIG.read(config_file)
click.echo('Scanning for Tilt data...')
t = tilt_device.TiltDevice()
t.start()
if keep_running:
while True:
tilt_data = t.scan_for_tilt_data()
if tilt_data:
click.echo(tilt_data)
sleep(10)
emit(config=CONFIG, tilt_data=tilt_data)
sleep(int(CONFIG['general']['sleep_interval']))
else:
tilt_data = t.scan_for_tilt_data()
if tilt_data:
click.echo(tilt_data)
click.echo(tilt_data)
emit(config=CONFIG, tilt_data=tilt_data)
Empty file added tilty/emitters/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions tilty/emitters/webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
""" Webhook emitter """
import requests

METHODS = {
"GET": requests.get,
"POST": requests.post,
}


class Webhook: # pylint: disable=too-few-public-methods
""" Class to represent the actual device """
def __init__(self, config):
""" Initializer
Args:
config: (dict) represents the configuration for the emitter
"""
self.url = config['url']
self.method = METHODS.get(config['method'])
self.headers = config['headers']
self.payload = config['payload']

def emit(self, **kwargs): # pylint: disable=no-self-use,unused-argument
""" Initializer
Args:
"""
self.method(
url=self.url,
headers=self.headers,
json=self.payload,
)
33 changes: 33 additions & 0 deletions tilty/tilty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
""" Class to encapsulate all the emitter logic """
import json

from jinja2 import Template

from tilty.emitters import webhook


def emit(config, tilt_data):
""" Find and call emitters from config
config (dict): configuration file loaded from disk
tilt_data (dict): data returned from valid tilt device scan
"""
if tilt_data is None:
return

if config.has_section('webhook'):
_template = Template(config['webhook']['payload_template'])
_config = {
'url': config['webhook']['url'],
'headers': config['webhook'].get('headers'),
'method': config['webhook']['method'],
'payload': json.loads(_template.render(
color=tilt_data['color'],
gravity=tilt_data['gravity'],
temp=tilt_data['temp'],
timestamp=tilt_data['timestamp'],
)),
}
_webhook = webhook.Webhook(config=_config)
_webhook.emit()

0 comments on commit 48ed7b7

Please sign in to comment.