added some more steps for the blog post

This commit is contained in:
2017-10-25 17:05:11 -04:00
parent 25b373a818
commit 4262b13969
10 changed files with 140 additions and 269 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.*sw*
.DS_Store
*~
.idea/

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.6.0 virtualenv at ~/.pyenv/versions/babysitter_bot" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>

View File

@@ -1,43 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="18">
<item index="0" class="java.lang.String" itemvalue="Werkzeug" />
<item index="1" class="java.lang.String" itemvalue="wsgiref" />
<item index="2" class="java.lang.String" itemvalue="MarkupSafe" />
<item index="3" class="java.lang.String" itemvalue="requests" />
<item index="4" class="java.lang.String" itemvalue="itsdangerous" />
<item index="5" class="java.lang.String" itemvalue="Jinja2" />
<item index="6" class="java.lang.String" itemvalue="click" />
<item index="7" class="java.lang.String" itemvalue="Flask" />
<item index="8" class="java.lang.String" itemvalue="plaid-python" />
<item index="9" class="java.lang.String" itemvalue="asn1crypto" />
<item index="10" class="java.lang.String" itemvalue="pyOpenSSL" />
<item index="11" class="java.lang.String" itemvalue="cffi" />
<item index="12" class="java.lang.String" itemvalue="cryptography" />
<item index="13" class="java.lang.String" itemvalue="packaging" />
<item index="14" class="java.lang.String" itemvalue="pycparser" />
<item index="15" class="java.lang.String" itemvalue="pyparsing" />
<item index="16" class="java.lang.String" itemvalue="twilio" />
<item index="17" class="java.lang.String" itemvalue="idna" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="plaid.Client" />
</list>
</option>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PreferredVcsStorage">
<preferredVcsName>ApexVCS</preferredVcsName>
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6.0 virtualenv at ~/.pyenv/versions/babysitter_bot" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/babysitter_bot.iml" filepath="$PROJECT_DIR$/.idea/babysitter_bot.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

185
.idea/workspace.xml generated
View File

@@ -1,185 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="d62c162e-b344-4d79-986f-e8c09fbec6ca" name="Default" comment="">
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/requirements.txt" afterPath="$PROJECT_DIR$/requirements.txt" />
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/sitter_bot.py" afterPath="$PROJECT_DIR$/sitter_bot.py" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="TRACKING_ENABLED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileColors">
<fileColor scope="Non-Project Files (Material Default)" color="2E3C43" />
<fileColor scope="Non-Project Files (Material Darker)" color="323232" />
<fileColor scope="Non-Project Files (Material Lighter)" color="eae8e8" />
<fileColor scope="Non-Project Files (Material Palenight)" color="2f2e43" />
</component>
<component name="FileEditorManager">
<leaf>
<file leaf-file-name="sitter_bot.py" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/sitter_bot.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="189">
<caret line="9" column="80" lean-forward="false" selection-start-line="6" selection-start-column="0" selection-end-line="9" selection-end-column="80" />
<folding>
<element signature="e#0#15#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/sitter_bot.py" />
</list>
</option>
</component>
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
<component name="JsGulpfileManager">
<detection-done>true</detection-done>
<sorting>DEFINITION_ORDER</sorting>
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="90" />
<option name="width" value="986" />
<option name="height" value="1920" />
</component>
<component name="ProjectView">
<navigator currentView="ProjectPane" proportions="" version="1">
<flattenPackages />
<showMembers />
<showModules />
<showLibraryContents />
<hideEmptyPackages />
<abbreviatePackageNames />
<autoscrollToSource />
<autoscrollFromSource />
<sortByType />
<manualOrder />
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="babysitter_bot" type="b2602c69:ProjectViewProjectNode" />
<item name="babysitter_bot" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scratches" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$USER_HOME$/.pyenv/versions/babysitter_bot/bin/python3.6" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager" selected="Python.babysitter_bot">
<configuration name="babysitter_bot" type="PythonConfigurationType" factoryName="Python" singleton="true">
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="IS_MODULE_SDK" value="false" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<module name="babysitter_bot" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/sitter_bot.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
</configuration>
</component>
<component name="ShelveChangesManager" show_recycled="false">
<option name="remove_strategy" value="false" />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="d62c162e-b344-4d79-986f-e8c09fbec6ca" name="Default" comment="" />
<created>1508950836856</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1508950836856</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="0" y="0" width="1080" height="1920" extended-state="6" />
<editor active="true" />
<layout>
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Python Console" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.14537036" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
<window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.32958803" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
<window_info id="Data View" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager />
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/sitter_bot.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="189">
<caret line="9" column="80" lean-forward="false" selection-start-line="6" selection-start-column="0" selection-end-line="9" selection-end-column="80" />
<folding>
<element signature="e#0#15#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</component>
</project>

59
basic_sitter_bot.py Normal file
View File

@@ -0,0 +1,59 @@
import os
from typing import Tuple
from flask import Flask, request
from twilio.rest import Client as TwilioClient
from twilio.twiml.messaging_response import MessagingResponse
twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN'))
app = Flask(__name__)
app.config.from_object(__name__)
sitters = {}
@app.route('/bot', methods=['POST'])
def bot():
from_ = request.values.get('From')
body = request.values.get('Body').lower()
response = 'I wasn\'t sure what to do with your input. '
if has_phone_num(body):
try:
sitter_name, sitter_num = add_sitter(body)
except (AssertionError, ValueError):
response = 'Sorry, did you mean to add a sitter? Please try again.'
else:
response = f'Okay, I added {sitter_name.title()} to sitters, with phone # {sitter_num}. '
print(sitters)
resp = MessagingResponse()
resp.message(response)
return str(resp)
def has_phone_num(string):
return len([char for char in string if char.isnumeric()]) == 10
def add_sitter(body: str) -> Tuple[str, str]:
name, *num_parts = body.split(' ')
num_only = ''.join(char
for num in num_parts
for char in num if char.isnumeric())
lowercase_name = name.lower()
sitter = sitters.get(lowercase_name)
assert len(num_only) == 10
sitters[lowercase_name] = f'+1{num_only}'
return name, sitters[lowercase_name]
if __name__ == '__main__':
app.run(debug=True, port=8000)

View File

@@ -0,0 +1,74 @@
import os
import pickle
from typing import Tuple
from flask import Flask, request
from twilio.rest import Client as TwilioClient
from twilio.twiml.messaging_response import MessagingResponse
MY_CELL = os.getenv('MY_CELL')
BOOKER_NUM = os.getenv('MY_TWILIO_NUM')
twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN'))
app = Flask(__name__)
app.config.from_object(__name__)
sitters = {}
if os.path.exists('sitters.p'):
sitters = pickle.load(open('sitters.p', 'rb'))
@app.route('/bot', methods=['POST'])
def bot():
from_ = request.values.get('From')
body = request.values.get('Body').lower()
response = 'I wasn\'t sure what to do with your input. '
if has_phone_num(body):
try:
sitter_name, sitter_num = add_sitter(body)
except (AssertionError, ValueError):
response = 'Sorry, did you mean to add a sitter? Please try again.'
else:
response = f'Okay, I added {sitter_name.title()} to sitters, with phone # {sitter_num}. '
print(sitters)
resp = MessagingResponse()
resp.message(response)
return str(resp)
def has_phone_num(string):
return len([char for char in string if char.isnumeric()]) == 10
def add_sitter(body: str) -> Tuple[str, str]:
name, *num_parts = body.split(' ')
num_only = ''.join(char
for num in num_parts
for char in num if char.isnumeric())
lowercase_name = name.lower()
sitter = sitters.get(lowercase_name)
assert len(num_only) == 10
sitters[lowercase_name] = f'+1{num_only}'
persist_sitters()
return name, sitters[lowercase_name]
def persist_sitters():
pickle.dump(sitters, open('sitters.p', 'wb'))
if __name__ == '__main__':
if sitters:
sitter_list = 'Your sitters are ' + ' and '.join(
f'{sitter_name.title()}' for sitter_name in sitters) + '.'
twilio_client.api.account.messages.create(to=MY_CELL, from_=BOOKER_NUM, body=sitter_list)
app.run(debug=True, port=8000)

View File

@@ -60,7 +60,7 @@ def bot() -> str:
if not sitters:
response = f'You don\'t have any sitters yet. {help_add}.'
else:
sitter_list = 'Your sitters are ' + ', '.join(
sitter_list = 'Your sitters are ' + ' and '.join(
f'{sitter_name.title()}' for sitter_name in sitters) + '.'
response = f'{sitter_list} {help_text}'
@@ -100,7 +100,7 @@ def bot() -> str:
def has_phone_num(string):
return len([char for char in string if char.isnumeric()]) >= 10
return len([char for char in string if char.isnumeric()]) == 10
def syndicate_and_book(session_start: datetime.datetime, session_end: datetime.datetime) -> Optional[str]:
@@ -111,13 +111,13 @@ def syndicate_and_book(session_start: datetime.datetime, session_end: datetime.d
pass
def book_sitter(in_message: str) -> Optional[str]:
session_start, session_end = parse_sitter_request(in_message)
def book_sitter(body: str) -> Optional[str]:
session_start, session_end = parse_sitter_request(body)
syndicate_and_book(session_start, session_end)
def add_sitter(in_message: str) -> Tuple[str, str]:
name, *num_parts = in_message.split(' ')
def add_sitter(body: str) -> Tuple[str, str]:
name, *num_parts = body.split(' ')
num_only = ''.join(char
for num in num_parts