-
Notifications
You must be signed in to change notification settings - Fork 49
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
Feature social say #522 #524
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #524 +/- ##
===========================================
+ Coverage 70.33% 70.46% +0.12%
===========================================
Files 62 64 +2
Lines 5418 5468 +50
Branches 752 759 +7
===========================================
+ Hits 3811 3853 +42
- Misses 1398 1406 +8
Partials 209 209
Continue to review full report at Codecov.
|
Thanks Jeremy. I'll take a look this weekend... I've got a very busy week in-progress now. |
I'd prefer to not have this integrated into core.py, but rather as a small binary (e.g. POCS/script/say_to_twitter.py), which takes command line args or can read from a yaml file as you've done, to configure the zeromq reading and the twitter writing. This makes it easier to test, and it doesn't further complicate the already complicated core.py? Thoughts? |
Sorry, I've been traveling to and talking at a conference so haven't looked
at this used but would tend to agree that this feature shouldn't be in
core.py. I'll try to take a look later tonight but it's long days here.
Cheers,
Wilfred
…On Sun, Jun 10, 2018, 12:40 James Synge ***@***.***> wrote:
I'd prefer to not have this integrated into core.py, but rather as a small
binary (e.g. POCS/script/say_to_twitter.py), which takes command line args
or can read from a yaml file as you've done, to configure the zeromq
reading and the twitter writing. This makes it easier to test, and it
doesn't further complicate the already complicated core.py?
Thoughts?
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#524 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAEUUJ8FKnCFX-bu8nKUro7Gg-Hn4uBpks5t7VoYgaJpZM4UdgpN>
.
|
Also, on quick glance, can you remove the camelCase? We're trying to
maintain a consistency with pep8.
Thanks!
…On Sun, Jun 10, 2018, 14:07 Wilfred Tyler Gee ***@***.***> wrote:
Sorry, I've been traveling to and talking at a conference so haven't
looked at this used but would tend to agree that this feature shouldn't be
in core.py. I'll try to take a look later tonight but it's long days here.
Cheers,
Wilfred
On Sun, Jun 10, 2018, 12:40 James Synge ***@***.***> wrote:
> I'd prefer to not have this integrated into core.py, but rather as a
> small binary (e.g. POCS/script/say_to_twitter.py), which takes command line
> args or can read from a yaml file as you've done, to configure the zeromq
> reading and the twitter writing. This makes it easier to test, and it
> doesn't further complicate the already complicated core.py?
>
> Thoughts?
>
> —
> You are receiving this because your review was requested.
> Reply to this email directly, view it on GitHub
> <#524 (comment)>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AAEUUJ8FKnCFX-bu8nKUro7Gg-Hn4uBpks5t7VoYgaJpZM4UdgpN>
> .
>
|
Hi thanks for the comments. Yes I can certainly have it a separate script. I left in core as there was already an asynchronous handler for commands so I reused the similar code and made it totally configurable from the config. Also the slack output was in core. If I have it on a separate script, how should it get invoked? I.e. as a totally separate process invoked from command line or invoked by Regarding the camel case no probs, as it is passing the unit test for consistency I thought it was ok. If you could give me guidance on the invocation question above I'll modify the PR accordingly. Cheers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Submitted here with a few comments. You were right to follow what was existing with the message processes, but they are slightly separate as some of those message loops need to interact with the running instance. The slack and twitter communication can be external as they mostly just need to receive the messages and then forward them on. I don't think we will ever move to having slack feed messages back to the system.
Also, the messaging items that we have currently are not really ideal and would probably also be removed from core anyway. They were inserted as part of a student project and it hasn't quite ever been cleaned up yet. For your new code it would be best for us implement it a little cleaner.
Sorry for the slow response. I'm at a conference in the US and it has my time fully occupied for this week.
requirements.txt
Outdated
@@ -22,3 +22,5 @@ scikit_image >= 0.12.3 | |||
scipy >= 0.17.1 | |||
transitions >= 0.4.0 | |||
wcsaxes | |||
tweepy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move this to alphabetical order?
pocs/utils/social_slack.py
Outdated
""" | ||
logger = get_root_logger() | ||
|
||
def __init__(self, web_hook, output_timestamp): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's switch this to output_timestamp=False
by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you want to address this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was actually changed to False by default
pocs/utils/social_twitter.py
Outdated
self.output_timestamp = output_timestamp | ||
|
||
except tweepy.TweepError as e: | ||
self.logger.error('Error connecting to Twitter. Err: {} - Message: {}'.format(e.args[0][0]['code'], e.args[0][0]['message'])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably just throw a warning
here and save error
for critical failures.
pocs/utils/social_twitter.py
Outdated
@@ -0,0 +1,32 @@ | |||
import tweepy | |||
|
|||
from pocs.utils import current_time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused?
pocs/utils/social_slack.py
Outdated
@@ -0,0 +1,26 @@ | |||
import requests | |||
|
|||
from pocs.utils import current_time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused.
So I think we want to make this a separate script in the |
Thanks for the comments @wtgee No problem about the response time, I'm also working at this when I can. Regarding the camel casing, I assume you mean the variables in code like I'll wait for @jamessynge guidance, but I think I got the scripts architecture a little better now, there is a So I could really have Cheers and enjoy the conference. |
Yup, the class names are fine. Relevant pep8 here.
Yeah, sounds good about the above.
Let's keep in Thanks! |
Amended PR based on feedback @wtgee @jamessynge. Sorry it took a while. |
pocs/utils/social_twitter.py
Outdated
|
||
self.api = tweepy.API(auth) | ||
self.output_timestamp = output_timestamp | ||
except tweepy.TweepError as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this error mean that the object is unusable? If so, it would probably be best to raise the exception again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this could happen due to config error. I'll address this.
# access_token_secret: [your_access_token_secret] | ||
# slack: | ||
# webhook_url: [your_webhook_url] | ||
# output_timestamp: False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should output_timestamp
also appear in the twitter config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can but I did not provide it in the template as Twitter will reject status updates which are considered duplicates. So if you start/stop/start your POCS instance for example, Twitter will reject the duplicated statuses without the timestamp.
Hence I left the possibility for the user to override in case he needs no timestamp but the recommended default is False
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. If the updates are identical but not consecutive, are they still considered duplicates? I.e. if the messages are A, A, and A, I gather that only the first A is allowed. But what about A, B, A?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification.
pocs/utils/social_slack.py
Outdated
|
||
class SocialSlack(object): | ||
|
||
"""Messaging class to output to Slack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please use punctuation. And in this case I'd end the docstring on that first line, e.g.:
"""Messaging class to output to Slack."""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry this is my editor playing with quotes, I will fix this
pocs/utils/social_slack.py
Outdated
""" | ||
logger = get_root_logger() | ||
|
||
def __init__(self, web_hook, output_timestamp): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you want to address this?
pocs/utils/social_twitter.py
Outdated
""" | ||
logger = get_root_logger() | ||
|
||
def __init__(self, consumer_key, consumer_secret, access_token, access_token_secret, output_timestamp): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether this class should just get a dict (from the yaml reader) representing the config inside the twitter block of the config file. That would allow the caller to be relatively independent of the set of configs needed by one of these classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is certainly possible, however the style of coding in the other modules I saw is to handle the config in the 'main' module rather than the utils module. I think that is how PanMessaging works for example.
I thought passing the parameters rather than a dictionary was a little clearer as it's a sort of DI of the stuff this class needs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not assume that we've been perfect with our previous code! ;-)
But this does remind me of another approach: pass in the dict as **kwargs so that they get expanded when passed into SocialTwitter or SocialSlack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I'll look into this thanks
parser.print_help() | ||
sys.exit(1) | ||
|
||
# Initialise social sinks to None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
English, rather than American, spelling of initialize. Just curious if you are from outside of the US.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I am Sydney based. I can change this if it's a problem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not a problem, just curious.
scripts/run_social_messaging.py
Outdated
if args.from_config: | ||
config = load_config(config_files=['pocs']) | ||
|
||
if 'social_accounts' in config: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternately, could do something like:
social_config = config.get('social_accounts', {})
twitter_config = social_config.get('twitter')
if twitter_config:
try:
social_twitter = SocialTwitter(twitter_config)
except SocialException as e:
log...
dict.get(key, default_value=None) defaults to returning None, so you don't have to worry about testing if the key is present. And you can provide a default value other than None.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok that's great. I'm not familiar with Python hence I copied this style from somewhere in core.py
. I can change to this as I also find it cleaner thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per my comment above, you can do:
social_twitter = SocialTwitter(**twitter_config)
Then declare SocialTwitter's ctor as:
def __init__(self, kwarg_one=some_default, kwarg_two=None, ...)
scripts/run_social_messaging.py
Outdated
if social_config is not None and 'twitter' in social_config: | ||
twitter_config = social_config['twitter'] | ||
if twitter_config is not None and \ | ||
'consumer_key' in twitter_config and \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you got with my suggestion earlier, this validation will move into the twitter class. And you could of course use a factory method rather than the constructor to do your validation, if that made things cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above about handling config in main module
scripts/run_social_messaging.py
Outdated
social_slack = SocialSlack(slack_config['webhook_url'], | ||
output_timestamp) | ||
else: | ||
arg_error('social_accounts setting not defined in config.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't emit an error for this, only if the config is actually bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
scripts/run_social_messaging.py
Outdated
else: | ||
arg_error('social_accounts setting not defined in config.') | ||
|
||
if not social_twitter and not social_slack: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's treat this as normal. That way our startup scripts can always include executing this script, and it will just exit if there are no social accounts specified, with a non-error message such as "No social messaging sinks defined, exiting."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok sure
Thanks very much for the adjustments, I think this is getting very close. |
Oh, and can you think how to test this? |
@@ -0,0 +1,25 @@ | |||
import requests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with the requests library. Where does that come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wtgee Can you tell us where this comes from? It doesn't appear to be standard Python.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not standard python but it's more straight-forward to use than urllib
or urllib2
. I was actually surprised we weren't already using it somewhere. It's a popular library so I don't have any issues including it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have this in requirements.txt. @wtgee do you know how we are getting it installed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's put it in the requirements. We hadn't used this before so wasn't there.
Hi @jamessynge thanks for the review. Please see my individual comments inline. |
Hi @jamessynge, @wtgee I just pushed another commit addressing (hopefully) all the points we discussed. Regarding testing, I'm not too sure how to write an unit test for this. I guess we could mock config values and send some tweets/slacks from the unit test, however test accounts would be needed... |
Thanks, I'll take a look soon. Regarding testing, I found these links with
a quick search:
https://bobbelderbos.com/2016/06/unittest-with-python/
https://github.com/python-glasgow/pythonglasgow/blob/master/tests/test_twitter.py
On Mon, Jun 18, 2018, 10:07 PM Luca ***@***.***> wrote:
Hi @jamessynge <https://github.com/jamessynge>, @wtgee
<https://github.com/wtgee>
I just pushed another commit addressing (hopefully) all the points we
discussed.
Regarding testing, I'm not too sure how to write an unit test for this. I
guess we could mock config values and send some tweets/slacks from the unit
test, however test accounts would be needed...
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#524 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABYmUwN-MbrIzQJVz85erohb38MVIfTOks5t-FzbgaJpZM4UdgpN>
.
On Jun 18, 2018 10:07 PM, "Luca" <[email protected]> wrote:
Hi @jamessynge <https://github.com/jamessynge>, @wtgee
<https://github.com/wtgee>
I just pushed another commit addressing (hopefully) all the points we
discussed.
Regarding testing, I'm not too sure how to write an unit test for this. I
guess we could mock config values and send some tweets/slacks from the unit
test, however test accounts would be needed...
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#524 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/ABYmUwN-MbrIzQJVz85erohb38MVIfTOks5t-FzbgaJpZM4UdgpN>
.
|
Hi @jamessynge Thanks for the links about unit testing, they were useful. Python's mocking framework is very interesting. I added the unit test module, it took me a little to finish as I also made a small change in the sink classes to better handle empty parameters, and I removed inconsistent use of single and double quotes. Thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, modulo a few nits.
# access_token_secret: [your_access_token_secret] | ||
# slack: | ||
# webhook_url: [your_webhook_url] | ||
# output_timestamp: False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification.
pocs/tests/test_social_messaging.py
Outdated
|
||
|
||
def test_send_message_twitter_no_timestamp(twitter_config): | ||
with unittest.mock.patch.dict(twitter_config, {'output_timestamp': False}): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, for this kind of nested with
you can put multiple with_items
in a single with statement. See the docs here: https://docs.python.org/3/reference/compound_stmts.html#the-with-statement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will change this thanks
@@ -0,0 +1,25 @@ | |||
import requests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have this in requirements.txt. @wtgee do you know how we are getting it installed?
@@ -0,0 +1,103 @@ | |||
import pytest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job on the tests, thanks.
pocs/utils/social_slack.py
Outdated
def send_message(self, msg, timestamp): | ||
try: | ||
if self.output_timestamp: | ||
post_msg = '{} - {}'.format(timestamp, msg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I think we might find it easier to consume these messages if the timestamp comes second, after msg. For example, on my phone I'd rather see timestamp truncated off than the msg.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK will do
pocs/utils/social_twitter.py
Outdated
def send_message(self, msg, timestamp): | ||
try: | ||
if self.output_timestamp: | ||
retStatus = self.api.update_status('{} - {}'.format(timestamp, msg)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here about order.
parser.add_argument( | ||
'--from_config', | ||
action='store_true', | ||
help='Read social channels config from the pocs.yaml and pocs_local.yaml config files.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point we don't really have any need for argparse, right? If you agree, I'd drop it until needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jamessynge you mean because there is only one option or just because it is the default?
I'd prefer that if we parse any command line options we do it with ArgParse, even if it is only one argument. It's more consistent and ArgParse looks cleaner than using sys.argv
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, what we haven't done is establish conventions on naming arguments from the command line. I think the standard would be to always do hypens instead of underscores on the command line, which ArgParse automatically adjusts. So --from-config
on the command line becomes args.from_config
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So @wtgee, @jamessynge nothing to do here then right? BTW I lifted this code from run_messaging_hub.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
#522
Created entries in the config file
pocs.yaml
, handling code incore.py
and social sinks classes for Twitter and Slack.