Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Text Me Maybe: Smarter Real-World Integrations ...

Text Me Maybe: Smarter Real-World Integrations with Python

Slides from my PyOhio 2016 talk about combining IFTTT (If This Then That), AWS Lambda and API Gateway, Twilio, and Python to make a location-triggered SMS notifier that's also sensitive to days and times.

Mike Pirnat

July 30, 2016
Tweet

More Decks by Mike Pirnat

Other Decks in Programming

Transcript

  1. text me MAYBE smarter real-world integrations with python Mike Pirnat

    ··· @mpirnat ··· http://mike.pirnat.com
  2. – my wife (more or less) Hey–you just left work

    And this is crazy But it’s your drive home So text me maybe?” “
  3. It’s Simple, Right? • Buy a domain • Set up

    a server • Build a TCPA-compliant SMS integration • Write and deploy a REST API • Write a geofenced mobile app and get it deployed to your device • Write the code you actually care about • Probably more things you don’t care about
  4. It’s Simple, Right? • Buy a domain • Set up

    a server • Build a TCPA-compliant SMS integration • Write and deploy a REST API • Write a geofenced mobile app and get it deployed to your device • Write the code you actually care about • Probably more things you don’t care about X
  5. + + https://ifttt.com mobile app - geofence trigger Maker channel

    - HTTP POST action https://aws.amazon.com/lambda AWS API Gateway - instant HTTP API AWS Lambda - serverless Python 2.7 https://www.twilio.com send SMS messages Python SDK
  6. Virtualenv Create and activate a virtual environment: $ virtualenv textmemaybe

    $ cd textmemaybe $ source bin/activate Or perhaps: $ mkvirtualenv textmemaybe $ workon textmemaybe
  7. config.py # Twilio account data... account_sid = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" auth_token =

    "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" from_phone = "+1XXXXXXXXXXXX" # Log level import logging log_level = logging.DEBUG
  8. messages.py GOT_EVENT = "got event {}" IN_WINDOW = "current time

    within window, will attempt to send message" NOT_IN_WINDOW = "current time not within window, ignoring message" SENT = "sent" NOT_SENT = "unsent"
  9. textmemaybe.py """ Your screen was glowing New files Vim was

    loading Late night Brain was rolling Where you think you're coding baby? """
  10. POST some JSON: { "to_phone": "+19995551234", "message": "Mike has left

    the office", "t1": "1500", "t2": "2200", "tz": "US/Eastern", "days": "12345" } Get some JSON back: { "result": "sent" }
  11. POST some JSON: { "to_phone": "+19995551234", "message": "Mike has left

    the office", "t1": "1500", "t2": "2200", "tz": "US/Eastern", "days": "12345" } Get some JSON back: { "result": "sent" }
  12. POST some JSON: { "to_phone": "+19995551234", "message": "Mike has left

    the office", "t1": "1500", "t2": "2200", "tz": "US/Eastern", "days": "12345" } Get some JSON back: { "result": "sent" }
  13. POST some JSON: { "to_phone": "+19995551234", "message": "Mike has left

    the office", "t1": "1500", "t2": "2200", "tz": "US/Eastern", "days": "12345" } Get some JSON back: { "result": "sent" }
  14. from twilio.rest import TwilioRestClient from config import account_sid, auth_token, from_phone

    client = TwilioRestClient(account_sid, auth_token) def send_sms(to_phone, from_phone, body): """ Send an SMS message containing a particular body to a phone number from a phone number. """ return client.messages.create(to=to_phone, from_=from_phone, body=body) message = send_sms(to_phone, from_phone, message_body)
  15. from twilio.rest import TwilioRestClient from config import account_sid, auth_token, from_phone

    client = TwilioRestClient(account_sid, auth_token) def send_sms(to_phone, from_phone, body): """ Send an SMS message containing a particular body to a phone number from a phone number. """ return client.messages.create(to=to_phone, from_=from_phone, body=body) message = send_sms(to_phone, from_phone, message_body)
  16. from twilio.rest import TwilioRestClient from config import account_sid, auth_token, from_phone

    client = TwilioRestClient(account_sid, auth_token) def send_sms(to_phone, from_phone, body): """ Send an SMS message containing a particular body to a phone number from a phone number. """ return client.messages.create(to=to_phone, from_=from_phone, body=body) message = send_sms(to_phone, from_phone, message_body)
  17. from twilio.rest import TwilioRestClient from config import account_sid, auth_token, from_phone

    client = TwilioRestClient(account_sid, auth_token) def send_sms(to_phone, from_phone, body): """ Send an SMS message containing a particular body to a phone number from a phone number. """ return client.messages.create(to=to_phone, from_=from_phone, body=body) message = send_sms(to_phone, from_phone, message_body)
  18. def is_time_between(now, t1, t2): """ Is a time (from a

    datetime) between two other times (as 24-hour 'HHMM' strings)? """ # Convert HHMM strings into tuples hhmm1 = tuple([int(x) for x in [t1[:2], t1[2:]]]) hhmm2 = tuple([int(x) for x in [t2[:2], t2[2:]]]) hhmm = now.hour, now.minute # Start time before end time; span stays within day if hhmm1 <= hhmm2: return hhmm1 <= hhmm <= hhmm2 # End time before start time; span crosses midnight else: return (hhmm1 <= hhmm <= (23, 59)) or \ ((0, 0) <= hhmm <= hhmm2) import datetime now = datetime.datetime.utcnow() if is_time_between(now, t1, t2): message = send_sms(to_phone, from_phone, message_body)
  19. def is_time_between(now, t1, t2): """ Is a time (from a

    datetime) between two other times (as 24-hour 'HHMM' strings)? """ # Convert HHMM strings into tuples hhmm1 = tuple([int(x) for x in [t1[:2], t1[2:]]]) hhmm2 = tuple([int(x) for x in [t2[:2], t2[2:]]]) hhmm = now.hour, now.minute # Start time before end time; span stays within day if hhmm1 <= hhmm2: return hhmm1 <= hhmm <= hhmm2 # End time before start time; span crosses midnight else: return (hhmm1 <= hhmm <= (23, 59)) or \ ((0, 0) <= hhmm <= hhmm2) import datetime now = datetime.datetime.utcnow() if is_time_between(now, t1, t2): message = send_sms(to_phone, from_phone, message_body)
  20. def is_time_between(now, t1, t2): """ Is a time (from a

    datetime) between two other times (as 24-hour 'HHMM' strings)? """ # Convert HHMM strings into tuples hhmm1 = tuple([int(x) for x in [t1[:2], t1[2:]]]) hhmm2 = tuple([int(x) for x in [t2[:2], t2[2:]]]) hhmm = now.hour, now.minute # Start time before end time; span stays within day if hhmm1 <= hhmm2: return hhmm1 <= hhmm <= hhmm2 # End time before start time; span crosses midnight else: return (hhmm1 <= hhmm <= (23, 59)) or \ ((0, 0) <= hhmm <= hhmm2) import datetime now = datetime.datetime.utcnow() if is_time_between(now, t1, t2): message = send_sms(to_phone, from_phone, message_body)
  21. def is_time_between(now, t1, t2): """ Is a time (from a

    datetime) between two other times (as 24-hour 'HHMM' strings)? """ # Convert HHMM strings into tuples hhmm1 = tuple([int(x) for x in [t1[:2], t1[2:]]]) hhmm2 = tuple([int(x) for x in [t2[:2], t2[2:]]]) hhmm = now.hour, now.minute # Start time before end time; span stays within day if hhmm1 <= hhmm2: return hhmm1 <= hhmm <= hhmm2 # End time before start time; span crosses midnight else: return (hhmm1 <= hhmm <= (23, 59)) or \ ((0, 0) <= hhmm <= hhmm2) import datetime now = datetime.datetime.utcnow() if is_time_between(now, t1, t2): message = send_sms(to_phone, from_phone, message_body)
  22. def is_time_between(now, t1, t2): """ Is a time (from a

    datetime) between two other times (as 24-hour 'HHMM' strings)? """ # Convert HHMM strings into tuples hhmm1 = tuple([int(x) for x in [t1[:2], t1[2:]]]) hhmm2 = tuple([int(x) for x in [t2[:2], t2[2:]]]) hhmm = now.hour, now.minute # Start time before end time; span stays within day if hhmm1 <= hhmm2: return hhmm1 <= hhmm <= hhmm2 # End time before start time; span crosses midnight else: return (hhmm1 <= hhmm <= (23, 59)) or \ ((0, 0) <= hhmm <= hhmm2) import datetime now = datetime.datetime.utcnow() if is_time_between(now, t1, t2): message = send_sms(to_phone, from_phone, message_body)
  23. Anatomy of a Shortcut hhmm1 = tuple([int(x) for x in

    [t1[:2], t1[2:]]]) get the first 2 characters
  24. Anatomy of a Shortcut hhmm1 = tuple([int(x) for x in

    [t1[:2], t1[2:]]]) get the first 2 characters get the last 2 characters
  25. Anatomy of a Shortcut hhmm1 = tuple([int(x) for x in

    [t1[:2], t1[2:]]]) get the first 2 characters get the last 2 characters put them in a list
  26. Anatomy of a Shortcut hhmm1 = tuple([int(x) for x in

    [t1[:2], t1[2:]]]) get the first 2 characters get the last 2 characters list comprehension: turn everything into a list of integers put them in a list
  27. Anatomy of a Shortcut hhmm1 = tuple([int(x) for x in

    [t1[:2], t1[2:]]]) get the first 2 characters get the last 2 characters list comprehension: turn everything into a list of integers turn the list into a tuple put them in a list
  28. def is_day_allowed(now, days): """ Is a day (from a datetime)

    among particular ISO 8601 (Monday = 1) days? """ return not days or now.isoweekday() in days now = datetime.datetime.utcnow() days = [1, 2, 3, 4, 5] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): message = send_sms(to_phone, from_phone, message_body)
  29. def is_day_allowed(now, days): """ Is a day (from a datetime)

    among particular ISO 8601 (Monday = 1) days? """ return not days or now.isoweekday() in days now = datetime.datetime.utcnow() days = [1, 2, 3, 4, 5] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): message = send_sms(to_phone, from_phone, message_body)
  30. def is_day_allowed(now, days): """ Is a day (from a datetime)

    among particular ISO 8601 (Monday = 1) days? """ return not days or now.isoweekday() in days now = datetime.datetime.utcnow() days = [1, 2, 3, 4, 5] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): message = send_sms(to_phone, from_phone, message_body)
  31. def lambda_handler(event, context): """ AWS Lambda handler function; accepts an

    event (dict) and context (LambdaContext) and sends an SMS message to the specified phone number(s) if the time is within the specified window. """ now = datetime.datetime.utcnow() t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days = [int(x) for x in event.get('days', '')] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): to_phone = event['to_phone'] message = send_sms(to_phone, from_phone, event['message']) return {'result': messages.SENT} else: return {'result': messages.NOT_SENT}
  32. def lambda_handler(event, context): """ AWS Lambda handler function; accepts an

    event (dict) and context (LambdaContext) and sends an SMS message to the specified phone number(s) if the time is within the specified window. """ now = datetime.datetime.utcnow() t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days = [int(x) for x in event.get('days', '')] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): to_phone = event['to_phone'] message = send_sms(to_phone, from_phone, event['message']) return {'result': messages.SENT} else: return {'result': messages.NOT_SENT}
  33. def lambda_handler(event, context): """ AWS Lambda handler function; accepts an

    event (dict) and context (LambdaContext) and sends an SMS message to the specified phone number(s) if the time is within the specified window. """ now = datetime.datetime.utcnow() t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days = [int(x) for x in event.get('days', '')] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): to_phone = event['to_phone'] message = send_sms(to_phone, from_phone, event['message']) return {'result': messages.SENT} else: return {'result': messages.NOT_SENT}
  34. def lambda_handler(event, context): """ AWS Lambda handler function; accepts an

    event (dict) and context (LambdaContext) and sends an SMS message to the specified phone number(s) if the time is within the specified window. """ now = datetime.datetime.utcnow() t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days = [int(x) for x in event.get('days', '')] if (is_time_between(now, t1, t2) and is_day_allowed(now, days)): to_phone = event['to_phone'] message = send_sms(to_phone, from_phone, event['message']) return {'result': messages.SENT} else: return {'result': messages.NOT_SENT}
  35. More Shortcuts t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days

    = [int(x) for x in event.get('days', '')] try to get t1 from the event dictionary, fall back to '0000'
  36. More Shortcuts t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days

    = [int(x) for x in event.get('days', '')] try to get t1 from the event dictionary, fall back to '0000' try to get t2 from the event dictionary, fall back to '0000'
  37. More Shortcuts t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days

    = [int(x) for x in event.get('days', '')] try to get t1 from the event dictionary, fall back to '0000' try to get t2 from the event dictionary, fall back to '0000' assign the values into t1 and t2 in a single step
  38. More Shortcuts t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days

    = [int(x) for x in event.get('days', '')] try to get t1 from the event dictionary, fall back to '0000' try to get t2 from the event dictionary, fall back to '0000' try to get days from the event dictionary, fall back to '' assign the values into t1 and t2 in a single step
  39. More Shortcuts t1, t2 = event.get('t1', '0000'), event.get('t2', '0000') days

    = [int(x) for x in event.get('days', '')] try to get t1 from the event dictionary, fall back to '0000' try to get t2 from the event dictionary, fall back to '0000' try to get days from the event dictionary, fall back to '' list comprehension: turn everything into a list of integers assign the values into t1 and t2 in a single step
  40. import logging from config import log_level logger = logging.getLogger() logger.setLevel(log_level)

    def lambda_handler(event, context): logger.info(messages.GOT_EVENT.format(event)) ... if (is_time_between(...) and is_day_allowed(...)): logger.info(messages.IN_WINDOW) message = send_sms(...) logger.debug(message) return {'result': messages.SENT} else: logger.info(messages.NOT_IN_WINDOW) return {'result': messages.NOT_SENT}
  41. import logging from config import log_level logger = logging.getLogger() logger.setLevel(log_level)

    def lambda_handler(event, context): logger.info(messages.GOT_EVENT.format(event)) ... if (is_time_between(...) and is_day_allowed(...)): logger.info(messages.IN_WINDOW) message = send_sms(...) logger.debug(message) return {'result': messages.SENT} else: logger.info(messages.NOT_IN_WINDOW) return {'result': messages.NOT_SENT}
  42. import logging from config import log_level logger = logging.getLogger() logger.setLevel(log_level)

    def lambda_handler(event, context): logger.info(messages.GOT_EVENT.format(event)) ... if (is_time_between(...) and is_day_allowed(...)): logger.info(messages.IN_WINDOW) message = send_sms(...) logger.debug(message) return {'result': messages.SENT} else: logger.info(messages.NOT_IN_WINDOW) return {'result': messages.NOT_SENT}
  43. import arrow def lambda_handler(event, context): ... # Shift to client

    local timezone if specified now = arrow.utcnow() tz = event.get('tz') now = now.to(tz) if tz else now ...
  44. import arrow def lambda_handler(event, context): ... # Shift to client

    local timezone if specified now = arrow.utcnow() tz = event.get('tz') now = now.to(tz) if tz else now ...
  45. import arrow def lambda_handler(event, context): ... # Shift to client

    local timezone if specified now = arrow.utcnow() tz = event.get('tz') now = now.to(tz) if tz else now ...
  46. import arrow def lambda_handler(event, context): ... # Shift to client

    local timezone if specified now = arrow.utcnow() tz = event.get('tz') now = now.to(tz) if tz else now ...
  47. makezip.sh #!/bin/bash PWD=`pwd` TIMESTAMP=`date -j +%Y%m%dT%H%M` ZIPFILE=$PWD/textMeMaybe-$TIMESTAMP.zip zip $ZIPFILE config.py

    messages.py textmemaybe.py SITE_PACKAGES=$VIRTUAL_ENV/lib/python2.7/site-packages/ cd $SITE_PACKAGES zip -gr $ZIPFILE ./
  48. Links • https://www.twilio.com • https://aws.amazon.com/lambda • https://ifttt.com • http://arrow.readthedocs.io •

    http://docs.aws.amazon.com/lambda/latest/dg/lambda-python- how-to-create-deployment-package.html • http://docs.aws.amazon.com/lambda/latest/dg/with-s3-example- deployment-pkg.html#with-s3-example-deployment-pkg-python