Skip to content

Commit

Permalink
Merge pull request #9 from iky/rc
Browse files Browse the repository at this point in the history
Rc
  • Loading branch information
iky authored Aug 25, 2017
2 parents 7dabc9c + 84a4458 commit 5455dc5
Show file tree
Hide file tree
Showing 10 changed files with 603 additions and 56 deletions.
43 changes: 36 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
language: python

python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- '2.7'
- '3.3'
- '3.4'
- '3.5'
- '3.6'
- nightly

install:
- pip install -U pip setuptools
- pip install -U -e ".[dev]"
- pip install -U pip setuptools
- pip install -U -e ".[dev]"

script:
- make test

deploy:

- provider: pypi

server: https://test.pypi.org/legacy/

on:
branch: rc
repo: iky/nameko-slack
condition: $TRAVIS_PYTHON_VERSION = "2.7"

user: iky
password:
secure: vQEPN4LK/cQBOAQYIqOHIQc5uc4IlzlNOM6hZ8/n9BOD5HYDXd0ZWEO2EK7D2Bl5ugD0liGS4vBlTPjdNUE8CchuEMuxramg699dFSipo8KOfLOtys7E2MQE/X0Hc6Z8hv49pCVuLFomUfhqm0jc+bLiIY0pFnz5d0WpaoeGL+dy7+NewBjOve3xh5xR4fPA8afB9y8iTe9dzLQmUDZ68MA9lYr66Y05uZLCih20/U9gt66/umISQOPsglRDov02Zzv8vZapeOxh8iESWO8Nnpp7zqsYyzY9ge4ljlvpkmhgd5jCH6B9s3eHkX56rYZn47YextHbBpI0ZBpBTB2QLo11P2tDxTux+fCdtChSJrpc/fLZ+NsiO6OV9jROf1fEGXqST0DNj7FkxXlfJ5RkBEfE1swCD2x2qaXHzFuFqdDuXRzW8EBn4KqF7GX7m5sJkZlR791DaKQkYaIqPq568vR+KPPfbwcz0mnYPJSsnwNf17K8FL1fL09ZBAOp7jsaj3VD007WVw2AfwvaN/9yPTPNd3wexD60u6h+qGU4BIovq1QtbQm8enGHGocgc+kmhgEgwoHSPBUxGXRSBu8O2+VQBDBDVARHvtmFdACIdyly6YTdzXOhcWMYiGKb6hOKlEkrKpF2fBfcGdFev9czN/M+scH60eHaZNSCtNRrlEY=

- provider: pypi

on:
tags: true
repo: iky/nameko-slack
condition: $TRAVIS_PYTHON_VERSION = "3.5"

distributions: "sdist bdist_wheel"

user: nameko-slack
password:
secure: CUC4Fojc+tsnztly2UCVWKl/VfP4+mePV6P+0ZHoNN05P7gliXfgnELRcbG9gg9lVPiEg2NpF3dtcTg0XB1bFf+0vZST+XN1iPydYPnTU5wA94a4OCdnEhVtYhN2kazxpBlBArRofDaSoAsZbhoC/owBjKSEPOw0PxYJf51mvotcsMeGjj2FuhkhRWNJjkmseGOQVRGEfPyrvJDoJ4tCrDT88wDc1euZTTEeIZ19W/QvA3n5MVOntYWxMW4dPifE090ZgGSuYGt/p314UB84klYL4pZj8L94T8h4VP+IWz8B6Du9M5X6j/lhNziLfLC9ZxhL75zu2rorvK4I57cdRpCWOxa+J+Uf8QlVXgDSyyLoO+9guQj4BEpttw1EqTi++WJQKJXVv3ZvIB5u3ksSQZkCOR7rt4yvzV6ERx6FjbqfsKJDavx4utptVzs4kkdqAHq4Ei3Lq/OHN6VAFqtv8mtiUkL2gYDOqyl1LugtIV1Xn2bcA3Uykv3BJj1YULwMMoKo3xo+mQeHm4Im4fvQxR9ahW7sdTfYOlKTNl+fqz91sTT3b6gdRPl6k2CeZWAPGXcpbWR+Cl3ay1aYU/vFVm+4j2APVw5Wq8THRK7qb0lUhZKguY3GFUeM4Ly2aq4eNcrkZTX+gyvDMkkIk7QrPOF/W7seMYTnyZYagXD6Ypw=
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Release Notes
=============

Version 0.0.4
-------------

Release 2017-08-25

* Adds Web API dependency provider
* Adds multi-bot support for RTM extension and Web API dependency provider


Version 0.0.3
-------------

Expand Down
119 changes: 119 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,122 @@ entrypoint:
@rtm.handle_message
def sure(self, event, message):
return 'sure, {}'.format(message)
Run multiple RTM bots:

.. code:: yaml
# config.yml
SLACK:
BOTS:
alice: ${ALICE_BOT_TOKEN}
bob: ${BOB_BOT_TOKEN}
.. code:: python
# service.py
from nameko_slack import rtm
class Service:
name = 'some-service'
@rtm.handle_message(bot_name='alice')
def listen_as_alice(self, event, message):
pass
@rtm.handle_message(bot_name='bob')
def listen_as_bob(self, event, message):
pass
.. code::
$ ALICE_BOT_TOKEN=xoxb-aaa-111 BOB_BOT_TOKEN=xoxb-bbb-222 nameko run --config ./config.yaml service
starting services: some-service
WEB API Client
==============

A simple dependency provider wrapping `Slack WEB API client`_.


.. _Slack WEB API client: http://slackapi.github.io/python-slackclient/basic_usage.html#sending-a-message


Usage
-----

The dependency provider uses the same config key as the RTM extension:

.. code:: yaml
# config.yml
AMQP_URI: 'pyamqp://guest:guest@localhost'
SLACK:
TOKEN: ${SLACK_BOT_TOKEN}
.. code:: python
# service.py
from nameko.rpc import rpc
from nameko_slack import web
class Service:
name = 'some-service'
slack = web.Slack()
@rpc
def say_hello(self, name):
self.slack.api_call(
'chat.postMessage',
channel="#nameko",
text="Hello from Nameko! :tada:")
You can also use multiple bots:

.. code:: yaml
# config.yml
AMQP_URI: 'pyamqp://guest:guest@localhost'
SLACK:
BOTS:
alice: ${ALICE_BOT_TOKEN}
bob: ${BOB_BOT_TOKEN}
.. code:: python
# service.py
from nameko.rpc import rpc
from nameko_slack import web
class Service:
name = 'some-service'
alice = web.Slack('alice')
bob = web.Slack('bob')
@rpc
def say_hello(self):
self.alice.api_call(
'chat.postMessage',
channel="#nameko",
text="Hello from Alice! :tada:")
self.bob.api_call(
'chat.postMessage',
channel="#nameko",
text="Hello from Bob! :tada:")
2 changes: 2 additions & 0 deletions nameko_slack/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CONFIG_KEY = 'SLACK'
DEFAULT_BOT_NAME = 'default'
82 changes: 46 additions & 36 deletions nameko_slack/rtm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,82 @@
import re

import eventlet
from nameko.exceptions import ConfigurationError
from nameko.extensions import Entrypoint, ProviderCollector, SharedExtension
from slackclient import SlackClient

from nameko_slack import constants

EVENT_TYPE_MESSAGE = 'message'

EVENT_TYPE_MESSAGE = 'message'

class SlackRTMClient(SharedExtension, ProviderCollector):
"""
Slack Real Time Messaging API Client

"""
class SlackRTMClientManager(SharedExtension, ProviderCollector):

def __init__(self):

super(SlackRTMClient, self).__init__()
super(SlackRTMClientManager, self).__init__()

self.read_interval = 1

self.token = None

self._client = None
self._handlers = set()
self.clients = {}

def setup(self):
config = self.container.config.get('SLACK', {})
self.token = config.get('TOKEN')

try:
config = self.container.config[constants.CONFIG_KEY]
except KeyError:
raise ConfigurationError(
'`{}` config key not found'.format(constants.CONFIG_KEY))

token = config.get('TOKEN')
clients = config.get('BOTS')
if token:
self.clients[constants.DEFAULT_BOT_NAME] = SlackClient(token)
if clients:
for bot_name, token in clients.items():
self.clients[bot_name] = SlackClient(token)

if not self.clients:
raise ConfigurationError(
'At least one token must be provided in `{}` config'
.format(constants.CONFIG_KEY))

def start(self):
self._register_handlers()
self._connect()
self.container.spawn_managed_thread(self.run)
for bot_name, client in self.clients.items():
client.server.rtm_connect()
run = partial(self.run, bot_name, client)
self.container.spawn_managed_thread(run)

def run(self):
def run(self, bot_name, client):
while True:
for event in self._client.rtm_read():
self.handle(event)
for event in client.rtm_read():
self.handle(bot_name, event)
eventlet.sleep(self.read_interval)

def _register_handlers(self):
def handle(self, bot_name, event):
for provider in self._providers:
self._handlers.add(provider.handle_event)

def _connect(self):
self._client = SlackClient(self.token)
self._client.server.rtm_connect()

def handle(self, event):
for handle_event in self._handlers:
handle_event(event)
if provider.bot_name == bot_name:
provider.handle_event(event)

def reply(self, event, message):
self._client.rtm_send_message(event['channel'], message)
def reply(self, bot_name, event, message):
client = self.clients[bot_name]
client.rtm_send_message(event['channel'], message)


class RTMEventHandlerEntrypoint(Entrypoint):

client = SlackRTMClient()
clients = SlackRTMClientManager()

def __init__(self, event_type=None):
def __init__(self, event_type=None, bot_name=None):
self.bot_name = bot_name or constants.DEFAULT_BOT_NAME
self.event_type = event_type

def setup(self):
self.client.register_provider(self)
self.clients.register_provider(self)

def stop(self):
self.client.unregister_provider(self)
self.clients.unregister_provider(self)

def handle_event(self, event):
if self.event_type and event.get('type') != self.event_type:
Expand All @@ -85,7 +94,8 @@ def handle_event(self, event):

class RTMMessageHandlerEntrypoint(RTMEventHandlerEntrypoint):

def __init__(self, message_pattern=None):
def __init__(self, message_pattern=None, bot_name=None):
self.bot_name = bot_name or constants.DEFAULT_BOT_NAME
if message_pattern:
self.message_pattern = re.compile(message_pattern)
else:
Expand Down Expand Up @@ -113,7 +123,7 @@ def handle_event(self, event):

def handle_result(self, event, worker_ctx, result, exc_info):
if result:
self.client.reply(event, result)
self.clients.reply(self.bot_name, event, result)
return result, exc_info


Expand Down
41 changes: 41 additions & 0 deletions nameko_slack/web.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from nameko.exceptions import ConfigurationError
from nameko.extensions import DependencyProvider
from slackclient import SlackClient

from nameko_slack import constants


class Slack(DependencyProvider):

def __init__(self, bot_name=None):
self.bot_name = bot_name
self.client = None

def setup(self):

try:
config = self.container.config[constants.CONFIG_KEY]
except KeyError:
raise ConfigurationError(
'`{}` config key not found'.format(constants.CONFIG_KEY))

if self.bot_name:
try:
token = config['BOTS'][self.bot_name]
except KeyError:
raise ConfigurationError(
'No token for `{}` bot in `{}` config'
.format(self.bot_name, constants.CONFIG_KEY))
else:
token = (
config.get('BOTS', {}).get(constants.DEFAULT_BOT_NAME) or
config.get('TOKEN'))
if not token:
raise ConfigurationError(
'No token provided by `{}` config'
.format(constants.CONFIG_KEY))

self.client = SlackClient(token)

def get_dependency(self, worker_ctx):
return self.client
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='nameko-slack',
version='0.0.3',
version='0.0.4',
description='Nameko extension for interaction with Slack APIs',
long_description=open('README.rst').read(),
author='Ondrej Kohout',
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pytest
from nameko_slack import constants


@pytest.fixture
def config():
return {constants.CONFIG_KEY: {'TOKEN': 'abc-123'}}
Loading

0 comments on commit 5455dc5

Please sign in to comment.