Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rc #9

Merged
merged 18 commits into from
Aug 25, 2017
Merged

Rc #9

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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