diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3faff97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +reader/ngrok\.exe diff --git a/f-ask/.gitignore b/f-ask/.gitignore deleted file mode 100644 index b601c41..0000000 --- a/f-ask/.gitignore +++ /dev/null @@ -1,102 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - -# Vagrant -.vagrant - -# Misc -.DS_Store -temp -.idea/ - -# Project -backups -settings.cfg -fabfile/settings.py diff --git a/f-ask/.vscode/launch.json b/f-ask/.vscode/launch.json deleted file mode 100644 index e69de29..0000000 diff --git a/f-ask/LICENSE.txt b/f-ask/LICENSE.txt deleted file mode 100644 index 264ebb3..0000000 --- a/f-ask/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016 John Wheeler - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/f-ask/MANIFEST.in b/f-ask/MANIFEST.in deleted file mode 100644 index 557c423..0000000 --- a/f-ask/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include *.rst *.txt LICENSE tox.ini .travis.yml docs/Makefile .coveragerc conftest.py -recursive-include tests *.py -recursive-include docs *.rst -recursive-include docs *.py -prune docs/_build - diff --git a/f-ask/README.md b/f-ask/README.md deleted file mode 100644 index 64dd38e..0000000 --- a/f-ask/README.md +++ /dev/null @@ -1 +0,0 @@ -GST diff --git a/f-ask/README.rst b/f-ask/README.rst deleted file mode 100644 index 63b4d34..0000000 --- a/f-ask/README.rst +++ /dev/null @@ -1,142 +0,0 @@ - -.. image:: http://flask-ask.readthedocs.io/en/latest/_images/logo-full.png - -=================================== -Program the Amazon Echo with Python -=================================== - -Flask-Ask is a `Flask extension `_ that makes building Alexa skills for the Amazon Echo easier and much more fun. - -* `Flask-Ask quickstart on Amazon's Developer Blog `_. -* `Level Up with our Alexa Skills Kit Video Tutorial `_ -* `Chat on Gitter.im `_ - -The Basics -=============== - -A Flask-Ask application looks like this: - -.. code-block:: python - - from flask import Flask - from flask_ask import Ask, statement - - app = Flask(__name__) - ask = Ask(app, '/') - - @ask.intent('HelloIntent') - def hello(firstname): - speech_text = "Hello %s" % firstname - return statement(speech_text).simple_card('Hello', speech_text) - - if __name__ == '__main__': - app.run() - -In the code above: - -#. The ``Ask`` object is created by passing in the Flask application and a route to forward Alexa requests to. -#. The ``intent`` decorator maps ``HelloIntent`` to a view function ``hello``. -#. The intent's ``firstname`` slot is implicitly mapped to ``hello``'s ``firstname`` parameter. -#. Lastly, a builder constructs a spoken response and displays a contextual card in the Alexa smartphone/tablet app. - -More code examples are in the `samples `_ directory. - -Jinja Templates ---------------- - -Since Alexa responses are usually short phrases, you might find it convenient to put them in the same file. -Flask-Ask has a `Jinja template loader `_ that loads -multiple templates from a single YAML file. For example, here's a template that supports the minimal voice interface -above: - -.. code-block:: yaml - - hello: Hello, {{ firstname }} - -Templates are stored in a file called `templates.yaml` located in the application root. Checkout the `Tidepooler example `_ to see why it makes sense to extract speech out of the code and into templates as the number of spoken phrases grow. - -Features -=============== - -Flask-Ask handles the boilerplate, so you can focus on writing clean code. Flask-Ask: - -* Has decorators to map Alexa requests and intent slots to view functions -* Helps construct ask and tell responses, reprompts and cards -* Makes session management easy -* Allows for the separation of code and speech through Jinja templates -* Verifies Alexa request signatures - -Installation -=============== - -To install Flask-Ask:: - - pip install flask-ask - -Documentation -=============== - -These resources will get you up and running quickly: - -* `5-minute quickstart `_ -* `Full online documentation `_ - -Fantastic 3-part tutorial series by Harrison Kinsley - -* `Intro and Skill Logic - Alexa Skills w/ Python and Flask-Ask Part 1 `_ -* `Headlines Function - Alexa Skills w/ Python and Flask-Ask Part 2 `_ -* `Testing our Skill - Alexa Skills w/ Python and Flask-Ask Part 3 `_ - -Deployment -=============== - -You can deploy using any WSGI compliant framework (uWSGI, Gunicorn). If you haven't deployed a Flask app to production, `checkout flask-live-starter `_. - -To deploy on AWS Lambda, you have two options. Use `Zappa `_ to automate the deployment of an AWS Lambda function and an AWS API Gateway to provide a public facing endpoint for your Lambda function. This `blog post `_ shows how to deploy Flask-Ask with Zappa from scratch. Note: When deploying to AWS Lambda with Zappa, make sure you point the Alexa skill to the HTTPS API gateway that Zappa creates, not the Lambda function's ARN. - -Alternatively, you can use AWS Lambda directly without the need for an AWS API Gateway endpoint. In this case you will need to `deploy `_ your Lambda function yourself and use `virtualenv `_ to create a deployment package that contains your Flask-Ask application along with its dependencies, which can be uploaded to Lambda. If your Lambda handler is configured as `lambda_function.lambda_handler`, then you would save the full application example above in a file called `lambda_function.py` and add the following two lines to it: - -.. code-block:: python - - def lambda_handler(event, _context): - return ask.run_aws_lambda(event) - - -Development -=============== - -If you'd like to work from the Flask-Ask source, clone the project and run:: - - pip install -r requirements-dev.txt - -This will install all base requirements from `requirements.txt` as well as requirements needed for running tests from the `tests` directory. - -Tests can be run with:: - - python setup.py test - -Or:: - - python -m unittest - -To install from your local clone or fork of the project, run:: - - python setup.py install - -Related projects -=============== - -`cookiecutter-flask-ask `_ is a Cookiecutter to easily bootstrap a Flask-Ask project, including documentation, speech assets and basic built-in intents. - -Have a Google Home? Checkout `Flask-Assistant `_ (early alpha) - - -Thank You -=============== - -Thanks for checking this library out! I hope you find it useful. - -Of course, there's always room for improvement. -Feel free to `open an issue `_ so we can make Flask-Ask better. - -Special thanks to `@kennethreitz `_ for his `sense `_ of `style `_, and of course, `@mitsuhiko `_ for `Flask `_ diff --git a/f-ask/docs/Makefile b/f-ask/docs/Makefile deleted file mode 100644 index 8061fad..0000000 --- a/f-ask/docs/Makefile +++ /dev/null @@ -1,230 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# User-friendly check for sphinx-build -ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) - $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/) -endif - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-Ask.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-Ask.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-Ask" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-Ask" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: epub3 -epub3: - $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 - @echo - @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -.PHONY: dummy -dummy: - $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy - @echo - @echo "Build finished. Dummy builder generates no files." diff --git a/f-ask/docs/_static/logo-full.png b/f-ask/docs/_static/logo-full.png deleted file mode 100644 index 9762abb..0000000 Binary files a/f-ask/docs/_static/logo-full.png and /dev/null differ diff --git a/f-ask/docs/_static/logo-sm.png b/f-ask/docs/_static/logo-sm.png deleted file mode 100644 index b4754fd..0000000 Binary files a/f-ask/docs/_static/logo-sm.png and /dev/null differ diff --git a/f-ask/docs/_templates/links.html b/f-ask/docs/_templates/links.html deleted file mode 100644 index b236224..0000000 --- a/f-ask/docs/_templates/links.html +++ /dev/null @@ -1,31 +0,0 @@ -
- -

Resources

- - - - -
- -

Project Links

- - - -
diff --git a/f-ask/docs/_templates/sidebarlogo.html b/f-ask/docs/_templates/sidebarlogo.html deleted file mode 100644 index f5e5494..0000000 --- a/f-ask/docs/_templates/sidebarlogo.html +++ /dev/null @@ -1,9 +0,0 @@ -

- - -Alexa Skills Kit Development for Amazon Echo Devices with Python - diff --git a/f-ask/docs/_templates/stayinformed.html b/f-ask/docs/_templates/stayinformed.html deleted file mode 100644 index 0222271..0000000 --- a/f-ask/docs/_templates/stayinformed.html +++ /dev/null @@ -1,20 +0,0 @@ - -

Stay Informed

- -Star - -
- -

- Receive updates on new releases and upcoming projects. -

- -

- Follow @johnwheeler -

- -

- -

- -
diff --git a/f-ask/docs/_themes/LICENSE b/f-ask/docs/_themes/LICENSE deleted file mode 100644 index 8daab7e..0000000 --- a/f-ask/docs/_themes/LICENSE +++ /dev/null @@ -1,37 +0,0 @@ -Copyright (c) 2010 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms of the theme, with or -without modification, are permitted provided that the following conditions -are met: - -* Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - -* The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -We kindly ask you to only use these themes in an unmodified manner just -for Flask and Flask-related products, not for unrelated projects. If you -like the visual style and want to use it for your own projects, please -consider making some larger changes to the themes (such as changing -font faces, sizes, colors or margins). - -THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/f-ask/docs/_themes/README b/f-ask/docs/_themes/README deleted file mode 100644 index b3292bd..0000000 --- a/f-ask/docs/_themes/README +++ /dev/null @@ -1,31 +0,0 @@ -Flask Sphinx Styles -=================== - -This repository contains sphinx styles for Flask and Flask related -projects. To use this style in your Sphinx documentation, follow -this guide: - -1. put this folder as _themes into your docs folder. Alternatively - you can also use git submodules to check out the contents there. -2. add this to your conf.py: - - sys.path.append(os.path.abspath('_themes')) - html_theme_path = ['_themes'] - html_theme = 'flask' - -The following themes exist: - -- 'flask' - the standard flask documentation theme for large - projects -- 'flask_small' - small one-page theme. Intended to be used by - very small addon libraries for flask. - -The following options exist for the flask_small theme: - - [options] - index_logo = '' filename of a picture in _static - to be used as replacement for the - h1 in the index.rst file. - index_logo_height = 120px height of the index logo - github_fork = '' repository name on github for the - "fork me" badge diff --git a/f-ask/docs/_themes/flask/layout.html b/f-ask/docs/_themes/flask/layout.html deleted file mode 100644 index a76d787..0000000 --- a/f-ask/docs/_themes/flask/layout.html +++ /dev/null @@ -1,36 +0,0 @@ -{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} - - - - - - - - -{% if theme_touch_icon %} - {% endif %} {% endblock %} {%- block relbar2 %} {% if theme_github_fork %} - - Fork me on GitHub - -{% endif %} {% endblock %} {%- block footer %} - -{%- endblock %} diff --git a/f-ask/docs/_themes/flask/relations.html b/f-ask/docs/_themes/flask/relations.html deleted file mode 100644 index 3bbcde8..0000000 --- a/f-ask/docs/_themes/flask/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -

Related Topics

- diff --git a/f-ask/docs/_themes/flask/static/flasky.css_t b/f-ask/docs/_themes/flask/static/flasky.css_t deleted file mode 100644 index d6f6d1b..0000000 --- a/f-ask/docs/_themes/flask/static/flasky.css_t +++ /dev/null @@ -1,581 +0,0 @@ -/* - * flasky.css_t - * ~~~~~~~~~~~~ - * - * :copyright: Copyright 2010 by Armin Ronacher. - * :license: Flask Design License, see LICENSE for details. - */ - -{% set page_width = '940px' %} -{% set sidebar_width = '220px' %} - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: 'Georgia', serif; - font-size: 17px; - background-color: white; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - width: {{ page_width }}; - margin: 30px auto 0 auto; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ sidebar_width }}; -} - -div.sphinxsidebar { - width: {{ sidebar_width }}; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 0 30px; -} - -img.floatingflask { - padding: 0 0 10px 10px; - float: right; -} - -div.footer { - width: {{ page_width }}; - margin: 20px auto 30px auto; - font-size: 14px; - color: #888; - text-align: right; -} - -div.footer a { - color: #888; -} - -div.related { - display: none; -} - -div.sphinxsidebar a { - color: #444; - text-decoration: none; - border-bottom: 1px dotted #999; -} - -div.sphinxsidebar a:hover { - border-bottom: 1px solid #999; -} - -div.sphinxsidebar { - font-size: 14px; - line-height: 1.5; -} - -div.sphinxsidebarwrapper { - padding: 18px 10px; -} - -div.sphinxsidebarwrapper p.logo { - padding: 0 0 6px 0; - margin: 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: 'Garamond', 'Georgia', serif; - color: #444; - font-size: 24px; - font-weight: normal; - margin: 0 0 5px 0; - padding: 0; -} - -div.sphinxsidebar h4 { - font-size: 20px; -} - -div.sphinxsidebar h3 a { - color: #444; -} - -div.sphinxsidebar p.logo a, -div.sphinxsidebar h3 a, -div.sphinxsidebar p.logo a:hover, -div.sphinxsidebar h3 a:hover { - border: none; -} - -div.sphinxsidebar p { - color: #555; - margin: 10px 0; -} - -div.sphinxsidebar ul { - margin: 10px 0; - padding: 0; - color: #000; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: 'Georgia', serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #004B6B; - text-decoration: underline; -} - -a:hover { - color: #6D4100; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - margin: 30px 0px 10px 0px; - padding: 0; -} - -{% if theme_index_logo %} -div.indexwrapper h1 { - text-indent: -999999px; - background: url({{ theme_index_logo }}) no-repeat center center; - height: {{ theme_index_logo_height }}; -} -{% endif %} -div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } -div.body h2 { font-size: 180%; } -div.body h3 { font-size: 150%; } -div.body h4 { font-size: 130%; } -div.body h5 { font-size: 100%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #ddd; - padding: 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - color: #444; - background: #eaeaea; -} - -div.body p, div.body dd, div.body li { - line-height: 1.4em; -} - -div.admonition { - background: #fafafa; - margin: 20px -30px; - padding: 10px 30px; - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} - -div.admonition tt.xref, div.admonition a tt { - border-bottom: 1px solid #fafafa; -} - -dd div.admonition { - margin-left: -60px; - padding-left: 60px; -} - -div.admonition p.admonition-title { - font-family: 'Garamond', 'Georgia', serif; - font-weight: normal; - font-size: 24px; - margin: 0 0 10px 0; - padding: 0; - line-height: 1; -} - -div.admonition p.last { - margin-bottom: 0; -} - -div.highlight { - background-color: white; -} - -dt:target, .highlight { - background: #FAF3E8; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; - padding: 0 7px 7px 7px; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre, tt { - font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; - font-size: 0.9em; -} - -img.screenshot { -} - -tt.descname, tt.descclassname { - font-size: 0.95em; -} - -tt.descname { - padding-right: 0.08em; -} - -img.screenshot { - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils { - border: 1px solid #888; - -moz-box-shadow: 2px 2px 4px #eee; - -webkit-box-shadow: 2px 2px 4px #eee; - box-shadow: 2px 2px 4px #eee; -} - -table.docutils td, table.docutils th { - border: 1px solid #888; - padding: 0.25em 0.7em; -} - -table.field-list, table.footnote { - border: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} - -table.footnote { - margin: 15px 0; - width: 100%; - border: 1px solid #eee; - background: #fdfdfd; - font-size: 0.9em; -} - -table.footnote + table.footnote { - margin-top: -15px; - border-top: none; -} - -table.field-list th { - padding: 0 0.8em 0 0; -} - -table.field-list td { - padding: 0; -} - -table.footnote td.label { - width: 0px; - padding: 0.3em 0 0.3em 0.5em; -} - -table.footnote td { - padding: 0.3em 0.5em; -} - -dl { - margin: 0; - padding: 0; -} - -dl dd { - margin-left: 30px; -} - -blockquote { - margin: 0 0 0 30px; - padding: 0; -} - -ul, ol { - margin: 10px 0 10px 30px; - padding: 0; -} - -pre { - background: #eee; - margin: 12px 0px; - padding: 11px 14px; - line-height: 1.3em; -} - -dl pre, blockquote pre, li pre { - margin-left: -60px; - padding-left: 60px; -} - -dl dl pre { - margin-left: -90px; - padding-left: 90px; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ -} - -tt.xref, a tt { - background-color: #FBFBFB; - border-bottom: 1px solid white; -} - -a.reference { - text-decoration: none; - border-bottom: 1px dotted #004B6B; -} - -a.reference:hover { - border-bottom: 1px solid #6D4100; -} - -a.footnote-reference { - text-decoration: none; - font-size: 0.7em; - vertical-align: top; - border-bottom: 1px dotted #004B6B; -} - -a.footnote-reference:hover { - border-bottom: 1px solid #6D4100; -} - -a:hover tt { - background: #EEE; -} - -small { - font-size: 0.9em; -} - - -@media screen and (max-width: 870px) { - - div.sphinxsidebar { - display: none; - } - - div.document { - width: 100%; - - } - - div.documentwrapper { - margin-left: 0; - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - } - - div.bodywrapper { - margin-top: 0; - margin-right: 0; - margin-bottom: 0; - margin-left: 0; - } - - ul { - margin-left: 0; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .bodywrapper { - margin: 0; - } - - .footer { - width: auto; - } - - .github { - display: none; - } - - - -} - - - -@media screen and (max-width: 875px) { - - body { - margin: 0; - padding: 20px 30px; - } - - div.documentwrapper { - float: none; - background: white; - } - - div.sphinxsidebar { - display: block; - float: none; - width: 102.5%; - margin: 50px -30px -20px -30px; - padding: 10px 20px; - background: #333; - color: white; - } - - div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, - div.sphinxsidebar h3 a { - color: white; - } - - div.sphinxsidebar a { - color: #aaa; - } - - div.sphinxsidebar p.logo { - display: none; - } - - div.document { - width: 100%; - margin: 0; - } - - div.related { - display: block; - margin: 0; - padding: 10px 0 20px 0; - } - - div.related ul, - div.related ul li { - margin: 0; - padding: 0; - } - - div.footer { - display: none; - } - - div.bodywrapper { - margin: 0; - } - - div.body { - min-height: 0; - padding: 0; - } - - .rtd_doc_footer { - display: none; - } - - .document { - width: auto; - } - - .footer { - width: auto; - } - - .footer { - width: auto; - } - - .github { - display: none; - } -} - - -/* scrollbars */ - -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-button:start:decrement, -::-webkit-scrollbar-button:end:increment { - display: block; - height: 10px; -} - -::-webkit-scrollbar-button:vertical:increment { - background-color: #fff; -} - -::-webkit-scrollbar-track-piece { - background-color: #eee; - -webkit-border-radius: 3px; -} - -::-webkit-scrollbar-thumb:vertical { - height: 50px; - background-color: #ccc; - -webkit-border-radius: 3px; -} - -::-webkit-scrollbar-thumb:horizontal { - width: 50px; - background-color: #ccc; - -webkit-border-radius: 3px; -} - -/* misc. */ - -.revsys-inline { - display: none!important; -} diff --git a/f-ask/docs/_themes/flask/theme.conf b/f-ask/docs/_themes/flask/theme.conf deleted file mode 100644 index ce3a5a8..0000000 --- a/f-ask/docs/_themes/flask/theme.conf +++ /dev/null @@ -1,10 +0,0 @@ -[theme] -inherit = basic -stylesheet = flasky.css -pygments_style = flask_theme_support.FlaskyStyle - -[options] -index_logo = '' -index_logo_height = 120px -touch_icon = -github_fork = '' diff --git a/f-ask/docs/_themes/flask_theme_support.py b/f-ask/docs/_themes/flask_theme_support.py deleted file mode 100644 index 33f4744..0000000 --- a/f-ask/docs/_themes/flask_theme_support.py +++ /dev/null @@ -1,86 +0,0 @@ -# flasky extensions. flasky pygments style based on tango style -from pygments.style import Style -from pygments.token import Keyword, Name, Comment, String, Error, \ - Number, Operator, Generic, Whitespace, Punctuation, Other, Literal - - -class FlaskyStyle(Style): - background_color = "#f8f8f8" - default_style = "" - - styles = { - # No corresponding class for the following: - #Text: "", # class: '' - Whitespace: "underline #f8f8f8", # class: 'w' - Error: "#a40000 border:#ef2929", # class: 'err' - Other: "#000000", # class 'x' - - Comment: "italic #8f5902", # class: 'c' - Comment.Preproc: "noitalic", # class: 'cp' - - Keyword: "bold #004461", # class: 'k' - Keyword.Constant: "bold #004461", # class: 'kc' - Keyword.Declaration: "bold #004461", # class: 'kd' - Keyword.Namespace: "bold #004461", # class: 'kn' - Keyword.Pseudo: "bold #004461", # class: 'kp' - Keyword.Reserved: "bold #004461", # class: 'kr' - Keyword.Type: "bold #004461", # class: 'kt' - - Operator: "#582800", # class: 'o' - Operator.Word: "bold #004461", # class: 'ow' - like keywords - - Punctuation: "bold #000000", # class: 'p' - - # because special names such as Name.Class, Name.Function, etc. - # are not recognized as such later in the parsing, we choose them - # to look the same as ordinary variables. - Name: "#000000", # class: 'n' - Name.Attribute: "#c4a000", # class: 'na' - to be revised - Name.Builtin: "#004461", # class: 'nb' - Name.Builtin.Pseudo: "#3465a4", # class: 'bp' - Name.Class: "#000000", # class: 'nc' - to be revised - Name.Constant: "#000000", # class: 'no' - to be revised - Name.Decorator: "#888", # class: 'nd' - to be revised - Name.Entity: "#ce5c00", # class: 'ni' - Name.Exception: "bold #cc0000", # class: 'ne' - Name.Function: "#000000", # class: 'nf' - Name.Property: "#000000", # class: 'py' - Name.Label: "#f57900", # class: 'nl' - Name.Namespace: "#000000", # class: 'nn' - to be revised - Name.Other: "#000000", # class: 'nx' - Name.Tag: "bold #004461", # class: 'nt' - like a keyword - Name.Variable: "#000000", # class: 'nv' - to be revised - Name.Variable.Class: "#000000", # class: 'vc' - to be revised - Name.Variable.Global: "#000000", # class: 'vg' - to be revised - Name.Variable.Instance: "#000000", # class: 'vi' - to be revised - - Number: "#990000", # class: 'm' - - Literal: "#000000", # class: 'l' - Literal.Date: "#000000", # class: 'ld' - - String: "#4e9a06", # class: 's' - String.Backtick: "#4e9a06", # class: 'sb' - String.Char: "#4e9a06", # class: 'sc' - String.Doc: "italic #8f5902", # class: 'sd' - like a comment - String.Double: "#4e9a06", # class: 's2' - String.Escape: "#4e9a06", # class: 'se' - String.Heredoc: "#4e9a06", # class: 'sh' - String.Interpol: "#4e9a06", # class: 'si' - String.Other: "#4e9a06", # class: 'sx' - String.Regex: "#4e9a06", # class: 'sr' - String.Single: "#4e9a06", # class: 's1' - String.Symbol: "#4e9a06", # class: 'ss' - - Generic: "#000000", # class: 'g' - Generic.Deleted: "#a40000", # class: 'gd' - Generic.Emph: "italic #000000", # class: 'ge' - Generic.Error: "#ef2929", # class: 'gr' - Generic.Heading: "bold #000080", # class: 'gh' - Generic.Inserted: "#00A000", # class: 'gi' - Generic.Output: "#888", # class: 'go' - Generic.Prompt: "#745334", # class: 'gp' - Generic.Strong: "bold #000000", # class: 'gs' - Generic.Subheading: "bold #800080", # class: 'gu' - Generic.Traceback: "bold #a40000", # class: 'gt' - } diff --git a/f-ask/docs/conf.py b/f-ask/docs/conf.py deleted file mode 100644 index ffc8f50..0000000 --- a/f-ask/docs/conf.py +++ /dev/null @@ -1,204 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Flask documentation build configuration file, created by -# sphinx-quickstart on Tue Apr 6 15:24:58 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. -from __future__ import print_function -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath('_themes')) -sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'flaskdocext' -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Flask-Ask' -copyright = u'2016, John Wheeler' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'flask' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = { - 'github_fork': 'johnwheeler/flask-ask' -} - -html_sidebars = { - 'index': ['globaltoc.html', 'links.html', 'stayinformed.html'], - '**': ['sidebarlogo.html', 'globaltoc.html', 'links.html', 'stayinformed.html'] -} - -# Add any paths that contain custom themes here, relative to this directory. -html_theme_path = ['_themes'] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. Do not set, template magic! -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = "flask-favicon.ico" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = { -# 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], -# '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', -# 'sourcelink.html', 'searchbox.html'] -# } - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -html_use_modindex = False - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -html_show_sphinx = False - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -#epub_title = '' -#epub_author = '' -#epub_publisher = '' -#epub_copyright = '' - -# The language of the text. It defaults to the language option -# or en if the language is not set. -#epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -#epub_identifier = '' - -# A unique identification for the text. -#epub_uid = '' - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_pre_files = [] - -# HTML files shat should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -#epub_post_files = [] - -# A list of files that should not be packed into the epub file. -#epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -#epub_tocdepth = 3 diff --git a/f-ask/docs/configuration.rst b/f-ask/docs/configuration.rst deleted file mode 100644 index 4643ec3..0000000 --- a/f-ask/docs/configuration.rst +++ /dev/null @@ -1,33 +0,0 @@ -Configuration -============= - -Configuration -------------- - -Flask-Ask exposes the following configuration variables: - -============================ ============================================================================================ -`ASK_APPLICATION_ID` Turn on application ID verification by setting this variable to an application ID or a - list of allowed application IDs. By default, application ID verification is disabled and a - warning is logged. This variable should be set in production to ensure - requests are being sent by the applications you specify. **Default:** ``None`` -`ASK_VERIFY_REQUESTS` Enables or disables - `Alexa request verification `_, - which ensures requests sent to your skill are - from Amazon's Alexa service. This setting should not be disabled in production. It is - useful for mocking JSON requests in automated tests. **Default:** ``True`` -`ASK_VERIFY_TIMESTAMP_DEBUG` Turn on request timestamp verification while debugging by setting this to ``True``. - Timestamp verification helps mitigate against - `replay attacks `_. It - relies on the system clock being synchronized with an NTP server. This setting should not - be enabled in production. **Default:** ``False`` -============================ ============================================================================================ - -Logging -------- - -To see the JSON request / response structures pretty printed in the logs, turn on ``DEBUG``-level logging:: - - import logging - - logging.getLogger('flask_ask').setLevel(logging.DEBUG) diff --git a/f-ask/docs/contents.rst.inc b/f-ask/docs/contents.rst.inc deleted file mode 100644 index b9c2ee0..0000000 --- a/f-ask/docs/contents.rst.inc +++ /dev/null @@ -1,11 +0,0 @@ -Table Of Contents ------------------ - -.. toctree:: - :maxdepth: 2 - - getting_started - requests - responses - configuration - user_contributions diff --git a/f-ask/docs/flaskdocext.py b/f-ask/docs/flaskdocext.py deleted file mode 100644 index db4cfd2..0000000 --- a/f-ask/docs/flaskdocext.py +++ /dev/null @@ -1,16 +0,0 @@ -import re -import inspect - - -_internal_mark_re = re.compile(r'^\s*:internal:\s*$(?m)') - - -def skip_member(app, what, name, obj, skip, options): - docstring = inspect.getdoc(obj) - if skip: - return True - return _internal_mark_re.search(docstring or '') is not None - - -def setup(app): - app.connect('autodoc-skip-member', skip_member) diff --git a/f-ask/docs/getting_started.rst b/f-ask/docs/getting_started.rst deleted file mode 100644 index 889350a..0000000 --- a/f-ask/docs/getting_started.rst +++ /dev/null @@ -1,50 +0,0 @@ -Getting Started -=============== - -Installation ------------- -To install Flask-Ask:: - - pip install flask-ask - - -A Minimal Voice User Interface ------------------------------- -A Flask-Ask application looks like this: - -.. code-block:: python - - from flask import Flask, render_template - from flask_ask import Ask, statement - - app = Flask(__name__) - ask = Ask(app, '/') - - @ask.intent('HelloIntent') - def hello(firstname): - text = render_template('hello', firstname=firstname) - return statement(text).simple_card('Hello', text) - - if __name__ == '__main__': - app.run(debug=True) - -In the code above: - -#. The ``Ask`` object is created by passing in the Flask application and a route to forward Alexa requests to. -#. The ``intent`` decorator maps ``HelloIntent`` to a view function ``hello``. -#. The intent's ``firstname`` slot is implicitly mapped to ``hello``'s ``firstname`` parameter. -#. Jinja templates are supported. Internally, templates are loaded from a YAML file (discussed further below). -#. Lastly, a builder constructs a spoken response and displays a contextual card in the Alexa smartphone/tablet app. - -Since Alexa responses are usually short phrases, it's convenient to put them in the same file. -Flask-Ask has a `Jinja template loader `_ that loads -multiple templates from a single YAML file. For example, here's a template that supports the minimal voice interface -above.Templates are stored in a file called `templates.yaml` located in the application root: - -.. code-block:: yaml - - hello: Hello, {{ firstname }} - -For more information about how the Alexa Skills Kit works, see `Understanding Custom Skills `_ in the Alexa Skills Kit documentation. - -Additionally, more code and template examples are in the `samples `_ directory. diff --git a/f-ask/docs/index.rst b/f-ask/docs/index.rst deleted file mode 100644 index 09ca71c..0000000 --- a/f-ask/docs/index.rst +++ /dev/null @@ -1,45 +0,0 @@ -:orphan: - -.. image:: _static/logo-full.png - :alt: Flask-Ask: Alexa Skills Kit Development for Amazon Echo Devices with Python - - -😎 `Lighten your cognitive load. Level up with the Alexa Skills Kit Video Tutorial `_. - - -.. raw:: html - -

- Star -

- - -Welcome to Flask-Ask -==================== - -Building high-quality Alexa skills for Amazon Echo Devices takes time. Flask-Ask makes it easier and much more fun. -Use Flask-Ask with `ngrok `_ to eliminate the deploy-to-test step and get work done faster. - -Flask-Ask: - -* Has decorators to map Alexa requests and intent slots to view functions -* Helps construct ask and tell responses, reprompts and cards -* Makes session management easy -* Allows for the separation of code and speech through Jinja templates -* Verifies Alexa request signatures - -.. raw:: html - -
- - -Follow along with this quickstart on `Amazon -`_. - -.. include:: contents.rst.inc - -.. raw:: html - -
- - diff --git a/f-ask/docs/make.bat b/f-ask/docs/make.bat deleted file mode 100644 index 8fae2bf..0000000 --- a/f-ask/docs/make.bat +++ /dev/null @@ -1,281 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-Ask.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-Ask.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) - -:end diff --git a/f-ask/docs/requests.rst b/f-ask/docs/requests.rst deleted file mode 100644 index 56ba348..0000000 --- a/f-ask/docs/requests.rst +++ /dev/null @@ -1,265 +0,0 @@ -Handling Requests -================= - -With the Alexa Skills Kit, spoken phrases are mapped to actions executed on a server. Alexa converts -speech into JSON and delivers the JSON to your application. -For example, the phrase: - - "Alexa, Tell HelloApp to say hi to John" - -produces JSON like the following: - -.. code-block:: javascript - - "request": { - "intent": { - "name": "HelloIntent", - "slots": { - "firstname": { - "name": "firstname", - "value": "John" - } - } - } - ... - } - -Parameters called 'slots' are defined and parsed out of speech at runtime. -For example, the spoken word 'John' above is parsed into the slot named ``firstname`` with the ``AMAZON.US_FIRST_NAME`` -data type. - -For detailed information, see -`Handling Requests Sent by Alexa `_ -on the Amazon developer website. - -This section shows how to process Alexa requests with Flask-Ask. It contains the following subsections: - -.. contents:: - :local: - :backlinks: none - -Mapping Alexa Requests to View Functions ----------------------------------------- - -📼 Here is a video demo on `Handling Requests with Flask-Ask video `_. - -Flask-Ask has decorators to map Alexa requests to view functions. - -The ``launch`` decorator handles launch requests:: - - @ask.launch - def launched(): - return question('Welcome to Foo') - -The ``intent`` decorator handles intent requests:: - - @ask.intent('HelloWorldIntent') - def hello(): - return statement('Hello, world') - -The ``session_ended`` decorator is for the session ended request:: - - @ask.session_ended - def session_ended(): - return "{}", 200 - -Launch and intent requests can both start sessions. Avoid duplicate code with the ``on_session_started`` callback:: - - @ask.on_session_started - def new_session(): - log.info('new session started') - - -Mapping Intent Slots to View Function Parameters ------------------------------------------------- - -📼 Here is a video demo on `Intent Slots with Flask-Ask video `_. - - -When Parameter and Slot Names Differ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Tell Flask-Ask when slot and view function parameter names differ with ``mapping``:: - - @ask.intent('WeatherIntent', mapping={'city': 'City'}) - def weather(city): - return statement('I predict great weather for {}'.format(city)) - -Above, the parameter ``city`` is mapped to the slot ``City``. - - -Assigning Default Values when Slots are Empty -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Parameters are assigned a value of ``None`` if the Alexa service: - -* Does not return a corresponding slot in the request -* Includes a corresponding slot without its ``value`` attribute -* Includes a corresponding slot with an empty ``value`` attribute (e.g. ``""``) - -Use the ``default`` parameter for default values instead of ``None``. The default itself should be a -literal or a callable that resolves to a value. The next example shows the literal ``'World'``:: - - @ask.intent('HelloIntent', default={'name': 'World'}) - def hello(name): - return statement('Hello, {}'.format(name)) - - -Converting Slots Values to Python Data Types -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -📼 Here is a video demo on `Slot Conversions with Flask-Ask video `_. - -When slot values are available, they're always assigned to parameters as strings. Convert to other Python -data types with ``convert``. ``convert`` is a ``dict`` that maps parameter names to callables:: - - @ask.intent('AddIntent', convert={'x': int, 'y': int}) - def add(x, y): - z = x + y - return statement('{} plus {} equals {}'.format(x, y, z)) - - -Above, ``x`` and ``y`` will both be passed to ``int()`` and thus converted to ``int`` instances. - -Flask-Ask provides convenient API constants for Amazon ``AMAZON.DATE``, ``AMAZON.TIME``, and ``AMAZON.DURATION`` -types exist since those are harder to build callables against. Instead of trying to define functions that work with -inputs like those in Amazon's -`documentation `_, -just pass the strings in the second column below: - -📼 Here is a video demo on `Slot Conversion Helpers with Flask-Ask video `_. - -=================== =============== ====================== -Amazon Data Type String Python Data Type -=================== =============== ====================== -``AMAZON.DATE`` ``'date'`` ``datetime.date`` -``AMAZON.TIME`` ``'time'`` ``datetime.time`` -``AMAZON.DURATION`` ``'timedelta'`` ``datetime.timedelta`` -=================== =============== ====================== - -**Examples** - -.. code-block:: python - - convert={'the_date': 'date'} - -converts ``'2015-11-24'``, ``'2015-W48-WE'``, or ``'201X'`` into a ``datetime.date`` - -.. code-block:: python - - convert={'appointment_time': 'time'} - -converts ``'06:00'``, ``'14:15'``, or ``'23:59'`` into a ``datetime.time``. - -.. code-block:: python - - convert={'ago': 'timedelta'} - -converts ``'PT10M'``, ``'PT45S'``, or ``'P2YT3H10M'`` into a ``datetime.timedelta``. - - -Handling Conversion Errors -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Sometimes Alexa doesn't understand what's said, and slots come in with question marks: - -.. code-block:: javascript - - "slots": { - "age": { - "name": "age", - "value": "?" - } - } - -Recover gracefully with the ``convert_errors`` context local. Import it to use it: - -.. code-block:: python - - ... - from flask_ask import statement, question, convert_errors - - - @ask.intent('AgeIntent', convert={'age': int}) - def say_age(age): - if 'age' in convert_errors: - # since age failed to convert, it keeps its string - # value (e.g. "?") for later interrogation. - return question("Can you please repeat your age?") - - # conversion guaranteed to have succeeded - # age is an int - return statement("Your age is {}".format(age)) - - -``convert_errors`` is a ``dict`` that maps parameter names to the ``Exceptions`` raised during -conversion. When writing your own converters, raise ``Exceptions`` on failure, so -they work with ``convert_errors``:: - - def to_direction_const(s): - if s.lower() not in ['left', 'right'] - raise Exception("must be left or right") - return LEFT if s == 'left' else RIGHT - - @ask.intent('TurnIntent', convert={'direction': to_direction_const}) - def turn(direction): - # do something with direction - ... - - -That ``convert_errors`` is a ``dict`` allows for granular error recovery:: - - if 'something' in convert_errors: - # Did something fail? - -or:: - - if convert_errors: - # Did anything fail? - - - -``session``, ``context``, ``request`` and ``version`` Context Locals ---------------------------------------------------------------------- -An Alexa -`request payload `_ -has four top-level elements: ``session``, ``context``, ``request`` and ``version``. Like Flask, Flask-Ask provides `context -locals `_ that spare you from having to add these as extra parameters to -your functions. However, the ``request`` and ``session`` objects are distinct from Flask's ``request`` and ``session``. -Flask-Ask's ``request``, ``context`` and ``session`` correspond to the Alexa request payload components while Flask's correspond -to lower-level HTTP constructs. - -To use Flask-Ask's context locals, just import them:: - - from flask import App - from flask_ask import Ask, request, context, session, version - - app = Flask(__name__) - ask = Ask(app) - log = logging.getLogger() - - @ask.intent('ExampleIntent') - def example(): - log.info("Request ID: {}".format(request.requestId)) - log.info("Request Type: {}".format(request.type)) - log.info("Request Timestamp: {}".format(request.timestamp)) - log.info("Session New?: {}".format(session.new)) - log.info("User ID: {}".format(session.user.userId)) - log.info("Alexa Version: {}".format(version)) - log.info("Device ID: {}".format(context.System.device.deviceId)) - log.info("Consent Token: {}".format(context.System.user.permissions.consentToken)) - ... - -If you want to use both Flask and Flask-Ask context locals in the same module, use ``import as``:: - - from flask import App, request, session - from flask_ask import ( - Ask, - request as ask_request, - session as ask_session, - version - ) - -For a complete reference on ``request``, ``context`` and ``session`` fields, see the -`JSON Interface Reference for Custom Skills `_ -in the Alexa Skills Kit documentation. diff --git a/f-ask/docs/responses.rst b/f-ask/docs/responses.rst deleted file mode 100644 index b262929..0000000 --- a/f-ask/docs/responses.rst +++ /dev/null @@ -1,152 +0,0 @@ -Building Responses -================== - -📼 Here is a video demo on `Building Responses with Flask-Ask video `_ . - -The two primary constructs in Flask-Ask for creating responses are ``statement`` and ``question``. - -Statements terminate Echo sessions. The user is free to start another session, but Alexa will have no memory of it -(unless persistence is programmed separately on the server with a database or the like). - -A ``question``, on the other hand, prompts the user for additional speech and keeps a session open. -This session is similar to an HTTP session but the implementation is different. Since your application is -communicating with the Alexa service instead of a browser, there are no cookies or local storage. Instead, the -session is maintained in both the request and response JSON structures. In addition to the session component of -questions, questions also allow a ``reprompt``, which is typically a rephrasing of the question if user did not answer -the first time. - -This sections shows how to build responses with Flask-Ask. It contains the following subsections: - -.. contents:: - :local: - :backlinks: none - -Telling with ``statement`` --------------------------- -``statement`` closes the session:: - - @ask.intent('AllYourBaseIntent') - def all_your_base(): - return statement('All your base are belong to us') - - -Asking with ``question`` ------------------------- -Asking with ``question`` prompts the user for a response while keeping the session open:: - - @ask.intent('AppointmentIntent') - def make_appointment(): - return question("What day would you like to make an appointment for?") - -If the user doesn't respond, encourage them by rephrasing the question with ``reprompt``:: - - @ask.intent('AppointmentIntent') - def make_appointment(): - return question("What day would you like to make an appointment for?") \ - .reprompt("I didn't get that. When would you like to be seen?") - - -Session Management ------------------- - -The ``session`` context local has an ``attributes`` dictionary for persisting information across requests:: - - session.attributes['city'] = "San Francisco" - -When the response is rendered, the session attributes are automatically copied over into -the response's ``sessionAttributes`` structure. - -The renderer looks for an ``attribute_encoder`` on the session. If the renderer finds one, it will pass it to -``json.dumps`` as either that function's ``cls`` or ``default`` keyword parameters depending on whether -a ``json.JSONEncoder`` or a function is used, respectively. - -Here's an example that uses a function:: - - def _json_date_handler(obj): - if isinstance(obj, datetime.date): - return obj.isoformat() - - session.attributes['date'] = date - session.attributes_encoder = _json_date_handler - -See the `json.dump documentation `_ for for details about -that method's ``cls`` and ``default`` parameters. - - -Automatic Handling of Plaintext and SSML ----------------------------------------- -The Alexa Skills Kit supports plain text or -`SSML `_ outputs. Flask-Ask automatically -detects if your speech text contains SSML by attempting to parse it into XML, and checking -if the root element is ``speak``:: - - try: - xmldoc = ElementTree.fromstring(text) - if xmldoc.tag == 'speak': - # output type is 'SSML' - except ElementTree.ParseError: - pass - # output type is 'PlainText' - - -Displaying Cards in the Alexa Smartphone/Tablet App ---------------------------------------------------- -In addition to speaking back, Flask-Ask can display contextual cards in the Alexa smartphone/tablet app. All four -of the Alexa Skills Kit card types are supported. - -Simple cards display a title and message:: - - @ask.intent('AllYourBaseIntent') - def all_your_base(): - return statement('All your base are belong to us') \ - .simple_card(title='CATS says...', content='Make your time') - -Standard cards are like simple cards but they also support small and large image URLs:: - - @ask.intent('AllYourBaseIntent') - def all_your_base(): - return statement('All your base are belong to us') \ - .standard_card(title='CATS says...', - text='Make your time', - small_image_url='https://example.com/small.png', - large_image_url='https://example.com/large.png') - -Link account cards display a link to authorize the Alexa user with a user account in your system. The link displayed is the auhorization URL you configure in the amazon skill developer portal:: - - @ask.intent('AllYourBaseIntent') - def all_your_base(): - return statement('Please link your account in the Alexa app') \ - .link_account_card() - -Consent cards ask for the permission to access the device's address. You can either ask for the country and postal code (`read::alexa:device:all:address:country_and_postal_code`) or for the full address (`read::alexa:device:all:address`). The permission you ask for has to match what you've specified in the amazon skill developer portal:: - - @ask.intent('AllYourBaseIntent') - def all_your_base(): - return statement('Please allow access to your location') \ - .consent_card("read::alexa:device:all:address") - - -Jinja Templates ---------------- -You can also use Jinja templates. Define them in a YAML file named `templates.yaml` inside your application root:: - - @ask.intent('RBelongToUsIntent') - def all_your_base(): - notice = render_template('all_your_base_msg', who='us') - return statement(notice) - -.. code-block:: yaml - - all_your_base_msg: All your base are belong to {{ who }} - - multiple_line_example: | - - I am a multi-line SSML template. My content spans more than one line, - so there's a pipe and a newline that separates my name and value. - Enjoy the sounds of the ocean. - - -You can also use a custom templates file passed into the Ask object:: - - ask = Ask(app, '/', None, 'custom-templates.yml') diff --git a/f-ask/docs/user_contributions.rst b/f-ask/docs/user_contributions.rst deleted file mode 100644 index 7b8f5d4..0000000 --- a/f-ask/docs/user_contributions.rst +++ /dev/null @@ -1,28 +0,0 @@ -User Contributions -================== - -Have an article or video to submit? Please send it to john@johnwheeler.org - -`Flask-Ask: A New Python Framework for Rapid Alexa Skills Kit Development `_ - - by John Wheeler - -`Running with Alexa Part I. `_ - - by Tim Kjær Lange - -`Intro and Skill Logic - Alexa Skills w/ Python and Flask-Ask Part 1 `_ - - by Harrison Kinsley - -`Headlines Function - Alexa Skills w/ Python and Flask-Ask Part 2 `_ - - by Harrison Kinsley - -`Testing our Skill - Alexa Skills w/ Python and Flask-Ask Part 3 `_ - - by Harrison Kinsley - -`Flask-Ask — A tutorial on a simple and easy way to build complex Alexa Skills `_ - - by Bjorn Vuylsteker diff --git a/f-ask/flask_ask/__init__.py b/f-ask/flask_ask/__init__.py deleted file mode 100644 index d878d12..0000000 --- a/f-ask/flask_ask/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -logger = logging.getLogger('flask_ask') -logger.addHandler(logging.StreamHandler()) -if logger.level == logging.NOTSET: - logger.setLevel(logging.WARN) - - -from .core import ( - Ask, - request, - session, - version, - context, - current_stream, - convert_errors -) - -from .models import ( - question, - statement, - audio, - delegate, - elicit_slot, - confirm_slot, - confirm_intent, - buy, - upsell, - refund -) diff --git a/f-ask/flask_ask/cache.py b/f-ask/flask_ask/cache.py deleted file mode 100644 index b810af0..0000000 --- a/f-ask/flask_ask/cache.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Stream cache functions -""" - - -def push_stream(cache, user_id, stream): - """ - Push a stream onto the stream stack in cache. - - :param cache: werkzeug BasicCache-like object - :param user_id: id of user, used as key in cache - :param stream: stream object to push onto stack - - :return: True on successful update, - False if failed to update, - None if invalid input was given - """ - stack = cache.get(user_id) - if stack is None: - stack = [] - if stream: - stack.append(stream) - return cache.set(user_id, stack) - return None - - -def pop_stream(cache, user_id): - """ - Pop an item off the stack in the cache. If stack - is empty after pop, it deletes the stack. - - :param cache: werkzeug BasicCache-like object - :param user_id: id of user, used as key in cache - - :return: top item from stack, otherwise None - """ - stack = cache.get(user_id) - if stack is None: - return None - - result = stack.pop() - - if len(stack) == 0: - cache.delete(user_id) - else: - cache.set(user_id, stack) - - return result - - -def set_stream(cache, user_id, stream): - """ - Overwrite stack in the cache. - - :param cache: werkzeug BasicCache-liek object - :param user_id: id of user, used as key in cache - :param stream: value to initialize new stack with - - :return: None - """ - if stream: - return cache.set(user_id, [stream]) - - -def top_stream(cache, user_id): - """ - Peek at the top of the stack in the cache. - - :param cache: werkzeug BasicCache-like object - :param user_id: id of user, used as key in cache - - :return: top item in user's cached stack, otherwise None - """ - if not user_id: - return None - - stack = cache.get(user_id) - if stack is None: - return None - return stack.pop() diff --git a/f-ask/flask_ask/convert.py b/f-ask/flask_ask/convert.py deleted file mode 100644 index fcc6a8a..0000000 --- a/f-ask/flask_ask/convert.py +++ /dev/null @@ -1,57 +0,0 @@ -import re -from datetime import datetime, time - -import aniso8601 - -from . import logger - - -_DATE_PATTERNS = { - # "today", "tomorrow", "november twenty-fifth": 2015-11-25 - '^\d{4}-\d{2}-\d{2}$': '%Y-%m-%d', - # "this week", "next week": 2015-W48 - '^\d{4}-W\d{2}$': '%Y-W%U-%w', - # "this weekend": 2015-W48-WE - '^\d{4}-W\d{2}-WE$': '%Y-W%U-WE-%w', - # "this month": 2015-11 - '^\d{4}-\d{2}$': '%Y-%m', - # "next year": 2016 - '^\d{4}$': '%Y', -} - - -def to_date(amazon_date): - # make so 'next decade' matches work against 'next year' regex - amazon_date = re.sub('X$', '0', amazon_date) - for re_pattern, format_pattern in list(_DATE_PATTERNS.items()): - if re.match(re_pattern, amazon_date): - if '%U' in format_pattern: - # http://stackoverflow.com/a/17087427/1163855 - amazon_date += '-0' - return datetime.strptime(amazon_date, format_pattern).date() - return None - - -def to_time(amazon_time): - if amazon_time == "AM": - return time(hour=0) - if amazon_time == "PM": - return time(hour=12) - if amazon_time == "MO": - return time(hour=5) - if amazon_time == "AF": - return time(hour=12) - if amazon_time == "EV": - return time(hour=17) - if amazon_time == "NI": - return time(hour=21) - try: - return aniso8601.parse_time(amazon_time) - except ValueError as e: - logger.warn("ValueError for amazon_time '{}'.".format(amazon_time)) - logger.warn("ValueError message: {}".format(e.message)) - return None - - -def to_timedelta(amazon_duration): - return aniso8601.parse_duration(amazon_duration) diff --git a/f-ask/flask_ask/core.py b/f-ask/flask_ask/core.py deleted file mode 100644 index bf006c1..0000000 --- a/f-ask/flask_ask/core.py +++ /dev/null @@ -1,951 +0,0 @@ -import os -import sys -import yaml -import inspect -import io -from datetime import datetime -from functools import wraps, partial - -import aniso8601 -from werkzeug.contrib.cache import SimpleCache -from werkzeug.local import LocalProxy, LocalStack -from jinja2 import BaseLoader, ChoiceLoader, TemplateNotFound -from flask import current_app, json, request as flask_request, _app_ctx_stack - -from . import verifier, logger -from .convert import to_date, to_time, to_timedelta -from .cache import top_stream, set_stream -import collections - - -def find_ask(): - """ - Find our instance of Ask, navigating Local's and possible blueprints. - - Note: This only supports returning a reference to the first instance - of Ask found. - """ - if hasattr(current_app, 'ask'): - return getattr(current_app, 'ask') - else: - if hasattr(current_app, 'blueprints'): - blueprints = getattr(current_app, 'blueprints') - for blueprint_name in blueprints: - if hasattr(blueprints[blueprint_name], 'ask'): - return getattr(blueprints[blueprint_name], 'ask') - - -def dbgdump(obj, default=None, cls=None): - if current_app.config.get('ASK_PRETTY_DEBUG_LOGS', False): - indent = 2 - else: - indent = None - msg = json.dumps(obj, indent=indent, default=default, cls=cls) - logger.debug(msg) - - -request = LocalProxy(lambda: find_ask().request) -session = LocalProxy(lambda: find_ask().session) -version = LocalProxy(lambda: find_ask().version) -context = LocalProxy(lambda: find_ask().context) -convert_errors = LocalProxy(lambda: find_ask().convert_errors) -current_stream = LocalProxy(lambda: find_ask().current_stream) -stream_cache = LocalProxy(lambda: find_ask().stream_cache) - -from . import models - - -_converters = {'date': to_date, 'time': to_time, 'timedelta': to_timedelta} - - -class Ask(object): - """The Ask object provides the central interface for interacting with the Alexa service. - - Ask object maps Alexa Requests to flask view functions and handles Alexa sessions. - The constructor is passed a Flask App instance, and URL endpoint. - The Flask instance allows the convienient API of endpoints and their view functions, - so that Alexa requests may be mapped with syntax similar to a typical Flask server. - Route provides the entry point for the skill, and must be provided if an app is given. - - Keyword Arguments: - app {Flask object} -- App instance - created with Flask(__name__) (default: {None}) - route {str} -- entry point to which initial Alexa Requests are forwarded (default: {None}) - blueprint {Flask blueprint} -- Flask Blueprint instance to use instead of Flask App (default: {None}) - stream_cache {Werkzeug BasicCache} -- BasicCache-like object for storing Audio stream data (default: {SimpleCache}) - path {str} -- path to templates yaml file for VUI dialog (default: {'templates.yaml'}) - """ - - def __init__(self, app=None, route=None, blueprint=None, stream_cache=None, path='templates.yaml'): - self.app = app - self._route = route - self._intent_view_funcs = {} - self._intent_converts = {} - self._intent_defaults = {} - self._intent_mappings = {} - self._launch_view_func = None - self._session_ended_view_func = None - self._on_session_started_callback = None - self._default_intent_view_func = None - self._player_request_view_funcs = {} - self._player_mappings = {} - self._player_converts = {} - if app is not None: - self.init_app(app, path) - elif blueprint is not None: - self.init_blueprint(blueprint, path) - if stream_cache is None: - self.stream_cache = SimpleCache() - else: - self.stream_cache = stream_cache - - def init_app(self, app, path='templates.yaml'): - """Initializes Ask app by setting configuration variables, loading templates, and maps Ask route to a flask view. - - The Ask instance is given the following configuration variables by calling on Flask's configuration: - - `ASK_APPLICATION_ID`: - - Turn on application ID verification by setting this variable to an application ID or a - list of allowed application IDs. By default, application ID verification is disabled and a - warning is logged. This variable should be set in production to ensure - requests are being sent by the applications you specify. - Default: None - - `ASK_VERIFY_REQUESTS`: - - Enables or disables Alexa request verification, which ensures requests sent to your skill - are from Amazon's Alexa service. This setting should not be disabled in production. - It is useful for mocking JSON requests in automated tests. - Default: True - - `ASK_VERIFY_TIMESTAMP_DEBUG`: - - Turn on request timestamp verification while debugging by setting this to True. - Timestamp verification helps mitigate against replay attacks. It relies on the system clock - being synchronized with an NTP server. This setting should not be enabled in production. - Default: False - - `ASK_PRETTY_DEBUG_LOGS`: - - Add tabs and linebreaks to the Alexa request and response printed to the debug log. - This improves readability when printing to the console, but breaks formatting when logging to CloudWatch. - Default: False - """ - if self._route is None: - raise TypeError("route is a required argument when app is not None") - - self.app = app - - app.ask = self - - app.add_url_rule(self._route, view_func=self._flask_view_func, methods=['POST']) - app.jinja_loader = ChoiceLoader([app.jinja_loader, YamlLoader(app, path)]) - - def init_blueprint(self, blueprint, path='templates.yaml'): - """Initialize a Flask Blueprint, similar to init_app, but without the access - to the application config. - - Keyword Arguments: - blueprint {Flask Blueprint} -- Flask Blueprint instance to initialize (Default: {None}) - path {str} -- path to templates yaml file, relative to Blueprint (Default: {'templates.yaml'}) - """ - if self._route is not None: - raise TypeError("route cannot be set when using blueprints!") - - # we need to tuck our reference to this Ask instance into the blueprint object and find it later! - blueprint.ask = self - - # BlueprintSetupState.add_url_rule gets called underneath the covers and - # concats the rule string, so we should set to an empty string to allow - # Blueprint('blueprint_api', __name__, url_prefix="/ask") to result in - # exposing the rule at "/ask" and not "/ask/". - blueprint.add_url_rule("", view_func=self._flask_view_func, methods=['POST']) - blueprint.jinja_loader = ChoiceLoader([YamlLoader(blueprint, path)]) - - @property - def ask_verify_requests(self): - return current_app.config.get('ASK_VERIFY_REQUESTS', True) - - @property - def ask_verify_timestamp_debug(self): - return current_app.config.get('ASK_VERIFY_TIMESTAMP_DEBUG', False) - - @property - def ask_application_id(self): - return current_app.config.get('ASK_APPLICATION_ID', None) - - def on_session_started(self, f): - """Decorator to call wrapped function upon starting a session. - - @ask.on_session_started - def new_session(): - log.info('new session started') - - Because both launch and intent requests may begin a session, this decorator is used call - a function regardless of how the session began. - - Arguments: - f {function} -- function to be called when session is started. - """ - self._on_session_started_callback = f - - def launch(self, f): - """Decorator maps a view function as the endpoint for an Alexa LaunchRequest and starts the skill. - - @ask.launch - def launched(): - return question('Welcome to Foo') - - The wrapped function is registered as the launch view function and renders the response - for requests to the Launch URL. - A request to the launch URL is verified with the Alexa server before the payload is - passed to the view function. - - Arguments: - f {function} -- Launch view function - """ - self._launch_view_func = f - - @wraps(f) - def wrapper(*args, **kw): - self._flask_view_func(*args, **kw) - return f - - def session_ended(self, f): - """Decorator routes Alexa SessionEndedRequest to the wrapped view function to end the skill. - - @ask.session_ended - def session_ended(): - return "{}", 200 - - The wrapped function is registered as the session_ended view function - and renders the response for requests to the end of the session. - - Arguments: - f {function} -- session_ended view function - """ - self._session_ended_view_func = f - - @wraps(f) - def wrapper(*args, **kw): - self._flask_view_func(*args, **kw) - return f - - def intent(self, intent_name, mapping={}, convert={}, default={}): - """Decorator routes an Alexa IntentRequest and provides the slot parameters to the wrapped function. - - Functions decorated as an intent are registered as the view function for the Intent's URL, - and provide the backend responses to give your Skill its functionality. - - @ask.intent('WeatherIntent', mapping={'city': 'City'}) - def weather(city): - return statement('I predict great weather for {}'.format(city)) - - Arguments: - intent_name {str} -- Name of the intent request to be mapped to the decorated function - - Keyword Arguments: - mapping {dict} -- Maps parameters to intent slots of a different name - default: {} - - convert {dict} -- Converts slot values to data types before assignment to parameters - default: {} - - default {dict} -- Provides default values for Intent slots if Alexa reuqest - returns no corresponding slot, or a slot with an empty value - default: {} - """ - def decorator(f): - self._intent_view_funcs[intent_name] = f - self._intent_mappings[intent_name] = mapping - self._intent_converts[intent_name] = convert - self._intent_defaults[intent_name] = default - - @wraps(f) - def wrapper(*args, **kw): - self._flask_view_func(*args, **kw) - return f - return decorator - - def default_intent(self, f): - """Decorator routes any Alexa IntentRequest that is not matched by any existing @ask.intent routing.""" - self._default_intent_view_func = f - - @wraps(f) - def wrapper(*args, **kw): - self._flask_view_func(*args, **kw) - return f - - def display_element_selected(self, f): - """Decorator routes Alexa Display.ElementSelected request to the wrapped view function. - - @ask.display_element_selected - def eval_element(): - return "", 200 - - The wrapped function is registered as the display_element_selected view function - and renders the response for requests. - - Arguments: - f {function} -- display_element_selected view function - """ - self._display_element_selected_func = f - - @wraps(f) - def wrapper(*args, **kw): - self._flask_view_func(*args, **kw) - return f - - - def on_purchase_completed(self, mapping={'payload': 'payload','name':'name','status':'status','token':'token'}, convert={}, default={}): - """Decorator routes an Connections.Response to the wrapped function. - - Request is sent when Alexa completes the purchase flow. - See https://developer.amazon.com/docs/in-skill-purchase/add-isps-to-a-skill.html#handle-results - - - The wrapped view function may accept parameters from the Request. - In addition to locale, requestId, timestamp, and type - - - @ask.on_purchase_completed( mapping={'payload': 'payload','name':'name','status':'status','token':'token'}) - def completed(payload, name, status, token): - logger.info(payload) - logger.info(name) - logger.info(status) - logger.info(token) - - """ - def decorator(f): - self._intent_view_funcs['Connections.Response'] = f - self._intent_mappings['Connections.Response'] = mapping - self._intent_converts['Connections.Response'] = convert - self._intent_defaults['Connections.Response'] = default - @wraps(f) - def wrapper(*args, **kwargs): - self._flask_view_func(*args, **kwargs) - return f - return decorator - - - def on_playback_started(self, mapping={'offset': 'offsetInMilliseconds'}, convert={}, default={}): - """Decorator routes an AudioPlayer.PlaybackStarted Request to the wrapped function. - - Request sent when Alexa begins playing the audio stream previously sent in a Play directive. - This lets your skill verify that playback began successfully. - This request is also sent when Alexa resumes playback after pausing it for a voice request. - - The wrapped view function may accept parameters from the AudioPlayer Request. - In addition to locale, requestId, timestamp, and type - AudioPlayer Requests include: - offsetInMilliseconds - Position in stream when request was sent. - Not end of stream, often few ms after Play Directive offset. - This parameter is automatically mapped to 'offset' by default - - token - token of the stream that is nearly finished. - - @ask.on_playback_started() - def on_playback_start(token, offset): - logger.info('stream has token {}'.format(token)) - logger.info('Current position within the stream is {} ms'.format(offset)) - """ - def decorator(f): - self._intent_view_funcs['AudioPlayer.PlaybackStarted'] = f - self._intent_mappings['AudioPlayer.PlaybackStarted'] = mapping - self._intent_converts['AudioPlayer.PlaybackStarted'] = convert - self._intent_defaults['AudioPlayer.PlaybackStarted'] = default - - @wraps(f) - def wrapper(*args, **kwargs): - self._flask_view_func(*args, **kwargs) - return f - return decorator - - def on_playback_finished(self, mapping={'offset': 'offsetInMilliseconds'}, convert={}, default={}): - """Decorator routes an AudioPlayer.PlaybackFinished Request to the wrapped function. - - This type of request is sent when the stream Alexa is playing comes to an end on its own. - - Note: If your skill explicitly stops the playback with the Stop directive, - Alexa sends PlaybackStopped instead of PlaybackFinished. - - The wrapped view function may accept parameters from the AudioPlayer Request. - In addition to locale, requestId, timestamp, and type - AudioPlayer Requests include: - offsetInMilliseconds - Position in stream when request was sent. - Not end of stream, often few ms after Play Directive offset. - This parameter is automatically mapped to 'offset' by default. - - token - token of the stream that is nearly finished. - - Audioplayer Requests do not include the stream URL, it must be accessed from current_stream.url - """ - def decorator(f): - self._intent_view_funcs['AudioPlayer.PlaybackFinished'] = f - self._intent_mappings['AudioPlayer.PlaybackFinished'] = mapping - self._intent_converts['AudioPlayer.PlaybackFinished'] = convert - self._intent_defaults['AudioPlayer.PlaybackFinished'] = default - - @wraps(f) - def wrapper(*args, **kwargs): - self._flask_view_func(*args, **kwargs) - return f - return decorator - - def on_playback_stopped(self, mapping={'offset': 'offsetInMilliseconds'}, convert={}, default={}): - """Decorator routes an AudioPlayer.PlaybackStopped Request to the wrapped function. - - Sent when Alexa stops playing an audio stream in response to one of the following: - -AudioPlayer.Stop - -AudioPlayer.Play with a playBehavior of REPLACE_ALL. - -AudioPlayer.ClearQueue with a clearBehavior of CLEAR_ALL. - - This request is also sent if the user makes a voice request to Alexa, - since this temporarily pauses the playback. - In this case, the playback begins automatically once the voice interaction is complete. - - Note: If playback stops because the audio stream comes to an end on its own, - Alexa sends PlaybackFinished instead of PlaybackStopped. - - The wrapped view function may accept parameters from the AudioPlayer Request. - In addition to locale, requestId, timestamp, and type - AudioPlayer Requests include: - offsetInMilliseconds - Position in stream when request was sent. - Not end of stream, often few ms after Play Directive offset. - This parameter is automatically mapped to 'offset' by default. - - token - token of the stream that is nearly finished. - - Audioplayer Requests do not include the stream URL, it must be accessed from current_stream.url - """ - def decorator(f): - self._intent_view_funcs['AudioPlayer.PlaybackStopped'] = f - self._intent_mappings['AudioPlayer.PlaybackStopped'] = mapping - self._intent_converts['AudioPlayer.PlaybackStopped'] = convert - self._intent_defaults['AudioPlayer.PlaybackStopped'] = default - - @wraps(f) - def wrapper(*args, **kwargs): - self._flask_view_func(*args, **kwargs) - return f - return decorator - - def on_playback_nearly_finished(self, mapping={'offset': 'offsetInMilliseconds'}, convert={}, default={}): - """Decorator routes an AudioPlayer.PlaybackNearlyFinished Request to the wrapped function. - - This AudioPlayer Request sent when the device is ready to receive a new stream. - To progress through a playlist, respond to this request with an enqueue or play_next audio response. - - **Note** that this request is sent when Alexa is ready to receive a new stream to enqueue, and not - necessarily when the stream's offset is near the end. - The request may be sent by Alexa immediately after your skill sends a Play Directive. - - The wrapped view function may accept parameters from the AudioPlayer Request. - In addition to locale, requestId, timestamp, and type - This AudioPlayer Request includes: - AudioPlayer Requests include: - offsetInMilliseconds - Position in stream when request was sent. - Not end of stream, often few ms after Play Directive offset. - This parameter is automatically mapped to 'offset' by default. - - token - token of the stream that is nearly finished. - - Audioplayer Requests do not include the stream URL, and must be accessed from current_stream - - Example usage: - - @ask.on_playback_nearly_finished() - def play_next_stream(): - audio().enqueue(my_next_song) - - # offsetInMilliseconds is mapped to offset by default for convenience - @ask.on_playback_nearly_finished() - def show_request_feedback(offset, token): - logging.info('Nearly Finished') - logging.info('Stream at {} ms when Playback Request sent'.format(offset)) - logging.info('Stream holds the token {}'.format(token)) - logging.info('Streaming from {}'.format(current_stream.url)) - - # example of changing the default parameter mapping - @ask.on_playback_nearly_finished(mapping={'pos': 'offsetInMilliseconds', 'stream_token': 'token'}) - def show_request_feedback(pos, stream_token): - _infodump('Nearly Finished') - _infodump('Stream at {} ms when Playback Request sent'.format(pos)) - _infodump('Stream holds the token {}'.format(stream_token)) - """ - def decorator(f): - self._intent_view_funcs['AudioPlayer.PlaybackNearlyFinished'] = f - self._intent_mappings['AudioPlayer.PlaybackNearlyFinished'] = mapping - self._intent_converts['AudioPlayer.PlaybackNearlyFinished'] = convert - self._intent_defaults['AudioPlayer.PlaybackNearlyFinished'] = default - - @wraps(f) - def wrapper(*args, **kwargs): - self._flask_view_func(*args, **kwargs) - return f - return decorator - - def on_playback_failed(self, mapping={}, convert={}, default={}): - """Decorator routes an AudioPlayer.PlaybackFailed Request to the wrapped function. - - This AudioPlayer Request sent when Alexa encounters an error when attempting to play a stream. - - The wrapped view function may accept parameters from the AudioPlayer Request. - In addition to locale, requestId, timestamp, and type - - PlayBackFailed Requests include: - error - Contains error info under parameters type and message - - token - represents the stream that failed to play. - - currentPlaybackState - Details about the playback activity occurring at the time of the error - Contains the following parameters: - - token - represents the audio stream currently playing when the error occurred. - Note that this may be different from the value of the request.token property. - - offsetInMilliseconds - Position in stream when request was sent. - Not end of stream, often few ms after Play Directive offset. - This parameter is automatically mapped to 'offset' by default. - - playerActivity - player state when the error occurred - """ - def decorator(f): - self._intent_view_funcs['AudioPlayer.PlaybackFailed'] = f - self._intent_mappings['AudioPlayer.PlaybackFailed'] = mapping - self._intent_converts['AudioPlayer.PlaybackFailed'] = convert - self._intent_defaults['AudioPlayer.PlaybackFailed'] = default - - @wraps(f) - def wrapper(*args, **kwargs): - self._flask_view_func(*args, **kwargs) - return f - return decorator - - @property - def request(self): - return getattr(_app_ctx_stack.top, '_ask_request', None) - - @request.setter - def request(self, value): - _app_ctx_stack.top._ask_request = value - - @property - def session(self): - return getattr(_app_ctx_stack.top, '_ask_session', models._Field()) - - @session.setter - def session(self, value): - _app_ctx_stack.top._ask_session = value - - @property - def version(self): - return getattr(_app_ctx_stack.top, '_ask_version', None) - - @version.setter - def version(self, value): - _app_ctx_stack.top._ask_version = value - - @property - def context(self): - return getattr(_app_ctx_stack.top, '_ask_context', None) - - @context.setter - def context(self, value): - _app_ctx_stack.top._ask_context = value - - @property - def convert_errors(self): - return getattr(_app_ctx_stack.top, '_ask_convert_errors', None) - - @convert_errors.setter - def convert_errors(self, value): - _app_ctx_stack.top._ask_convert_errors = value - - @property - def current_stream(self): - #return getattr(_app_ctx_stack.top, '_ask_current_stream', models._Field()) - user = self._get_user() - if user: - stream = top_stream(self.stream_cache, user) - if stream: - current = models._Field() - current.__dict__.update(stream) - return current - return models._Field() - - @current_stream.setter - def current_stream(self, value): - # assumption 1 is we get a models._Field as value - # assumption 2 is if someone sets a value, it's resetting the stack - user = self._get_user() - if user: - set_stream(self.stream_cache, user, value.__dict__) - - def run_aws_lambda(self, event): - """Invoke the Flask Ask application from an AWS Lambda function handler. - - Use this method to service AWS Lambda requests from a custom Alexa - skill. This method will invoke your Flask application providing a - WSGI-compatible environment that wraps the original Alexa event - provided to the AWS Lambda handler. Returns the output generated by - a Flask Ask application, which should be used as the return value - to the AWS Lambda handler function. - - Example usage: - - from flask import Flask - from flask_ask import Ask, statement - - app = Flask(__name__) - ask = Ask(app, '/') - - # This function name is what you defined when you create an - # AWS Lambda function. By default, AWS calls this function - # lambda_handler. - def lambda_handler(event, _context): - return ask.run_aws_lambda(event) - - @ask.intent('HelloIntent') - def hello(firstname): - speech_text = "Hello %s" % firstname - return statement(speech_text).simple_card('Hello', speech_text) - """ - - # We are guaranteed to be called by AWS as a Lambda function does not - # expose a public facing interface. - self.app.config['ASK_VERIFY_REQUESTS'] = False - - # Convert an environment variable to a WSGI "bytes-as-unicode" string - enc, esc = sys.getfilesystemencoding(), 'surrogateescape' - def unicode_to_wsgi(u): - return u.encode(enc, esc).decode('iso-8859-1') - - # Create a WSGI-compatible environ that can be passed to the - # application. It is loaded with the OS environment variables, - # mandatory CGI-like variables, as well as the mandatory WSGI - # variables. - environ = {k: unicode_to_wsgi(v) for k, v in os.environ.items()} - environ['REQUEST_METHOD'] = 'POST' - environ['PATH_INFO'] = '/' - environ['SERVER_NAME'] = 'AWS-Lambda' - environ['SERVER_PORT'] = '80' - environ['SERVER_PROTOCOL'] = 'HTTP/1.0' - environ['wsgi.version'] = (1, 0) - environ['wsgi.url_scheme'] = 'http' - environ['wsgi.errors'] = sys.stderr - environ['wsgi.multithread'] = False - environ['wsgi.multiprocess'] = False - environ['wsgi.run_once'] = True - - # Convert the event provided by the AWS Lambda handler to a JSON - # string that can be read as the body of a HTTP POST request. - body = json.dumps(event) - environ['CONTENT_TYPE'] = 'application/json' - environ['CONTENT_LENGTH'] = len(body) - - PY3 = sys.version_info[0] == 3 - - if PY3: - environ['wsgi.input'] = io.StringIO(body) - else: - environ['wsgi.input'] = io.BytesIO(body) - - # Start response is a required callback that must be passed when - # the application is invoked. It is used to set HTTP status and - # headers. Read the WSGI spec for details (PEP3333). - headers = [] - def start_response(status, response_headers, _exc_info=None): - headers[:] = [status, response_headers] - - # Invoke the actual Flask application providing our environment, - # with our Alexa event as the body of the HTTP request, as well - # as the callback function above. The result will be an iterator - # that provides a serialized JSON string for our Alexa response. - result = self.app(environ, start_response) - try: - if not headers: - raise AssertionError("start_response() not called by WSGI app") - - output = b"".join(result) - if not headers[0].startswith("2"): - raise AssertionError("Non-2xx from app: hdrs={}, body={}".format(headers, output)) - - # The Lambda handler expects a Python object that can be - # serialized as JSON, so we need to take the already serialized - # JSON and deserialize it. - return json.loads(output) - - finally: - # Per the WSGI spec, we need to invoke the close method if it - # is implemented on the result object. - if hasattr(result, 'close'): - result.close() - - - def _get_user(self): - if self.context: - return self.context.get('System', {}).get('user', {}).get('userId') - return None - - - def _alexa_request(self, verify=True): - raw_body = flask_request.data - alexa_request_payload = json.loads(raw_body) - - if verify: - cert_url = flask_request.headers['Signaturecertchainurl'] - signature = flask_request.headers['Signature'] - - # load certificate - this verifies a the certificate url and format under the hood - cert = verifier.load_certificate(cert_url) - # verify signature - verifier.verify_signature(cert, signature, raw_body) - - # verify timestamp - raw_timestamp = alexa_request_payload.get('request', {}).get('timestamp') - timestamp = self._parse_timestamp(raw_timestamp) - - if not current_app.debug or self.ask_verify_timestamp_debug: - verifier.verify_timestamp(timestamp) - - # verify application id - try: - application_id = alexa_request_payload['session']['application']['applicationId'] - except KeyError: - application_id = alexa_request_payload['context'][ - 'System']['application']['applicationId'] - if self.ask_application_id is not None: - verifier.verify_application_id(application_id, self.ask_application_id) - - return alexa_request_payload - - @staticmethod - def _parse_timestamp(timestamp): - """ - Parse a given timestamp value, raising ValueError if None or Flasey - """ - if timestamp: - try: - return aniso8601.parse_datetime(timestamp) - except AttributeError: - # raised by aniso8601 if raw_timestamp is not valid string - # in ISO8601 format - try: - return datetime.utcfromtimestamp(timestamp) - except: - # relax the timestamp a bit in case it was sent in millis - return datetime.utcfromtimestamp(timestamp/1000) - - raise ValueError('Invalid timestamp value! Cannot parse from either ISO8601 string or UTC timestamp.') - - - def _update_stream(self): - fresh_stream = models._Field() - fresh_stream.__dict__.update(self.current_stream.__dict__) # keeps url attribute after stopping stream - fresh_stream.__dict__.update(self._from_directive()) - - context_info = self._from_context() - if context_info != None: - fresh_stream.__dict__.update(context_info) - - self.current_stream = fresh_stream - dbgdump(current_stream.__dict__) - - def _from_context(self): - return getattr(self.context, 'AudioPlayer', {}) - - def _from_directive(self): - from_buffer = top_stream(self.stream_cache, self._get_user()) - if from_buffer: - if self.request.intent and 'PauseIntent' in self.request.intent.name: - return {} - return from_buffer - return {} - - def _flask_view_func(self, *args, **kwargs): - ask_payload = self._alexa_request(verify=self.ask_verify_requests) - dbgdump(ask_payload) - request_body = models._Field(ask_payload) - - self.request = request_body.request - self.version = request_body.version - self.context = getattr(request_body, 'context', models._Field()) - self.session = getattr(request_body, 'session', self.session) # to keep old session.attributes through AudioRequests - - if not self.session: - self.session = models._Field() - if not self.session.attributes: - self.session.attributes = models._Field() - - self._update_stream() - - # add current dialog state in session - try: - self.session["dialogState"] = request.dialogState - except KeyError: - self.session["dialogState"] = "unknown" - - try: - if self.session.new and self._on_session_started_callback is not None: - self._on_session_started_callback() - except AttributeError: - pass - - result = None - request_type = self.request.type - - if request_type == 'LaunchRequest' and self._launch_view_func: - result = self._launch_view_func() - elif request_type == 'SessionEndedRequest': - if self._session_ended_view_func: - result = self._session_ended_view_func() - else: - result = "{}", 200 - elif request_type == 'IntentRequest' and self._intent_view_funcs: - result = self._map_intent_to_view_func(self.request.intent)() - elif request_type == 'Display.ElementSelected' and self._display_element_selected_func: - result = self._display_element_selected_func() - elif 'AudioPlayer' in request_type: - result = self._map_player_request_to_func(self.request.type)() - # routes to on_playback funcs - # user can also access state of content.AudioPlayer with current_stream - elif 'Connections.Response' in request_type: - result = self._map_purchase_request_to_func(self.request.type)() - - if result is not None: - if isinstance(result, models._Response): - return result.render_response() - return result - return "", 400 - - def _map_intent_to_view_func(self, intent): - """Provides appropiate parameters to the intent functions.""" - if intent.name in self._intent_view_funcs: - view_func = self._intent_view_funcs[intent.name] - elif self._default_intent_view_func is not None: - view_func = self._default_intent_view_func - else: - raise NotImplementedError('Intent "{}" not found and no default intent specified.'.format(intent.name)) - - PY3 = sys.version_info[0] == 3 - if PY3: - argspec = inspect.getfullargspec(view_func) - else: - argspec = inspect.getargspec(view_func) - - arg_names = argspec.args - arg_values = self._map_params_to_view_args(intent.name, arg_names) - - return partial(view_func, *arg_values) - - def _map_player_request_to_func(self, player_request_type): - """Provides appropriate parameters to the on_playback functions.""" - # calbacks for on_playback requests are optional - view_func = self._intent_view_funcs.get(player_request_type, lambda: None) - - argspec = inspect.getargspec(view_func) - arg_names = argspec.args - arg_values = self._map_params_to_view_args(player_request_type, arg_names) - - return partial(view_func, *arg_values) - - def _map_purchase_request_to_func(self, purchase_request_type): - """Provides appropriate parameters to the on_purchase functions.""" - - if purchase_request_type in self._intent_view_funcs: - view_func = self._intent_view_funcs[purchase_request_type] - else: - raise NotImplementedError('Request type "{}" not found and no default view specified.'.format(purchase_request_type)) - - argspec = inspect.getargspec(view_func) - arg_names = argspec.args - arg_values = self._map_params_to_view_args(purchase_request_type, arg_names) - - print('_map_purchase_request_to_func', arg_names, arg_values, view_func, purchase_request_type) - return partial(view_func, *arg_values) - - def _get_slot_value(self, slot_object): - slot_name = slot_object.name - slot_value = getattr(slot_object, 'value', None) - resolutions = getattr(slot_object, 'resolutions', None) - - if resolutions is not None: - resolutions_per_authority = getattr(resolutions, 'resolutionsPerAuthority', None) - if resolutions_per_authority is not None and len(resolutions_per_authority) > 0: - values = resolutions_per_authority[0].get('values', None) - if values is not None and len(values) > 0: - value = values[0].get('value', None) - if value is not None: - slot_value = value.get('name', slot_value) - - return slot_value - - def _map_params_to_view_args(self, view_name, arg_names): - - arg_values = [] - convert = self._intent_converts.get(view_name) - default = self._intent_defaults.get(view_name) - mapping = self._intent_mappings.get(view_name) - - convert_errors = {} - - request_data = {} - intent = getattr(self.request, 'intent', None) - if intent is not None: - if intent.slots is not None: - for slot_key in intent.slots.keys(): - slot_object = getattr(intent.slots, slot_key) - request_data[slot_object.name] = self._get_slot_value(slot_object=slot_object) - - else: - for param_name in self.request: - request_data[param_name] = getattr(self.request, param_name, None) - - for arg_name in arg_names: - param_or_slot = mapping.get(arg_name, arg_name) - arg_value = request_data.get(param_or_slot) - if arg_value is None or arg_value == "": - if arg_name in default: - default_value = default[arg_name] - if isinstance(default_value, collections.Callable): - default_value = default_value() - arg_value = default_value - elif arg_name in convert: - shorthand_or_function = convert[arg_name] - if shorthand_or_function in _converters: - shorthand = shorthand_or_function - convert_func = _converters[shorthand] - else: - convert_func = shorthand_or_function - try: - arg_value = convert_func(arg_value) - except Exception as e: - convert_errors[arg_name] = e - arg_values.append(arg_value) - self.convert_errors = convert_errors - return arg_values - - -class YamlLoader(BaseLoader): - - def __init__(self, app, path): - self.path = app.root_path + os.path.sep + path - self.mapping = {} - self._reload_mapping() - - def _reload_mapping(self): - if os.path.isfile(self.path): - self.last_mtime = os.path.getmtime(self.path) - with open(self.path) as f: - self.mapping = yaml.safe_load(f.read()) - - def get_source(self, environment, template): - if not os.path.isfile(self.path): - return None, None, None - if self.last_mtime != os.path.getmtime(self.path): - self._reload_mapping() - if template in self.mapping: - source = self.mapping[template] - return source, None, lambda: source == self.mapping.get(template) - raise TemplateNotFound(template) diff --git a/f-ask/flask_ask/models.py b/f-ask/flask_ask/models.py deleted file mode 100644 index d159f7b..0000000 --- a/f-ask/flask_ask/models.py +++ /dev/null @@ -1,460 +0,0 @@ -import inspect -from flask import json -from xml.etree import ElementTree -import aniso8601 -from .core import session, context, current_stream, stream_cache, dbgdump -from .cache import push_stream -import uuid - - -class _Field(dict): - """Container to represent Alexa Request Data. - - Initialized with request_json and creates a dict object with attributes - to be accessed via dot notation or as a dict key-value. - - Parameters within the request_json that contain their data as a json object - are also represented as a _Field object. - - Example: - - payload_object = _Field(alexa_json_payload) - - request_type_from_keys = payload_object['request']['type'] - request_type_from_attrs = payload_object.request.type - - assert request_type_from_keys == request_type_from_attrs - """ - - def __init__(self, request_json={}): - super(_Field, self).__init__(request_json) - for key, value in request_json.items(): - if isinstance(value, dict): - value = _Field(value) - self[key] = value - - def __getattr__(self, attr): - # converts timestamp str to datetime.datetime object - if 'timestamp' in attr: - return aniso8601.parse_datetime(self.get(attr)) - return self.get(attr) - - def __setattr__(self, key, value): - self.__setitem__(key, value) - - -class _Response(object): - - def __init__(self, speech): - self._json_default = None - self._response = { - 'outputSpeech': _output_speech(speech) - } - - def simple_card(self, title=None, content=None): - card = { - 'type': 'Simple', - 'title': title, - 'content': content - } - self._response['card'] = card - return self - - def standard_card(self, title=None, text=None, small_image_url=None, large_image_url=None): - card = { - 'type': 'Standard', - 'title': title, - 'text': text - } - - if any((small_image_url, large_image_url)): - card['image'] = {} - if small_image_url is not None: - card['image']['smallImageUrl'] = small_image_url - if large_image_url is not None: - card['image']['largeImageUrl'] = large_image_url - - self._response['card'] = card - return self - - def list_display_render(self, template=None, title=None, backButton='HIDDEN', token=None, background_image_url=None, image=None, listItems=None, hintText=None): - directive = [ - { - 'type': 'Display.RenderTemplate', - 'template': { - 'type': template, - 'backButton': backButton, - 'title': title, - 'listItems': listItems - } - } - ] - - if background_image_url is not None: - directive[0]['template']['backgroundImage'] = { - 'sources': [ - {'url': background_image_url} - ] - } - - if hintText is not None: - hint = { - 'type':'Hint', - 'hint': { - 'type':"PlainText", - 'text': hintText - } - } - directive.append(hint) - self._response['directives'] = directive - return self - - def display_render(self, template=None, title=None, backButton='HIDDEN', token=None, background_image_url=None, image=None, text=None, hintText=None): - directive = [ - { - 'type': 'Display.RenderTemplate', - 'template': { - 'type': template, - 'backButton': backButton, - 'title': title, - 'textContent': text - } - } - ] - - if background_image_url is not None: - directive[0]['template']['backgroundImage'] = { - 'sources': [ - {'url': background_image_url} - ] - } - - if image is not None: - directive[0]['template']['image'] = { - 'sources': [ - {'url': image} - ] - } - - if token is not None: - directive[0]['template']['token'] = token - - if hintText is not None: - hint = { - 'type':'Hint', - 'hint': { - 'type':"PlainText", - 'text': hintText - } - } - directive.append(hint) - - self._response['directives'] = directive - return self - - def link_account_card(self): - card = {'type': 'LinkAccount'} - self._response['card'] = card - return self - - def consent_card(self, permissions): - card = { - 'type': 'AskForPermissionsConsent', - 'permissions': [permissions] - } - self._response['card'] = card - return self - - def render_response(self): - response_wrapper = { - 'version': '1.0', - 'response': self._response, - 'sessionAttributes': session.attributes - } - - kw = {} - if hasattr(session, 'attributes_encoder'): - json_encoder = session.attributes_encoder - kwargname = 'cls' if inspect.isclass(json_encoder) else 'default' - kw[kwargname] = json_encoder - dbgdump(response_wrapper, **kw) - - return json.dumps(response_wrapper, **kw) - - -class statement(_Response): - - def __init__(self, speech): - super(statement, self).__init__(speech) - self._response['shouldEndSession'] = True - - -class question(_Response): - - def __init__(self, speech): - super(question, self).__init__(speech) - self._response['shouldEndSession'] = False - - def reprompt(self, reprompt): - reprompt = {'outputSpeech': _output_speech(reprompt)} - self._response['reprompt'] = reprompt - return self - - -class buy(_Response): - - def __init__(self, productId=None): - self._response = { - 'shouldEndSession': True, - 'directives': [{ - 'type': 'Connections.SendRequest', - 'name': 'Buy', - 'payload': { - 'InSkillProduct': { - 'productId': productId - } - }, - 'token': 'correlationToken' - }] - } - - -class refund(_Response): - - def __init__(self, productId=None): - self._response = { - 'shouldEndSession': True, - 'directives': [{ - 'type': 'Connections.SendRequest', - 'name': 'Cancel', - 'payload': { - 'InSkillProduct': { - 'productId': productId - } - }, - 'token': 'correlationToken' - }] - } - -class upsell(_Response): - - def __init__(self, productId=None, msg=None): - self._response = { - 'shouldEndSession': True, - 'directives': [{ - 'type': 'Connections.SendRequest', - 'name': 'Upsell', - 'payload': { - 'InSkillProduct': { - 'productId': productId - }, - 'upsellMessage': msg - }, - 'token': 'correlationToken' - }] - } - -class delegate(_Response): - - def __init__(self, updated_intent=None): - self._response = { - 'shouldEndSession': False, - 'directives': [{'type': 'Dialog.Delegate'}] - } - - if updated_intent: - self._response['directives'][0]['updatedIntent'] = updated_intent - - -class elicit_slot(_Response): - """ - Sends an ElicitSlot directive. - slot - The slot name to elicit - speech - The output speech - updated_intent - Optional updated intent - """ - - def __init__(self, slot, speech, updated_intent=None): - self._response = { - 'shouldEndSession': False, - 'directives': [{ - 'type': 'Dialog.ElicitSlot', - 'slotToElicit': slot, - }], - 'outputSpeech': _output_speech(speech), - } - - if updated_intent: - self._response['directives'][0]['updatedIntent'] = updated_intent - -class confirm_slot(_Response): - """ - Sends a ConfirmSlot directive. - slot - The slot name to confirm - speech - The output speech - updated_intent - Optional updated intent - """ - - def __init__(self, slot, speech, updated_intent=None): - self._response = { - 'shouldEndSession': False, - 'directives': [{ - 'type': 'Dialog.ConfirmSlot', - 'slotToConfirm': slot, - }], - 'outputSpeech': _output_speech(speech), - } - - if updated_intent: - self._response['directives'][0]['updatedIntent'] = updated_intent - -class confirm_intent(_Response): - """ - Sends a ConfirmIntent directive. - - """ - def __init__(self, speech, updated_intent=None): - self._response = { - 'shouldEndSession': False, - 'directives': [{ - 'type': 'Dialog.ConfirmIntent', - }], - 'outputSpeech': _output_speech(speech), - } - - if updated_intent: - self._response['directives'][0]['updatedIntent'] = updated_intent - - -class audio(_Response): - """Returns a response object with an Amazon AudioPlayer Directive. - - Responses for LaunchRequests and IntentRequests may include outputSpeech in addition to an audio directive - - Note that responses to AudioPlayer requests do not allow outputSpeech. - These must only include AudioPlayer Directives. - - @ask.intent('PlayFooAudioIntent') - def play_foo_audio(): - speech = 'playing from foo' - stream_url = www.foo.com - return audio(speech).play(stream_url) - - - @ask.intent('AMAZON.PauseIntent') - def stop_audio(): - return audio('Ok, stopping the audio').stop() - """ - - def __init__(self, speech=''): - super(audio, self).__init__(speech) - if not speech: - self._response = {} - self._response['directives'] = [] - - def play(self, stream_url, offset=0, opaque_token=None): - """Sends a Play Directive to begin playback and replace current and enqueued streams.""" - - self._response['shouldEndSession'] = True - directive = self._play_directive('REPLACE_ALL') - directive['audioItem'] = self._audio_item(stream_url=stream_url, offset=offset, opaque_token=opaque_token) - self._response['directives'].append(directive) - return self - - def enqueue(self, stream_url, offset=0, opaque_token=None): - """Adds stream to the queue. Does not impact the currently playing stream.""" - directive = self._play_directive('ENQUEUE') - audio_item = self._audio_item(stream_url=stream_url, - offset=offset, - push_buffer=False, - opaque_token=opaque_token) - audio_item['stream']['expectedPreviousToken'] = current_stream.token - - directive['audioItem'] = audio_item - self._response['directives'].append(directive) - return self - - def play_next(self, stream_url=None, offset=0, opaque_token=None): - """Replace all streams in the queue but does not impact the currently playing stream.""" - - directive = self._play_directive('REPLACE_ENQUEUED') - directive['audioItem'] = self._audio_item(stream_url=stream_url, offset=offset, opaque_token=opaque_token) - self._response['directives'].append(directive) - return self - - def resume(self): - """Sends Play Directive to resume playback at the paused offset""" - directive = self._play_directive('REPLACE_ALL') - directive['audioItem'] = self._audio_item() - self._response['directives'].append(directive) - return self - - def _play_directive(self, behavior): - directive = {} - directive['type'] = 'AudioPlayer.Play' - directive['playBehavior'] = behavior - return directive - - def _audio_item(self, stream_url=None, offset=0, push_buffer=True, opaque_token=None): - """Builds an AudioPlayer Directive's audioItem and updates current_stream""" - audio_item = {'stream': {}} - stream = audio_item['stream'] - - # existing stream - if not stream_url: - # stream.update(current_stream.__dict__) - stream['url'] = current_stream.url - stream['token'] = current_stream.token - stream['offsetInMilliseconds'] = current_stream.offsetInMilliseconds - - # new stream - else: - stream['url'] = stream_url - stream['token'] = opaque_token or str(uuid.uuid4()) - stream['offsetInMilliseconds'] = offset - - if push_buffer: # prevents enqueued streams from becoming current_stream - push_stream(stream_cache, context['System']['user']['userId'], stream) - return audio_item - - def stop(self): - """Sends AudioPlayer.Stop Directive to stop the current stream playback""" - self._response['directives'].append({'type': 'AudioPlayer.Stop'}) - return self - - def clear_queue(self, stop=False): - """Clears queued streams and optionally stops current stream. - - Keyword Arguments: - stop {bool} set True to stop current current stream and clear queued streams. - set False to clear queued streams and allow current stream to finish - default: {False} - """ - - directive = {} - directive['type'] = 'AudioPlayer.ClearQueue' - if stop: - directive['clearBehavior'] = 'CLEAR_ALL' - else: - directive['clearBehavior'] = 'CLEAR_ENQUEUED' - - self._response['directives'].append(directive) - return self - - -def _copyattr(src, dest, attr, convert=None): - if attr in src: - value = src[attr] - if convert is not None: - value = convert(value) - setattr(dest, attr, value) - - -def _output_speech(speech): - try: - xmldoc = ElementTree.fromstring(speech) - if xmldoc.tag == 'speak': - return {'type': 'SSML', 'ssml': speech} - except (UnicodeEncodeError, ElementTree.ParseError) as e: - pass - return {'type': 'PlainText', 'text': speech} diff --git a/f-ask/flask_ask/verifier.py b/f-ask/flask_ask/verifier.py deleted file mode 100644 index 29b256c..0000000 --- a/f-ask/flask_ask/verifier.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import base64 -import posixpath -from datetime import datetime -from six.moves.urllib.parse import urlparse -from six.moves.urllib.request import urlopen - -from OpenSSL import crypto - -from . import logger - - -class VerificationError(Exception): pass - - -def load_certificate(cert_url): - if not _valid_certificate_url(cert_url): - raise VerificationError("Certificate URL verification failed") - cert_data = urlopen(cert_url).read() - cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) - if not _valid_certificate(cert): - raise VerificationError("Certificate verification failed") - return cert - - -def verify_signature(cert, signature, signed_data): - try: - signature = base64.b64decode(signature) - crypto.verify(cert, signature, signed_data, 'sha1') - except crypto.Error as e: - raise VerificationError(e) - - -def verify_timestamp(timestamp): - dt = datetime.utcnow() - timestamp.replace(tzinfo=None) - if abs(dt.total_seconds()) > 150: - raise VerificationError("Timestamp verification failed") - - -def verify_application_id(candidate, records): - if candidate not in records: - raise VerificationError("Application ID verification failed") - - -def _valid_certificate_url(cert_url): - parsed_url = urlparse(cert_url) - if parsed_url.scheme == 'https': - if parsed_url.hostname == "s3.amazonaws.com": - if posixpath.normpath(parsed_url.path).startswith("/echo.api/"): - return True - return False - - -def _valid_certificate(cert): - not_after = cert.get_notAfter().decode('utf-8') - not_after = datetime.strptime(not_after, '%Y%m%d%H%M%SZ') - if datetime.utcnow() >= not_after: - return False - found = False - for i in range(0, cert.get_extension_count()): - extension = cert.get_extension(i) - short_name = extension.get_short_name().decode('utf-8') - value = str(extension) - if 'subjectAltName' == short_name and 'DNS:echo-api.amazon.com' == value: - found = True - break - if not found: - return False - return True diff --git a/f-ask/ngrok.exe b/f-ask/ngrok.exe deleted file mode 100644 index 0ec6adb..0000000 Binary files a/f-ask/ngrok.exe and /dev/null differ diff --git a/f-ask/requirements-dev.txt b/f-ask/requirements-dev.txt deleted file mode 100644 index f8d2793..0000000 --- a/f-ask/requirements-dev.txt +++ /dev/null @@ -1,5 +0,0 @@ --r requirements.txt -mock==2.0.0 -requests==2.13.0 -tox==2.7.0 - diff --git a/f-ask/requirements.txt b/f-ask/requirements.txt deleted file mode 100644 index 50a7927..0000000 --- a/f-ask/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -aniso8601==1.2.0 -Flask==0.12.1 -cryptography==2.1.4 -pyOpenSSL==17.0.0 -PyYAML==3.12 -six==1.11.0 - diff --git a/f-ask/samples/audio/playlist_demo/playlist.py b/f-ask/samples/audio/playlist_demo/playlist.py deleted file mode 100644 index fd493e1..0000000 --- a/f-ask/samples/audio/playlist_demo/playlist.py +++ /dev/null @@ -1,256 +0,0 @@ -import collections -import logging -import os -from copy import copy - -from flask import Flask, json -from flask_ask import Ask, question, statement, audio, current_stream, logger - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.INFO) - - -playlist = [ - # 'https://www.freesound.org/data/previews/367/367142_2188-lq.mp3', - 'https://archive.org/download/mailboxbadgerdrumsamplesvolume2/Ringing.mp3', - 'https://archive.org/download/petescott20160927/20160927%20RC300-53-127.0bpm.mp3', - 'https://archive.org/download/plpl011/plpl011_05-johnny_ripper-rain.mp3', - 'https://archive.org/download/piano_by_maxmsp/beats107.mp3', - 'https://archive.org/download/petescott20160927/20160927%20RC300-58-115.1bpm.mp3', - 'https://archive.org/download/PianoScale/PianoScale.mp3', - # 'https://archive.org/download/FemaleVoiceSample/Female_VoiceTalent_demo.mp4', - 'https://archive.org/download/mailboxbadgerdrumsamplesvolume2/Risset%20Drum%201.mp3', - 'https://archive.org/download/mailboxbadgerdrumsamplesvolume2/Submarine.mp3', - # 'https://ia800203.us.archive.org/27/items/CarelessWhisper_435/CarelessWhisper.ogg' -] - - -class QueueManager(object): - """Manages queue data in a seperate context from current_stream. - - The flask-ask Local current_stream refers only to the current data from Alexa requests and Skill Responses. - Alexa Skills Kit does not provide enqueued or stream-histroy data and does not provide a session attribute - when delivering AudioPlayer Requests. - - This class is used to maintain accurate control of multiple streams, - so that the user may send Intents to move throughout a queue. - """ - - def __init__(self, urls): - self._urls = urls - self._queued = collections.deque(urls) - self._history = collections.deque() - self._current = None - - @property - def status(self): - status = { - 'Current Position': self.current_position, - 'Current URL': self.current, - 'Next URL': self.up_next, - 'Previous': self.previous, - 'History': list(self.history) - } - return status - - @property - def up_next(self): - """Returns the url at the front of the queue""" - qcopy = copy(self._queued) - try: - return qcopy.popleft() - except IndexError: - return None - - @property - def current(self): - return self._current - - @current.setter - def current(self, url): - self._save_to_history() - self._current = url - - @property - def history(self): - return self._history - - @property - def previous(self): - history = copy(self.history) - try: - return history.pop() - except IndexError: - return None - - def add(self, url): - self._urls.append(url) - self._queued.append(url) - - def extend(self, urls): - self._urls.extend(urls) - self._queued.extend(urls) - - def _save_to_history(self): - if self._current: - self._history.append(self._current) - - def end_current(self): - self._save_to_history() - self._current = None - - def step(self): - self.end_current() - self._current = self._queued.popleft() - return self._current - - def step_back(self): - self._queued.appendleft(self._current) - self._current = self._history.pop() - return self._current - - def reset(self): - self._queued = collections.deque(self._urls) - self._history = [] - - def start(self): - self.__init__(self._urls) - return self.step() - - @property - def current_position(self): - return len(self._history) + 1 - - -queue = QueueManager(playlist) - - -@ask.launch -def launch(): - card_title = 'Playlist Example' - text = 'Welcome to an example for playing a playlist. You can ask me to start the playlist.' - prompt = 'You can ask start playlist.' - return question(text).reprompt(prompt).simple_card(card_title, text) - - -@ask.intent('PlaylistDemoIntent') -def start_playlist(): - speech = 'Heres a playlist of some sounds. You can ask me Next, Previous, or Start Over' - stream_url = queue.start() - return audio(speech).play(stream_url) - - -# QueueManager object is not stepped forward here. -# This allows for Next Intents and on_playback_finished requests to trigger the step -@ask.on_playback_nearly_finished() -def nearly_finished(): - if queue.up_next: - _infodump('Alexa is now ready for a Next or Previous Intent') - # dump_stream_info() - next_stream = queue.up_next - _infodump('Enqueueing {}'.format(next_stream)) - return audio().enqueue(next_stream) - else: - _infodump('Nearly finished with last song in playlist') - - -@ask.on_playback_finished() -def play_back_finished(): - _infodump('Finished Audio stream for track {}'.format(queue.current_position)) - if queue.up_next: - queue.step() - _infodump('stepped queue forward') - dump_stream_info() - else: - return statement('You have reached the end of the playlist!') - - -# NextIntent steps queue forward and clears enqueued streams that were already sent to Alexa -# next_stream will match queue.up_next and enqueue Alexa with the correct subsequent stream. -@ask.intent('AMAZON.NextIntent') -def next_song(): - if queue.up_next: - speech = 'playing next queued song' - next_stream = queue.step() - _infodump('Stepped queue forward to {}'.format(next_stream)) - dump_stream_info() - return audio(speech).play(next_stream) - else: - return audio('There are no more songs in the queue') - - -@ask.intent('AMAZON.PreviousIntent') -def previous_song(): - if queue.previous: - speech = 'playing previously played song' - prev_stream = queue.step_back() - dump_stream_info() - return audio(speech).play(prev_stream) - - else: - return audio('There are no songs in your playlist history.') - - -@ask.intent('AMAZON.StartOverIntent') -def restart_track(): - if queue.current: - speech = 'Restarting current track' - dump_stream_info() - return audio(speech).play(queue.current, offset=0) - else: - return statement('There is no current song') - - -@ask.on_playback_started() -def started(offset, token, url): - _infodump('Started audio stream for track {}'.format(queue.current_position)) - dump_stream_info() - - -@ask.on_playback_stopped() -def stopped(offset, token): - _infodump('Stopped audio stream for track {}'.format(queue.current_position)) - -@ask.intent('AMAZON.PauseIntent') -def pause(): - seconds = current_stream.offsetInMilliseconds / 1000 - msg = 'Paused the Playlist on track {}, offset at {} seconds'.format( - queue.current_position, seconds) - _infodump(msg) - dump_stream_info() - return audio(msg).stop().simple_card(msg) - - -@ask.intent('AMAZON.ResumeIntent') -def resume(): - seconds = current_stream.offsetInMilliseconds / 1000 - msg = 'Resuming the Playlist on track {}, offset at {} seconds'.format(queue.current_position, seconds) - _infodump(msg) - dump_stream_info() - return audio(msg).resume().simple_card(msg) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - -def dump_stream_info(): - status = { - 'Current Stream Status': current_stream.__dict__, - 'Queue status': queue.status - } - _infodump(status) - - -def _infodump(obj, indent=2): - msg = json.dumps(obj, indent=indent) - logger.info(msg) - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) diff --git a/f-ask/samples/audio/playlist_demo/speech_assets/IntentSchema.json b/f-ask/samples/audio/playlist_demo/speech_assets/IntentSchema.json deleted file mode 100644 index 0c699c4..0000000 --- a/f-ask/samples/audio/playlist_demo/speech_assets/IntentSchema.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "intents": [ - { - "intent": "AMAZON.PauseIntent" - }, - { - "intent": "PlaylistDemoIntent" - }, - { - "intent": "AMAZON.StopIntent" - }, - { - "intent": "AMAZON.ResumeIntent" - } - ] -} \ No newline at end of file diff --git a/f-ask/samples/audio/playlist_demo/speech_assets/SampleUtterances.txt b/f-ask/samples/audio/playlist_demo/speech_assets/SampleUtterances.txt deleted file mode 100644 index 167d52f..0000000 --- a/f-ask/samples/audio/playlist_demo/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1 +0,0 @@ -PlaylistDemoIntent start the playlist \ No newline at end of file diff --git a/f-ask/samples/audio/simple_demo/ask_audio.py b/f-ask/samples/audio/simple_demo/ask_audio.py deleted file mode 100644 index edaec93..0000000 --- a/f-ask/samples/audio/simple_demo/ask_audio.py +++ /dev/null @@ -1,89 +0,0 @@ -import logging -import os - -from flask import Flask, json, render_template -from flask_ask import Ask, request, session, question, statement, context, audio, current_stream - -app = Flask(__name__) -ask = Ask(app, "/") -logger = logging.getLogger() -logging.getLogger('flask_ask').setLevel(logging.INFO) - - -@ask.launch -def launch(): - card_title = 'Audio Example' - text = 'Welcome to an audio example. You can ask to begin demo, or try asking me to play the sax.' - prompt = 'You can ask to begin demo, or try asking me to play the sax.' - return question(text).reprompt(prompt).simple_card(card_title, text) - - -@ask.intent('DemoIntent') -def demo(): - speech = "Here's one of my favorites" - stream_url = 'https://www.vintagecomputermusic.com/mp3/s2t9_Computer_Speech_Demonstration.mp3' - return audio(speech).play(stream_url, offset=93000) - - -# 'ask audio_skil Play the sax -@ask.intent('SaxIntent') -def george_michael(): - speech = 'yeah you got it!' - stream_url = 'https://ia800203.us.archive.org/27/items/CarelessWhisper_435/CarelessWhisper.ogg' - return audio(speech).play(stream_url) - - -@ask.intent('AMAZON.PauseIntent') -def pause(): - return audio('Paused the stream.').stop() - - -@ask.intent('AMAZON.ResumeIntent') -def resume(): - return audio('Resuming.').resume() - -@ask.intent('AMAZON.StopIntent') -def stop(): - return audio('stopping').clear_queue(stop=True) - - - -# optional callbacks -@ask.on_playback_started() -def started(offset, token): - _infodump('STARTED Audio Stream at {} ms'.format(offset)) - _infodump('Stream holds the token {}'.format(token)) - _infodump('STARTED Audio stream from {}'.format(current_stream.url)) - - -@ask.on_playback_stopped() -def stopped(offset, token): - _infodump('STOPPED Audio Stream at {} ms'.format(offset)) - _infodump('Stream holds the token {}'.format(token)) - _infodump('Stream stopped playing from {}'.format(current_stream.url)) - - -@ask.on_playback_nearly_finished() -def nearly_finished(): - _infodump('Stream nearly finished from {}'.format(current_stream.url)) - -@ask.on_playback_finished() -def stream_finished(token): - _infodump('Playback has finished for stream with token {}'.format(token)) - -@ask.session_ended -def session_ended(): - return "{}", 200 - -def _infodump(obj, indent=2): - msg = json.dumps(obj, indent=indent) - logger.info(msg) - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) - diff --git a/f-ask/samples/audio/simple_demo/speech_assets/IntentSchema.json b/f-ask/samples/audio/simple_demo/speech_assets/IntentSchema.json deleted file mode 100644 index 553597f..0000000 --- a/f-ask/samples/audio/simple_demo/speech_assets/IntentSchema.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "intents": [ - { - "intent": "AMAZON.PauseIntent" - }, - { - "intent": "DemoIntent" - }, - { - "intent": "SaxIntent" - }, - { - "intent": "AMAZON.StopIntent" - }, - { - "intent": "AMAZON.ResumeIntent" - } - ] -} \ No newline at end of file diff --git a/f-ask/samples/audio/simple_demo/speech_assets/SampleUtterances.txt b/f-ask/samples/audio/simple_demo/speech_assets/SampleUtterances.txt deleted file mode 100644 index 98fdc7d..0000000 --- a/f-ask/samples/audio/simple_demo/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,3 +0,0 @@ -DemoIntent begin demo -SaxIntent play the sax -SaxIntent play sax \ No newline at end of file diff --git a/f-ask/samples/blueprint_demo/demo.py b/f-ask/samples/blueprint_demo/demo.py deleted file mode 100644 index 746c14c..0000000 --- a/f-ask/samples/blueprint_demo/demo.py +++ /dev/null @@ -1,18 +0,0 @@ -import logging -import os - -from flask import Flask -from helloworld import blueprint - -app = Flask(__name__) -app.register_blueprint(blueprint) - -logging.getLogger('flask_app').setLevel(logging.DEBUG) - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) diff --git a/f-ask/samples/blueprint_demo/helloworld.py b/f-ask/samples/blueprint_demo/helloworld.py deleted file mode 100644 index 55e6405..0000000 --- a/f-ask/samples/blueprint_demo/helloworld.py +++ /dev/null @@ -1,34 +0,0 @@ -import logging - -from flask import Blueprint, render_template -from flask_ask import Ask, question, statement - - -blueprint = Blueprint('blueprint_api', __name__, url_prefix="/ask") -ask = Ask(blueprint=blueprint) - -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -@ask.launch -def launch(): - speech_text = render_template('welcome') - return question(speech_text).reprompt(speech_text).simple_card('HelloWorld', speech_text) - - -@ask.intent('HelloWorldIntent') -def hello_world(): - speech_text = render_template('hello') - return statement(speech_text).simple_card('HelloWorld', speech_text) - - -@ask.intent('AMAZON.HelpIntent') -def help(): - speech_text = render_template('help') - return question(speech_text).reprompt(speech_text).simple_card('HelloWorld', speech_text) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - diff --git a/f-ask/samples/blueprint_demo/speech_assets/IntentSchema.json b/f-ask/samples/blueprint_demo/speech_assets/IntentSchema.json deleted file mode 100644 index 37c2405..0000000 --- a/f-ask/samples/blueprint_demo/speech_assets/IntentSchema.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "intents": [ - { - "intent": "HelloWorldIntent" - }, - { - "intent": "AMAZON.HelpIntent" - } - ] -} diff --git a/f-ask/samples/blueprint_demo/speech_assets/SampleUtterances.txt b/f-ask/samples/blueprint_demo/speech_assets/SampleUtterances.txt deleted file mode 100644 index d9f178e..0000000 --- a/f-ask/samples/blueprint_demo/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,7 +0,0 @@ -HelloWorldIntent say hello -HelloWorldIntent say hello world -HelloWorldIntent hello -HelloWorldIntent say hi -HelloWorldIntent say hi world -HelloWorldIntent hi -HelloWorldIntent how are you diff --git a/f-ask/samples/blueprint_demo/templates.yaml b/f-ask/samples/blueprint_demo/templates.yaml deleted file mode 100644 index 81996fb..0000000 --- a/f-ask/samples/blueprint_demo/templates.yaml +++ /dev/null @@ -1,3 +0,0 @@ -welcome: "Welcome to the Alexa Skills Kit, you can say hello" -hello: "Hello world!" -help: "You can say hello to me!" \ No newline at end of file diff --git a/f-ask/samples/helloworld/helloworld.py b/f-ask/samples/helloworld/helloworld.py deleted file mode 100644 index 0daba43..0000000 --- a/f-ask/samples/helloworld/helloworld.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -import os - -from flask import Flask -from flask_ask import Ask, request, session, question, statement - - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -@ask.launch -def launch(): - speech_text = 'Welcome to the Alexa Skills Kit, you can say hello' - return question(speech_text).reprompt(speech_text).simple_card('HelloWorld', speech_text) - - -@ask.intent('HelloWorldIntent') -def hello_world(): - speech_text = 'Hello world' - return statement(speech_text).simple_card('HelloWorld', speech_text) - - -@ask.intent('AMAZON.HelpIntent') -def help(): - speech_text = 'You can say hello to me!' - return question(speech_text).reprompt(speech_text).simple_card('HelloWorld', speech_text) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) diff --git a/f-ask/samples/helloworld/speech_assets/IntentSchema.json b/f-ask/samples/helloworld/speech_assets/IntentSchema.json deleted file mode 100644 index 37c2405..0000000 --- a/f-ask/samples/helloworld/speech_assets/IntentSchema.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "intents": [ - { - "intent": "HelloWorldIntent" - }, - { - "intent": "AMAZON.HelpIntent" - } - ] -} diff --git a/f-ask/samples/helloworld/speech_assets/SampleUtterances.txt b/f-ask/samples/helloworld/speech_assets/SampleUtterances.txt deleted file mode 100644 index d9f178e..0000000 --- a/f-ask/samples/helloworld/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,7 +0,0 @@ -HelloWorldIntent say hello -HelloWorldIntent say hello world -HelloWorldIntent hello -HelloWorldIntent say hi -HelloWorldIntent say hi world -HelloWorldIntent hi -HelloWorldIntent how are you diff --git a/f-ask/samples/historybuff/historybuff.py b/f-ask/samples/historybuff/historybuff.py deleted file mode 100644 index fbe94f2..0000000 --- a/f-ask/samples/historybuff/historybuff.py +++ /dev/null @@ -1,145 +0,0 @@ -import logging -import os -import re -from six.moves.urllib.request import urlopen - - -from flask import Flask -from flask_ask import Ask, request, session, question, statement - - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -# URL prefix to download history content from Wikipedia. -URL_PREFIX = 'https://en.wikipedia.org/w/api.php?action=query&prop=extracts' + \ - '&format=json&explaintext=&exsectionformat=plain&redirects=&titles=' - -# Constant defining number of events to be read at one time. -PAGINATION_SIZE = 3 - -# Length of the delimiter between individual events. -DELIMITER_SIZE = 2 - -# Size of events from Wikipedia response. -SIZE_OF_EVENTS = 10 - -# Constant defining session attribute key for the event index -SESSION_INDEX = 'index' - -# Constant defining session attribute key for the event text key for date of events. -SESSION_TEXT = 'text' - - -@ask.launch -def launch(): - speech_output = 'History buff. What day do you want events for?' - reprompt_text = "With History Buff, you can get historical events for any day of the year. " + \ - "For example, you could say today, or August thirtieth. " + \ - "Now, which day do you want?" - return question(speech_output).reprompt(reprompt_text) - - -@ask.intent('GetFirstEventIntent', convert={ 'day': 'date' }) -def get_first_event(day): - month_name = day.strftime('%B') - day_number = day.day - events = _get_json_events_from_wikipedia(month_name, day_number) - if not events: - speech_output = "There is a problem connecting to Wikipedia at this time. Please try again later." - return statement('{}'.format(speech_output)) - else: - card_title = "Events on {} {}".format(month_name, day_number) - speech_output = "

For {} {}

".format(month_name, day_number) - card_output = "" - for i in range(PAGINATION_SIZE): - speech_output += "

{}

".format(events[i]) - card_output += "{}\n".format(events[i]) - speech_output += " Wanna go deeper into history?" - card_output += " Wanna go deeper into history?" - reprompt_text = "With History Buff, you can get historical events for any day of the year. " + \ - "For example, you could say today, or August thirtieth. " + \ - "Now, which day do you want?" - session.attributes[SESSION_INDEX] = PAGINATION_SIZE - session.attributes[SESSION_TEXT] = events - speech_output = '{}'.format(speech_output) - return question(speech_output).reprompt(reprompt_text).simple_card(card_title, card_output) - - -@ask.intent('GetNextEventIntent') -def get_next_event(): - events = session.attributes[SESSION_TEXT] - index = session.attributes[SESSION_INDEX] - card_title = "More events on this day in history" - speech_output = "" - card_output = "" - i = 0 - while i < PAGINATION_SIZE and index < len(events): - speech_output += "

{}

".format(events[index]) - card_output += "{}\n".format(events[index]) - i += 1 - index += 1 - speech_output += " Wanna go deeper into history?" - reprompt_text = "Do you want to know more about what happened on this date?" - session.attributes[SESSION_INDEX] = index - speech_output = '{}'.format(speech_output) - return question(speech_output).reprompt(reprompt_text).simple_card(card_title, card_output) - - -@ask.intent('AMAZON.StopIntent') -def stop(): - return statement("Goodbye") - - -@ask.intent('AMAZON.CancelIntent') -def cancel(): - return statement("Goodbye") - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -def _get_json_events_from_wikipedia(month, date): - url = "{}{}_{}".format(URL_PREFIX, month, date) - data = urlopen(url).read().decode('utf-8') - return _parse_json(data) - - -def _parse_json(text): - events = [] - try: - slice_start = text.index("\\nEvents\\n") + SIZE_OF_EVENTS - slice_end = text.index("\\n\\n\\nBirths") - text = text[slice_start:slice_end]; - except ValueError: - return events - start_index = end_index = 0 - done = False - while not done: - try: - end_index = text.index('\\n', start_index + DELIMITER_SIZE) - event_text = text[start_index:end_index] - start_index = end_index + 2 - except ValueError: - event_text = text[start_index:] - done = True - # replace dashes returned in text from Wikipedia's API - event_text = event_text.replace('\\u2013', '') - # add comma after year so Alexa pauses before continuing with the sentence - event_text = re.sub('^\d+', r'\g<0>,', event_text) - events.append(event_text) - events.reverse() - return events - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) - diff --git a/f-ask/samples/historybuff/speech_assets/IntentSchema.json b/f-ask/samples/historybuff/speech_assets/IntentSchema.json deleted file mode 100644 index 8979d54..0000000 --- a/f-ask/samples/historybuff/speech_assets/IntentSchema.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "intents": [ - { - "intent": "GetFirstEventIntent", - "slots": [ - { - "name": "day", - "type": "AMAZON.DATE" - } - ] - }, - { - "intent": "GetNextEventIntent" - }, - { - "intent": "AMAZON.HelpIntent" - }, - { - "intent": "AMAZON.StopIntent" - }, - { - "intent": "AMAZON.CancelIntent" - } - ] -} diff --git a/f-ask/samples/historybuff/speech_assets/SampleUtterances.txt b/f-ask/samples/historybuff/speech_assets/SampleUtterances.txt deleted file mode 100644 index 56ef054..0000000 --- a/f-ask/samples/historybuff/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,15 +0,0 @@ -GetFirstEventIntent get events for {day} -GetFirstEventIntent give me events for {day} -GetFirstEventIntent what happened on {day} -GetFirstEventIntent what happened -GetFirstEventIntent {day} - -GetNextEventIntent yes -GetNextEventIntent yup -GetNextEventIntent sure -GetNextEventIntent yes please - -AMAZON.StopIntent no -AMAZON.StopIntent nope -AMAZON.StopIntent no thanks -AMAZON.StopIntent no thank you diff --git a/f-ask/samples/purchase/IntentSchema.json b/f-ask/samples/purchase/IntentSchema.json deleted file mode 100644 index 80b813f..0000000 --- a/f-ask/samples/purchase/IntentSchema.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "interactionModel": { - "languageModel": { - "invocationName": "demo", - "intents": [ - { - "name": "AMAZON.FallbackIntent", - "samples": [] - }, - { - "name": "AMAZON.CancelIntent", - "samples": [] - }, - { - "name": "AMAZON.HelpIntent", - "samples": [] - }, - { - "name": "AMAZON.StopIntent", - "samples": [] - }, - { - "name": "BuySkillItemIntent", - "slots": [ - { - "name": "ProductName", - "type": "LIST_OF_PRODUCT_NAMES" - } - ], - "samples": [ - "{ProductName}", - "buy", - "shop", - "buy {ProductName}", - "purchase {ProductName}", - "want {ProductName}", - "would like {ProductName}" - ] - }, - { - "name": "RefundSkillItemIntent", - "slots": [ - { - "name": "ProductName", - "type": "LIST_OF_PRODUCT_NAMES" - } - ], - "samples": [ - "cancel {ProductName}", - "return {ProductName}", - "refund {ProductName}", - "want a refund for {ProductName}", - "would like to return {ProductName}" - ] - } - ], - "types": [ - { - "name": "LIST_OF_PRODUCT_NAMES", - "values": [ - { - "name": { - "value": "monthly subscription" - } - }, - { - "name": { - "value": "start smoking" - } - }, - { - "name": { - "value": "stop smoking" - } - } - ] - } - ] - } - } -} diff --git a/f-ask/samples/purchase/model.py b/f-ask/samples/purchase/model.py deleted file mode 100644 index 957033c..0000000 --- a/f-ask/samples/purchase/model.py +++ /dev/null @@ -1,79 +0,0 @@ -import requests -from flask import json -from flask_ask import logger - -class Product(): - ''' - Object model for inSkillProducts and methods to access products. - - {"inSkillProducts":[ - {"productId":"amzn1.adg.product.your_product_id", - "referenceName":"product_name", - "type":"ENTITLEMENT", - "name":"product name", - "summary":"This product has helped many people.", - "entitled":"NOT_ENTITLED", - "purchasable":"NOT_PURCHASABLE"}], - "nextToken":null, - "truncated":false} - - ''' - - def __init__(self, apiAccessToken): - self.token = apiAccessToken - self.product_list = self.query() - - - def query(self): - # Information required to invoke the API is available in the session - apiEndpoint = "https://api.amazonalexa.com" - apiPath = "/v1/users/~current/skills/~current/inSkillProducts" - token = "bearer " + self.token - language = "en-US" #self.event.request.locale - - url = apiEndpoint + apiPath - headers = { - "Content-Type" : 'application/json', - "Accept-Language" : language, - "Authorization" : token - } - #Call the API - res = requests.get(url, headers=headers) - logger.info('PRODUCTS:' + '*' * 80) - logger.info(res.status_code) - logger.info(res.text) - if res.status_code == 200: - data = json.loads(res.text) - return data['inSkillProducts'] - else: - return None - - def list(self): - """ return list of purchasable and not entitled products""" - mylist = [] - for prod in self.product_list: - if self.purchasable(prod) and not self.entitled(prod): - mylist.append(prod) - return mylist - - def purchasable(self, product): - """ return True if purchasable product""" - return 'PURCHASABLE' == product['purchasable'] - - def entitled(self, product): - """ return True if entitled product""" - return 'ENTITLED' == product['entitled'] - - - def productId(self, name): - print(self.product_list) - for prod in self.product_list: - if name == prod['name'].lower(): - return prod['productId'] - return None - - def productName(self, id): - for prod in self.product_list: - if id == prod['productId']: - return prod['name'] - return None diff --git a/f-ask/samples/purchase/purchase.py b/f-ask/samples/purchase/purchase.py deleted file mode 100644 index ba03a27..0000000 --- a/f-ask/samples/purchase/purchase.py +++ /dev/null @@ -1,88 +0,0 @@ -import logging -import os -import requests - -from flask import Flask, json, render_template -from flask_ask import Ask, request, session, question, statement, context, buy, upsell, refund, logger -from model import Product - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -PRODUCT_KEY = "PRODUCT" - - - -@ask.on_purchase_completed( mapping={'payload': 'payload','name':'name','status':'status','token':'token'}) -def completed(payload, name, status, token): - products = Product(context.System.apiAccessToken) - logger.info('on-purchase-completed {}'.format( request)) - logger.info('payload: {} {}'.format(payload.purchaseResult, payload.productId)) - logger.info('name: {}'.format(name)) - logger.info('token: {}'.format(token)) - logger.info('status: {}'.format( status.code == 200)) - product_name = products.productName(payload.productId) - logger.info('Product name'.format(product_name)) - if status.code == '200' and ('ACCEPTED' in payload.purchaseResult): - return question('To listen it just say - play {} '.format(product_name)) - else: - return question('Do you want to buy another product?') - -@ask.launch -def launch(): - products = Product(context.System.apiAccessToken) - question_text = render_template('welcome', products=products.list()) - reprompt_text = render_template('welcome_reprompt') - return question(question_text).reprompt(reprompt_text).simple_card('Welcome', question_text) - - -@ask.intent('BuySkillItemIntent', mapping={'product_name': 'ProductName'}) -def buy_intent(product_name): - products = Product(context.System.apiAccessToken) - logger.info("PRODUCT: {}".format(product_name)) - buy_card = render_template('buy_card', product=product_name) - productId = products.productId(product_name) - if productId is not None: - session.attributes[PRODUCT_KEY] = productId - else: - return statement("I didn't find a product {}".format(product_name)) - raise NotImplementedError() - return buy(productId).simple_card('Welcome', question_text) - - #return upsell(product,'get this great product') - - -@ask.intent('RefundSkillItemIntent', mapping={'product_name': 'ProductName'}) -def refund_intent(product_name): - refund_card = render_template('refund_card') - logger.info("PRODUCT: {}".format(product_name)) - - products = Product(context.System.apiAccessToken) - productId = products.productId(product_name) - - if productId is not None: - session.attributes[PRODUCT_KEY] = productId - else: - raise NotImplementedError() - return refund(productId) - - -@ask.intent('AMAZON.FallbackIntent') -def fallback_intent(): - return statement("FallbackIntent") - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) - diff --git a/f-ask/samples/purchase/templates.yaml b/f-ask/samples/purchase/templates.yaml deleted file mode 100644 index 7b3d637..0000000 --- a/f-ask/samples/purchase/templates.yaml +++ /dev/null @@ -1,21 +0,0 @@ -welcome: | - Welcome to the Flask-ask purchase demo. - {% if products %} - Here is a list of products available: - {%for product in products%} - {{ product.name}}, - {%endfor %} - Please tell me the product name you want to buy. - {%else%} - You have no products configured. Please configure products using ASK CLI. - {%endif%} - - -welcome_reprompt: Please tell me the product name you want to buy. - -refund_card: | - Refund Intent for {{product}} - - -buy_card: | - Buy Intent for {{product}} diff --git a/f-ask/samples/reddit/main.py b/f-ask/samples/reddit/main.py deleted file mode 100644 index 1cb6e8f..0000000 --- a/f-ask/samples/reddit/main.py +++ /dev/null @@ -1,61 +0,0 @@ -import logging -import os -from flask import Flask -from flask_ask import Ask, request, session, question, statement -import random -import yaml - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -@ask.launch -def launch(): - return question("Each excercise will take about 5 minutes. Are you Ready?") - - -def get_workoutplan(): - with open("C:/Users/John/Desktop/GST/samples/reddit/workouts.yaml", 'r') as stream: - exercises = yaml.load(stream) - exercise_names = random.sample(list(exercises), 5) - workout_plan = [] - for exercise_name in exercise_names: - exercise = str(exercises[exercise_name]["duration"][0]) + " " + str(exercises[exercise_name]["duration"][1] )+ " of " + exercise_name - workout_plan.append(exercise) - return workout_plan - - -@ask.intent('YesIntent', mapping={'part': 'Part'}) -def start_workout(part): - print(part) - workout_plan = get_workoutplan() - response = "Do " - - for excercise in workout_plan[:-1]: - response += excercise + ", " - response += " and " + workout_plan[-1] - print(response) - return statement(response) - -@ask.intent('NoIntent') -def no(): - return statement("Allright, maybe later.") - -@ask.intent('AMAZON.HelpIntent') -def help(): - speech_text = 'If you want a quick workout just say "workout"' - return statement(speech_text) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run() diff --git a/f-ask/samples/reddit/speech_assets/IntentSchema.json b/f-ask/samples/reddit/speech_assets/IntentSchema.json deleted file mode 100644 index bd9db8d..0000000 --- a/f-ask/samples/reddit/speech_assets/IntentSchema.json +++ /dev/null @@ -1,50 +0,0 @@ - -{ - "interactionModel": { - "languageModel": { - "invocationName": "give me a workout", - "intents": [ - { - "name": "AMAZON.CancelIntent", - "samples": [] - }, - { - "name": "AMAZON.HelpIntent", - "samples": [] - }, - { - "name": "AMAZON.StopIntent", - "samples": [] - }, - { - "name": "YesIntent", - "slots": [], - "samples": [ - "yes" - ] - }, - { - "name": "NoIntent", - "slots": [], - "samples": [ - "no" - ] - }, - { - "name": "HelloWorldIntent", - "slots": [], - "samples": [ - "let's workout", - "give me todays workout", - "what's todays workout" - ] - }, - { - "name": "AMAZON.NavigateHomeIntent", - "samples": [] - } - ], - "types": [] - } - } -} \ No newline at end of file diff --git a/f-ask/samples/reddit/speech_assets/SampleUtterances.txt b/f-ask/samples/reddit/speech_assets/SampleUtterances.txt deleted file mode 100644 index d9f178e..0000000 --- a/f-ask/samples/reddit/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,7 +0,0 @@ -HelloWorldIntent say hello -HelloWorldIntent say hello world -HelloWorldIntent hello -HelloWorldIntent say hi -HelloWorldIntent say hi world -HelloWorldIntent hi -HelloWorldIntent how are you diff --git a/f-ask/samples/reddit/speech_assets/customSlotTypes/NAME_OF_BODY_PARTS b/f-ask/samples/reddit/speech_assets/customSlotTypes/NAME_OF_BODY_PARTS deleted file mode 100644 index 451682c..0000000 --- a/f-ask/samples/reddit/speech_assets/customSlotTypes/NAME_OF_BODY_PARTS +++ /dev/null @@ -1,4 +0,0 @@ -Chest -Legs -Torso -Upper body diff --git a/f-ask/samples/reddit/test.py b/f-ask/samples/reddit/test.py deleted file mode 100644 index 6acd029..0000000 --- a/f-ask/samples/reddit/test.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging -import os -from flask import Flask -from flask_ask import Ask, request, session, question, statement -import random -import yaml - -def read_exercises(): - with open("workouts.yaml", 'r') as stream: - exercises = yaml.load(stream) - exercise_names = random.sample(list(exercises), 5) - workout_plan = [] - for exercise_name in exercise_names: - exercise = str(exercises[exercise_name]["duration"][0]) + " " + str(exercises[exercise_name]["duration"][1] )+ " of " + exercise_name - workout_plan.append(exercise) - print(exercise) - - - - -read_exercises() \ No newline at end of file diff --git a/f-ask/samples/reddit/workouts.yaml b/f-ask/samples/reddit/workouts.yaml deleted file mode 100644 index 7a11123..0000000 --- a/f-ask/samples/reddit/workouts.yaml +++ /dev/null @@ -1,55 +0,0 @@ ---- - -Run in place: - duration: - - 30 - - seconds - explaination: "just run in place... dummy" - -Jumping jacks: - duration: - - 30 - - repetitions - explaination: "act like your Patric Star jumping of joy" - -Burpees: - duration: - - 10 - - repetitions - explaination: "do a push-up, then stand up and do a little jump" - -Push-ups: - duration: - - 10 - - repetitions - explaination: "lie on the ground, with your arms besides your toso. Angle them at 45° and push." - -Mountain climbers: - duration: - - 20 - - repetitions - explaination: "get in the upwards push-up position and move alternating knees to your chest" - -Jump lunges: - duration: - - 15 - - repetitions - explaination: "Walk in a way that your knees almost touch the ground" - -Jump squats: - duration: - - 15 - - repetitions - explaination: "jump and pull ypur knees to your chest" - -Bicycle crunches: - duration: - - 20 - - repetitions - explaination: "lay on your back with your hands behind your head, move your ellenbow to your opposite knee" - -Squats: - duration: - - 15 - - repetitions - explaination: "you know what a squat is, mind the 90°" \ No newline at end of file diff --git a/f-ask/samples/session/session.py b/f-ask/samples/session/session.py deleted file mode 100644 index 0cd3927..0000000 --- a/f-ask/samples/session/session.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -import os - -from flask import Flask, json, render_template -from flask_ask import Ask, request, session, question, statement - - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -COLOR_KEY = "COLOR" - - -@ask.launch -def launch(): - card_title = render_template('card_title') - question_text = render_template('welcome') - reprompt_text = render_template('welcome_reprompt') - return question(question_text).reprompt(reprompt_text).simple_card(card_title, question_text) - - -@ask.intent('MyColorIsIntent', mapping={'color': 'Color'}) -def my_color_is(color): - card_title = render_template('card_title') - if color is not None: - session.attributes[COLOR_KEY] = color - question_text = render_template('known_color', color=color) - reprompt_text = render_template('known_color_reprompt') - else: - question_text = render_template('unknown_color') - reprompt_text = render_template('unknown_color_reprompt') - return question(question_text).reprompt(reprompt_text).simple_card(card_title, question_text) - - -@ask.intent('WhatsMyColorIntent') -def whats_my_color(): - card_title = render_template('card_title') - color = session.attributes.get(COLOR_KEY) - if color is not None: - statement_text = render_template('known_color_bye', color=color) - return statement(statement_text).simple_card(card_title, statement_text) - else: - question_text = render_template('unknown_color_reprompt') - return question(question_text).reprompt(question_text).simple_card(card_title, question_text) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) - diff --git a/f-ask/samples/session/speech_assets/IntentSchema.json b/f-ask/samples/session/speech_assets/IntentSchema.json deleted file mode 100644 index a8e06f2..0000000 --- a/f-ask/samples/session/speech_assets/IntentSchema.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "intents": [ - { - "intent": "MyColorIsIntent", - "slots": [ - { - "name": "Color", - "type": "LIST_OF_COLORS" - } - ] - }, - { - "intent": "WhatsMyColorIntent" - } - ] -} diff --git a/f-ask/samples/session/speech_assets/SampleUtterances.txt b/f-ask/samples/session/speech_assets/SampleUtterances.txt deleted file mode 100644 index 6d4ae18..0000000 --- a/f-ask/samples/session/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,11 +0,0 @@ -MyColorIsIntent my color is {Color} -MyColorIsIntent my favorite color is {Color} -WhatsMyColorIntent whats my color -WhatsMyColorIntent what is my color -WhatsMyColorIntent say my color -WhatsMyColorIntent tell me my color -WhatsMyColorIntent whats my favorite color -WhatsMyColorIntent what is my favorite color -WhatsMyColorIntent say my favorite color -WhatsMyColorIntent tell me my favorite color -WhatsMyColorIntent tell me what my favorite color is diff --git a/f-ask/samples/session/speech_assets/customSlotTypes/LIST_OF_COLORS b/f-ask/samples/session/speech_assets/customSlotTypes/LIST_OF_COLORS deleted file mode 100644 index f9e6bcf..0000000 --- a/f-ask/samples/session/speech_assets/customSlotTypes/LIST_OF_COLORS +++ /dev/null @@ -1,6 +0,0 @@ -green -blue -purple -red -orange -yellow diff --git a/f-ask/samples/session/templates.yaml b/f-ask/samples/session/templates.yaml deleted file mode 100644 index 454528f..0000000 --- a/f-ask/samples/session/templates.yaml +++ /dev/null @@ -1,21 +0,0 @@ -welcome: | - Welcome to the Alexa Skills Kit sample. Please tell me your favorite color by - saying, my favorite color is red - -welcome_reprompt: Please tell me your favorite color by saying, my favorite color is red - -known_color: | - I now know that your favorite color is {{ color }}. You can ask me your favorite color - by saying, what's my favorite color? - -known_color_reprompt: You can ask me your favorite color by saying, what's my favorite color? - -known_color_bye: Your favorite color is {{ color }}. Goodbye - -unknown_color: I'm not sure what your favorite color is, please try again - -unknown_color_reprompt: | - I'm not sure what your favorite color is. You can tell me your favorite color by saying, - my favorite color is red - -card_title: Session diff --git a/f-ask/samples/spacegeek/spacegeek.py b/f-ask/samples/spacegeek/spacegeek.py deleted file mode 100644 index 4f2153f..0000000 --- a/f-ask/samples/spacegeek/spacegeek.py +++ /dev/null @@ -1,56 +0,0 @@ -import logging -import os -from random import randint - -from flask import Flask, render_template -from flask_ask import Ask, request, session, question, statement - - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -@ask.launch -def launch(): - return get_new_fact() - - -@ask.intent('GetNewFactIntent') -def get_new_fact(): - num_facts = 13 # increment this when adding a new fact template - fact_index = randint(0, num_facts-1) - fact_text = render_template('space_fact_{}'.format(fact_index)) - card_title = render_template('card_title') - return statement(fact_text).simple_card(card_title, fact_text) - - -@ask.intent('AMAZON.HelpIntent') -def help(): - help_text = render_template('help') - return question(help_text).reprompt(help_text) - - -@ask.intent('AMAZON.StopIntent') -def stop(): - bye_text = render_template('bye') - return statement(bye_text) - - -@ask.intent('AMAZON.CancelIntent') -def cancel(): - bye_text = render_template('bye') - return statement(bye_text) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) diff --git a/f-ask/samples/spacegeek/speech_assets/IntentSchema.json b/f-ask/samples/spacegeek/speech_assets/IntentSchema.json deleted file mode 100644 index dfdf9ba..0000000 --- a/f-ask/samples/spacegeek/speech_assets/IntentSchema.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "intents": [ - { - "intent": "GetNewFactIntent" - }, - { - "intent": "AMAZON.HelpIntent" - }, - { - "intent": "AMAZON.StopIntent" - }, - { - "intent": "AMAZON.CancelIntent" - } - ] -} diff --git a/f-ask/samples/spacegeek/speech_assets/SampleUtterances.txt b/f-ask/samples/spacegeek/speech_assets/SampleUtterances.txt deleted file mode 100644 index 9d3e6c6..0000000 --- a/f-ask/samples/spacegeek/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,14 +0,0 @@ -GetNewFactIntent a fact -GetNewFactIntent a space fact -GetNewFactIntent tell me a fact -GetNewFactIntent tell me a space fact -GetNewFactIntent give me a fact -GetNewFactIntent give me a space fact -GetNewFactIntent tell me trivia -GetNewFactIntent tell me a space trivia -GetNewFactIntent give me trivia -GetNewFactIntent give me a space trivia -GetNewFactIntent give me some information -GetNewFactIntent give me some space information -GetNewFactIntent tell me something -GetNewFactIntent give me something diff --git a/f-ask/samples/spacegeek/templates.yaml b/f-ask/samples/spacegeek/templates.yaml deleted file mode 100644 index 930ea0b..0000000 --- a/f-ask/samples/spacegeek/templates.yaml +++ /dev/null @@ -1,16 +0,0 @@ -space_fact_0: A year on Mercury is just 88 days long. -space_fact_1: Despite being farther from the Sun, Venus experiences higher temperatures than Mercury. -space_fact_2: Venus rotates counter-clockwise, possibly because of a collision in the past with an asteroid. -space_fact_3: On Mars, the Sun appears about half the size as it does on Earth. -space_fact_4: Earth is the only planet not named after a god. -space_fact_5: Jupiter has the shortest day of all the planets. -space_fact_6: The Milky Way galaxy will collide with the Andromeda Galaxy in about 5 billion years. -space_fact_7: The Sun contains 99.86% of the mass in the Solar System. -space_fact_8: The Sun is an almost perfect sphere. -space_fact_9: A total solar eclipse can happen once every 1 to 2 years. This makes them a rare event. -space_fact_10: Saturn radiates two and a half times more energy into space than it receives from the sun. -space_fact_11: The temperature inside the Sun can reach 15 million degrees Celsius. -space_fact_12: The Moon is moving approximately 3.8 cm away from our planet every year. -card_title: SpaceGeek -help: You can ask Space Geek tell me a space fact, or, you can say exit. What can I help you with? -bye: Goodbye diff --git a/f-ask/samples/tidepooler/speech_assets/IntentSchema.json b/f-ask/samples/tidepooler/speech_assets/IntentSchema.json deleted file mode 100644 index a428bdd..0000000 --- a/f-ask/samples/tidepooler/speech_assets/IntentSchema.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "intents": [ - { - "intent": "OneshotTideIntent", - "slots": [ - { - "name": "City", - "type": "LIST_OF_CITIES" - }, - { - "name": "State", - "type": "LIST_OF_STATES" - }, - { - "name": "Date", - "type": "AMAZON.DATE" - } - ] - }, - { - "intent": "DialogTideIntent", - "slots": [ - { - "name": "City", - "type": "LIST_OF_CITIES" - }, - { - "name": "State", - "type": "LIST_OF_STATES" - }, - { - "name": "Date", - "type": "AMAZON.DATE" - } - ] - }, - { - "intent": "SupportedCitiesIntent" - }, - { - "intent": "AMAZON.HelpIntent" - }, - { - "intent": "AMAZON.StopIntent" - }, - { - "intent": "AMAZON.CancelIntent" - } - ] -} diff --git a/f-ask/samples/tidepooler/speech_assets/SampleUtterances.txt b/f-ask/samples/tidepooler/speech_assets/SampleUtterances.txt deleted file mode 100644 index afee81d..0000000 --- a/f-ask/samples/tidepooler/speech_assets/SampleUtterances.txt +++ /dev/null @@ -1,92 +0,0 @@ -DialogTideIntent {City} -DialogTideIntent {City} {State} -DialogTideIntent {Date} - -OneshotTideIntent get high tide -OneshotTideIntent get high tide for {City} {State} -OneshotTideIntent get high tide for {City} {State} {Date} -OneshotTideIntent get high tide for {City} {Date} -OneshotTideIntent get high tide for {Date} -OneshotTideIntent get high tide {Date} -OneshotTideIntent get the high tide for {City} {Date} -OneshotTideIntent get the next tide for {City} for {Date} -OneshotTideIntent get the tides for {Date} -OneshotTideIntent get tide information for {City} -OneshotTideIntent get tide information for {City} {State} -OneshotTideIntent get tide information for {City} {State} on {Date} -OneshotTideIntent get tide information for {City} city -OneshotTideIntent get tide information for {City} for {Date} -OneshotTideIntent get tide information for {City} on {Date} -OneshotTideIntent get tide information for {City} {Date} -OneshotTideIntent get tides for {City} -OneshotTideIntent get tides for {City} {State} -OneshotTideIntent get tides for {City} {State} {Date} -OneshotTideIntent get tides for {City} {Date} -OneshotTideIntent tide information -OneshotTideIntent tide information for {City} -OneshotTideIntent tide information for {City} {State} -OneshotTideIntent tide information for {City} on {Date} -OneshotTideIntent tide information for {Date} -OneshotTideIntent when high tide is -OneshotTideIntent when is high tide -OneshotTideIntent when is high tide for {City} {Date} -OneshotTideIntent when is high tide in {City} -OneshotTideIntent when is high tide in {City} {State} -OneshotTideIntent when is high tide in {City} city -OneshotTideIntent when is high tide in {City} on {Date} -OneshotTideIntent when is high tide on {Date} -OneshotTideIntent when is high tide {Date} -OneshotTideIntent when is next tide -OneshotTideIntent when is next tide in {City} {State} -OneshotTideIntent when is next tide in {City} {State} on {Date} -OneshotTideIntent when is next tide on {Date} -OneshotTideIntent when is the highest tide in {City} -OneshotTideIntent when is the highest tide in {City} {Date} -OneshotTideIntent when is the highest tide {Date} -OneshotTideIntent when is the next high tide {Date} -OneshotTideIntent when is the next highest water -OneshotTideIntent when is the next highest water for {City} -OneshotTideIntent when is the next highest water for {City} {State} for {Date} -OneshotTideIntent when is the next highest water for {City} {State} -OneshotTideIntent when is the next highest water for {Date} -OneshotTideIntent when is the next tide for {City} -OneshotTideIntent when is the next tide for {City} {State} -OneshotTideIntent when is the next tide for {City} city -OneshotTideIntent when is the next tide for {City} for {Date} -OneshotTideIntent when is the next tide for {Date} -OneshotTideIntent when is today's high tide -OneshotTideIntent when is today's highest tide {Date} -OneshotTideIntent when will the water be highest for {City} -OneshotTideIntent when will the water be highest for {City} for {Date} -OneshotTideIntent when will the water be highest for {Date} -OneshotTideIntent when will the water be highest {Date} -OneshotTideIntent when is high tide on {Date} -OneshotTideIntent when is the next tide for {Date} -OneshotTideIntent when is next tide on {Date} -OneshotTideIntent get the tides for {Date} -OneshotTideIntent when is the highest tide {Date} -OneshotTideIntent when is the next highest water for {Date} -OneshotTideIntent when is high tide on {Date} -OneshotTideIntent get high tide for {Date} -OneshotTideIntent get high tide {Date} -OneshotTideIntent when is high tide {Date} -OneshotTideIntent tide information for {Date} -OneshotTideIntent when is high tide in {City} on {Date} -OneshotTideIntent get the next tide for {City} for {Date} -OneshotTideIntent get high tide for {City} California {Date} -OneshotTideIntent when is high tide for {City} {Date} -OneshotTideIntent tide information for {City} on {Date} -OneshotTideIntent when is high tide in {City} on {Date} -OneshotTideIntent when is the next tide for {City} for {Date} -OneshotTideIntent when is next tide in {City} {State} on {Date} -OneshotTideIntent get high tide for {City} {Date} -OneshotTideIntent get the high tide for {City} {Date} -OneshotTideIntent tide information for {City} on {Date} -OneshotTideIntent get tide information for {City} on {Date} -OneshotTideIntent get tides for {City} {Date} - -SupportedCitiesIntent what cities -SupportedCitiesIntent what cities are supported -SupportedCitiesIntent which cities are supported -SupportedCitiesIntent which cities -SupportedCitiesIntent which cities do you know \ No newline at end of file diff --git a/f-ask/samples/tidepooler/speech_assets/customSlotTypes/LIST_OF_CITIES b/f-ask/samples/tidepooler/speech_assets/customSlotTypes/LIST_OF_CITIES deleted file mode 100644 index 63eb710..0000000 --- a/f-ask/samples/tidepooler/speech_assets/customSlotTypes/LIST_OF_CITIES +++ /dev/null @@ -1,17 +0,0 @@ -seattle -los angeles -monterey -san diego -san francisco -boston -new york -miami -wilmington -tampa -galveston -morehead -new orleans -beaufort -myrtle beach -virginia beach -charleston \ No newline at end of file diff --git a/f-ask/samples/tidepooler/speech_assets/customSlotTypes/LIST_OF_STATES b/f-ask/samples/tidepooler/speech_assets/customSlotTypes/LIST_OF_STATES deleted file mode 100644 index 3a8b3bb..0000000 --- a/f-ask/samples/tidepooler/speech_assets/customSlotTypes/LIST_OF_STATES +++ /dev/null @@ -1,10 +0,0 @@ -california -florida -louisiana -massachusetts -new york -north carolina -south carolina -texas -virginia -washington \ No newline at end of file diff --git a/f-ask/samples/tidepooler/templates.yaml b/f-ask/samples/tidepooler/templates.yaml deleted file mode 100644 index 3ea2456..0000000 --- a/f-ask/samples/tidepooler/templates.yaml +++ /dev/null @@ -1,41 +0,0 @@ -welcome: | - - Welcome to Tide Pooler. - - -tide_info: | - {{ date | humanize_date }} in {{ city }}, the first high tide will be around - {{ tideinfo.first_high_tide_time | humanize_time }}, and will peak at about - {{ tideinfo.first_high_tide_height | humanize_height }}, followed by a low tide around - {{ tideinfo.low_tide_time | humanize_time }}, that will be about - {{ tideinfo.low_tide_height | humanize_height }}. - - The second high tide will be around {{ tideinfo.second_high_tide_time | humanize_time }}, - and will peak at about {{ tideinfo.second_high_tide_height | humanize_height }} - -help: | - I can lead you through providing a city and day of the week to get tide information, or you can simply open - Tide Pooler and ask a question like, get tide information for Seattle on Saturday. For a list of supported - cities, ask what cities are supported. Which city would you like tide information for? - -list_cities: | - Currently, I know tide information for these coastal cities: {{ cities }} - Which city would you like tide information for? - -list_cities_reprompt: Which city would you like tide information for? - -city_dialog: For which city would you like tide information for {{ date | humanize_date }} - -city_dialog_reprompt: For which city? - -date_dialog: For which date would you like tide information for {{ city }}? - -date_dialog_reprompt: For which date? - -date_dialog2: Please try again saying a day of the week, for example, Saturday - -noaa_problem: Sorry, the National Oceanic tide service is experiencing a problem. Please try again later. - -bye: Goodbye diff --git a/f-ask/samples/tidepooler/tidepooler.py b/f-ask/samples/tidepooler/tidepooler.py deleted file mode 100644 index e051b58..0000000 --- a/f-ask/samples/tidepooler/tidepooler.py +++ /dev/null @@ -1,298 +0,0 @@ -import os -import logging -import datetime -import math -import re -from six.moves.urllib.request import urlopen -from six.moves.urllib.parse import urlencode - -import aniso8601 -from flask import Flask, json, render_template -from flask_ask import Ask, request, session, question, statement - - -ENDPOINT = "http://tidesandcurrents.noaa.gov/api/datagetter" -SESSION_CITY = "city" -SESSION_DATE = "date" - -# NOAA station codes -STATION_CODE_SEATTLE = "9447130" -STATION_CODE_SAN_FRANCISCO = "9414290" -STATION_CODE_MONTEREY = "9413450" -STATION_CODE_LOS_ANGELES = "9410660" -STATION_CODE_SAN_DIEGO = "9410170" -STATION_CODE_BOSTON = "8443970" -STATION_CODE_NEW_YORK = "8518750" -STATION_CODE_VIRGINIA_BEACH = "8638863" -STATION_CODE_WILMINGTON = "8658163" -STATION_CODE_CHARLESTON = "8665530" -STATION_CODE_BEAUFORT = "8656483" -STATION_CODE_MYRTLE_BEACH = "8661070" -STATION_CODE_MIAMI = "8723214" -STATION_CODE_TAMPA = "8726667" -STATION_CODE_NEW_ORLEANS = "8761927" -STATION_CODE_GALVESTON = "8771341" - -STATIONS = {} -STATIONS["seattle"] = STATION_CODE_SEATTLE -STATIONS["san francisco"] = STATION_CODE_SAN_FRANCISCO -STATIONS["monterey"] = STATION_CODE_MONTEREY -STATIONS["los angeles"] = STATION_CODE_LOS_ANGELES -STATIONS["san diego"] = STATION_CODE_SAN_DIEGO -STATIONS["boston"] = STATION_CODE_BOSTON -STATIONS["new york"] = STATION_CODE_NEW_YORK -STATIONS["virginia beach"] = STATION_CODE_VIRGINIA_BEACH -STATIONS["wilmington"] = STATION_CODE_WILMINGTON -STATIONS["charleston"] = STATION_CODE_CHARLESTON -STATIONS["beaufort"] = STATION_CODE_BEAUFORT -STATIONS["myrtle beach"] = STATION_CODE_MYRTLE_BEACH -STATIONS["miami"] = STATION_CODE_MIAMI -STATIONS["tampa"] = STATION_CODE_TAMPA -STATIONS["new orleans"] = STATION_CODE_NEW_ORLEANS -STATIONS["galveston"] = STATION_CODE_GALVESTON - - -app = Flask(__name__) -ask = Ask(app, "/") -logging.getLogger('flask_ask').setLevel(logging.DEBUG) - - -class TideInfo(object): - - def __init__(self): - self.first_high_tide_time = None - self.first_high_tide_height = None - self.low_tide_time = None - self.low_tide_height = None - self.second_high_tide_time = None - self.second_high_tide_height = None - - -@ask.launch -def launch(): - welcome_text = render_template('welcome') - help_text = render_template('help') - return question(welcome_text).reprompt(help_text) - - -@ask.intent('OneshotTideIntent', - mapping={'city': 'City', 'date': 'Date'}, - convert={'date': 'date'}, - default={'city': 'seattle', 'date': datetime.date.today }) -def one_shot_tide(city, date): - if city.lower() not in STATIONS: - return supported_cities() - return _make_tide_request(city, date) - - -@ask.intent('DialogTideIntent', - mapping={'city': 'City', 'date': 'Date'}, - convert={'date': 'date'}) -def dialog_tide(city, date): - if city is not None: - if city.lower() not in STATIONS: - return supported_cities() - if SESSION_DATE not in session.attributes: - session.attributes[SESSION_CITY] = city - return _dialog_date(city) - date = aniso8601.parse_date(session.attributes[SESSION_DATE]) - return _make_tide_request(city, date) - elif date is not None: - if SESSION_CITY not in session.attributes: - session.attributes[SESSION_DATE] = date.isoformat() - return _dialog_city(date) - city = session.attributes[SESSION_CITY] - return _make_tide_request(city, date) - else: - return _dialog_no_slot() - - -@ask.intent('SupportedCitiesIntent') -def supported_cities(): - cities = ", ".join(sorted(STATIONS.keys())) - list_cities_text = render_template('list_cities', cities=cities) - list_cities_reprompt_text = render_template('list_cities_reprompt') - return question(list_cities_text).reprompt(list_cities_reprompt_text) - - -@ask.intent('AMAZON.HelpIntent') -def help(): - help_text = render_template('help') - list_cities_reprompt_text = render_template('list_cities_reprompt') - return question(help_text).reprompt(list_cities_reprompt_text) - - -@ask.intent('AMAZON.StopIntent') -def stop(): - bye_text = render_template('bye') - return statement(bye_text) - - -@ask.intent('AMAZON.CancelIntent') -def cancel(): - bye_text = render_template('bye') - return statement(bye_text) - - -@ask.session_ended -def session_ended(): - return "{}", 200 - - -@app.template_filter() -def humanize_date(dt): - # http://stackoverflow.com/a/20007730/1163855 - ordinal = lambda n: "%d%s" % (n,"tsnrhtdd"[(n/10%10!=1)*(n%10<4)*n%10::4]) - month_and_day_of_week = dt.strftime('%A %B') - day_of_month = ordinal(dt.day) - year = dt.year if dt.year != datetime.datetime.now().year else "" - formatted_date = "{} {} {}".format(month_and_day_of_week, day_of_month, year) - formatted_date = re.sub('\s+', ' ', formatted_date) - return formatted_date - - -@app.template_filter() -def humanize_time(dt): - morning_threshold = 12 - afternoon_threshold = 17 - evening_threshold = 20 - hour_24 = dt.hour - if hour_24 < morning_threshold: - period_of_day = "in the morning" - elif hour_24 < afternoon_threshold: - period_of_day = "in the afternoon" - elif hour_24 < evening_threshold: - period_of_day = "in the evening" - else: - period_of_day = " at night" - the_time = dt.strftime('%I:%M') - formatted_time = "{} {}".format(the_time, period_of_day) - return formatted_time - - -@app.template_filter() -def humanize_height(height): - round_down_threshold = 0.25 - round_to_half_threshold = 0.75 - is_negative = False - if height < 0: - height = abs(height) - is_negative = True - remainder = height % 1 - if remainder < round_down_threshold: - remainder_text = "" - feet = int(math.floor(height)) - elif remainder < round_to_half_threshold: - remainder_text = "and a half" - feet = int(math.floor(height)) - else: - remainder_text = "" - feet = int(math.floor(height)) - if is_negative: - feet *= -1 - formatted_height = "{} {} feet".format(feet, remainder_text) - formatted_height = re.sub('\s+', ' ', formatted_height) - return formatted_height - - -def _dialog_no_slot(): - if SESSION_CITY in session.attributes: - date_dialog2_text = render_template('date_dialog2') - return question(date_dialog2_text).reprompt(date_dialog2_text) - else: - return supported_cities() - - -def _dialog_date(city): - date_dialog_text = render_template('date_dialog', city=city) - date_dialog_reprompt_text = render_template('date_dialog_reprompt') - return question(date_dialog_text).reprompt(date_dialog_reprompt_text) - - -def _dialog_city(date): - session.attributes[SESSION_DATE] = date - session.attributes_encoder = _json_date_handler - city_dialog_text = render_template('city_dialog', date=date) - city_dialog_reprompt_text = render_template('city_dialog_reprompt') - return question(city_dialog_text).reprompt(city_dialog_reprompt_text) - - -def _json_date_handler(obj): - if isinstance(obj, datetime.date): - return obj.isoformat() - - -def _make_tide_request(city, date): - station = STATIONS.get(city.lower()) - noaa_api_params = { - 'station': station, - 'product': 'predictions', - 'datum': 'MLLW', - 'units': 'english', - 'time_zone': 'lst_ldt', - 'format': 'json' - } - if date == datetime.date.today(): - noaa_api_params['date'] = 'today' - else: - noaa_api_params['begin_date'] = date.strftime('%Y%m%d') - noaa_api_params['range'] = 24 - url = ENDPOINT + "?" + urlencode(noaa_api_params) - resp_body = urlopen(url).read() - if len(resp_body) == 0: - statement_text = render_template('noaa_problem') - else: - noaa_response_obj = json.loads(resp_body) - predictions = noaa_response_obj['predictions'] - tideinfo = _find_tide_info(predictions) - statement_text = render_template('tide_info', date=date, city=city, tideinfo=tideinfo) - return statement(statement_text).simple_card("Tide Pooler", statement_text) - - -def _find_tide_info(predictions): - """ - Algorithm to find the 2 high tides for the day, the first of which is smaller and occurs - mid-day, the second of which is larger and typically in the evening. - """ - - last_prediction = None - first_high_tide = None - second_high_tide = None - low_tide = None - first_tide_done = False - for prediction in predictions: - if last_prediction is None: - last_prediction = prediction - continue - if last_prediction['v'] < prediction['v']: - if not first_tide_done: - first_high_tide = prediction - else: - second_high_tide = prediction - else: # we're decreasing - if not first_tide_done and first_high_tide is not None: - first_tide_done = True - elif second_high_tide is not None: - break # we're decreasing after having found the 2nd tide. We're done. - if first_tide_done: - low_tide = prediction - last_prediction = prediction - - fmt = '%Y-%m-%d %H:%M' - parse = datetime.datetime.strptime - tideinfo = TideInfo() - tideinfo.first_high_tide_time = parse(first_high_tide['t'], fmt) - tideinfo.first_high_tide_height = float(first_high_tide['v']) - tideinfo.second_high_tide_time = parse(second_high_tide['t'], fmt) - tideinfo.second_high_tide_height = float(second_high_tide['v']) - tideinfo.low_tide_time = parse(low_tide['t'], fmt) - tideinfo.low_tide_height = float(low_tide['v']) - return tideinfo - - -if __name__ == '__main__': - if 'ASK_VERIFY_REQUESTS' in os.environ: - verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower() - if verify == 'false': - app.config['ASK_VERIFY_REQUESTS'] = False - app.run(debug=True) diff --git a/f-ask/setup.cfg b/f-ask/setup.cfg deleted file mode 100644 index 1eee7db..0000000 --- a/f-ask/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -universal = 1 - -[metadata] -description-file = README.md diff --git a/f-ask/setup.py b/f-ask/setup.py deleted file mode 100644 index 17031ea..0000000 --- a/f-ask/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Flask-Ask -------------- - -Easy Alexa Skills Kit integration for Flask -""" -from setuptools import setup - -def parse_requirements(filename): - """ load requirements from a pip requirements file """ - lineiter = (line.strip() for line in open(filename)) - return [line for line in lineiter if line and not line.startswith("#")] - -setup( - name='Flask-Ask', - version='0.9.7', - url='https://github.com/johnwheeler/flask-ask', - license='Apache 2.0', - author='John Wheeler', - author_email='john@johnwheeler.org', - description='Rapid Alexa Skills Kit Development for Amazon Echo Devices in Python', - long_description=__doc__, - packages=['flask_ask'], - zip_safe=False, - include_package_data=True, - platforms='any', - install_requires=parse_requirements('requirements.txt'), - test_requires=[ - 'mock', - 'requests' - ], - test_suite='tests', - classifiers=[ - 'License :: OSI Approved :: Apache Software License', - 'Framework :: Flask', - 'Programming Language :: Python', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'Operating System :: OS Independent', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - 'Topic :: Software Development :: Libraries :: Python Modules' - ] -) diff --git a/f-ask/tests/__init__.py b/f-ask/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/f-ask/tests/test_audio.py b/f-ask/tests/test_audio.py deleted file mode 100644 index 310d082..0000000 --- a/f-ask/tests/test_audio.py +++ /dev/null @@ -1,67 +0,0 @@ -import unittest -from mock import patch, MagicMock -from flask import Flask -from flask_ask import Ask, audio -from flask_ask.models import _Field - - -class AudioUnitTests(unittest.TestCase): - - def setUp(self): - self.ask_patcher = patch('flask_ask.core.find_ask', return_value=Ask()) - self.ask_patcher.start() - self.context_patcher = patch('flask_ask.models.context', return_value=MagicMock()) - self.context_patcher.start() - - def tearDown(self): - self.ask_patcher.stop() - self.context_patcher.stop() - - def test_token_generation(self): - """ Confirm we get a new token when setting a stream url """ - audio_item = audio()._audio_item(stream_url='https://fakestream', offset=123) - self.assertEqual(36, len(audio_item['stream']['token'])) - self.assertEqual(123, audio_item['stream']['offsetInMilliseconds']) - - def test_custom_token(self): - """ Check to see that the provided opaque token remains constant""" - token = "hello_world" - audio_item = audio()._audio_item(stream_url='https://fakestream', offset=10, opaque_token=token) - self.assertEqual(token, audio_item['stream']['token']) - self.assertEqual(10, audio_item['stream']['offsetInMilliseconds']) - - -class AskStreamHandlingTests(unittest.TestCase): - - def setUp(self): - fake_context = {'System': {'user': {'userId': 'dave'}}} - self.context_patcher = patch.object(Ask, 'context', return_value=fake_context) - self.context_patcher.start() - self.request_patcher = patch.object(Ask, 'request', return_value=MagicMock()) - self.request_patcher.start() - - def tearDown(self): - self.context_patcher.stop() - self.request_patcher.stop() - - def test_setting_and_getting_current_stream(self): - ask = Ask() - with patch('flask_ask.core.find_ask', return_value=ask): - self.assertEqual(_Field(), ask.current_stream) - - stream = _Field() - stream.__dict__.update({'token': 'asdf', 'offsetInMilliseconds': 123, 'url': 'junk'}) - with patch('flask_ask.core.top_stream', return_value=stream): - self.assertEqual(stream, ask.current_stream) - - def test_from_directive_call(self): - ask = Ask() - fake_stream = _Field() - fake_stream.__dict__.update({'token':'fake'}) - with patch('flask_ask.core.top_stream', return_value=fake_stream): - from_buffer = ask._from_directive() - self.assertEqual(fake_stream, from_buffer) - - -if __name__ == '__main__': - unittest.main() diff --git a/f-ask/tests/test_cache.py b/f-ask/tests/test_cache.py deleted file mode 100644 index 418dde8..0000000 --- a/f-ask/tests/test_cache.py +++ /dev/null @@ -1,60 +0,0 @@ -import unittest -from mock import patch, Mock -from werkzeug.contrib.cache import SimpleCache -from flask_ask.core import Ask -from flask_ask.cache import push_stream, pop_stream, top_stream, set_stream - - -class CacheTests(unittest.TestCase): - - def setUp(self): - self.patcher = patch('flask_ask.core.find_ask', return_value=Ask()) - self.ask = self.patcher.start() - self.user_id = 'dave' - self.token = '123-abc' - self.cache = SimpleCache() - - def tearDown(self): - self.patcher.stop() - - def test_adding_removing_stream(self): - self.assertTrue(push_stream(self.cache, self.user_id, self.token)) - - # peak at the top - self.assertEqual(self.token, top_stream(self.cache, self.user_id)) - self.assertIsNone(top_stream(self.cache, 'not dave')) - - # pop it off - self.assertEqual(self.token, pop_stream(self.cache, self.user_id)) - self.assertIsNone(top_stream(self.cache, self.user_id)) - - def test_pushing_works_like_a_stack(self): - push_stream(self.cache, self.user_id, 'junk') - push_stream(self.cache, self.user_id, self.token) - - self.assertEqual(self.token, pop_stream(self.cache, self.user_id)) - self.assertEqual('junk', pop_stream(self.cache, self.user_id)) - self.assertIsNone(pop_stream(self.cache, self.user_id)) - - def test_cannot_push_nones_into_stack(self): - self.assertIsNone(push_stream(self.cache, self.user_id, None)) - - def test_set_overrides_stack(self): - push_stream(self.cache, self.user_id, '1') - push_stream(self.cache, self.user_id, '2') - self.assertEqual('2', top_stream(self.cache, self.user_id)) - - set_stream(self.cache, self.user_id, '3') - self.assertEqual('3', pop_stream(self.cache, self.user_id)) - self.assertIsNone(pop_stream(self.cache, self.user_id)) - - def test_calls_to_top_with_no_user_return_none(self): - """ RedisCache implementation doesn't like None key values. """ - mock = Mock() - result = top_stream(mock, None) - self.assertFalse(mock.get.called) - self.assertIsNone(result) - - -if __name__ == '__main__': - unittest.main() diff --git a/f-ask/tests/test_core.py b/f-ask/tests/test_core.py deleted file mode 100644 index 6b424c4..0000000 --- a/f-ask/tests/test_core.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -import unittest -from aniso8601.timezone import UTCOffset, build_utcoffset -from flask_ask.core import Ask - -from datetime import datetime, timedelta -from mock import patch, MagicMock -import json - - -class FakeRequest(object): - """ Fake out a Flask request for testing purposes for now """ - - headers = {'Signaturecertchainurl': None, 'Signature': None} - - def __init__(self, data): - self.data = json.dumps(data) - - -class TestCoreRoutines(unittest.TestCase): - """ Tests for core Flask Ask functionality """ - - - def setUp(self): - self.mock_app = MagicMock() - self.mock_app.debug = True - self.mock_app.config = {'ASK_VERIFY_TIMESTAMP_DEBUG': False} - - # XXX: this mess implies we should think about tidying up Ask._alexa_request - self.patch_current_app = patch('flask_ask.core.current_app', new=self.mock_app) - self.patch_load_cert = patch('flask_ask.core.verifier.load_certificate') - self.patch_verify_sig = patch('flask_ask.core.verifier.verify_signature') - self.patch_current_app.start() - self.patch_load_cert.start() - self.patch_verify_sig.start() - - @patch('flask_ask.core.flask_request', - new=FakeRequest({'request': {'timestamp': 1234}, - 'session': {'application': {'applicationId': 1}}})) - def test_alexa_request_parsing(self): - ask = Ask() - ask._alexa_request() - - - def test_parse_timestamp(self): - utc = build_utcoffset('UTC', timedelta(hours=0)) - result = Ask._parse_timestamp('2017-07-08T07:38:00Z') - self.assertEqual(datetime(2017, 7, 8, 7, 38, 0, 0, utc), result) - - result = Ask._parse_timestamp(1234567890) - self.assertEqual(datetime(2009, 2, 13, 23, 31, 30), result) - - with self.assertRaises(ValueError): - Ask._parse_timestamp(None) - - def test_tries_parsing_on_valueerror(self): - max_timestamp = 253402300800 - - # should cause a ValueError normally - with self.assertRaises(ValueError): - datetime.utcfromtimestamp(max_timestamp) - - # should safely parse, assuming scale change needed - # note: this assert looks odd, but Py2 handles the parsing - # differently, resulting in a differing timestamp - # due to more granularity of microseconds - result = Ask._parse_timestamp(max_timestamp) - self.assertEqual(datetime(1978, 1, 11, 21, 31, 40).timetuple()[0:6], - result.timetuple()[0:6]) - - with self.assertRaises(ValueError): - # still raise an error if too large - Ask._parse_timestamp(max_timestamp * 1000) - - def tearDown(self): - self.patch_current_app.stop() - self.patch_load_cert.stop() - self.patch_verify_sig.stop() diff --git a/f-ask/tests/test_integration.py b/f-ask/tests/test_integration.py deleted file mode 100644 index 0c18c5a..0000000 --- a/f-ask/tests/test_integration.py +++ /dev/null @@ -1,122 +0,0 @@ -import unittest -import json -import uuid - -from flask_ask import Ask, audio -from flask import Flask - - -play_request = { - "version": "1.0", - "session": { - "new": True, - "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000", - "application": { - "applicationId": "fake-application-id" - }, - "attributes": {}, - "user": { - "userId": "amzn1.account.AM3B00000000000000000000000" - } - }, - "context": { - "System": { - "application": { - "applicationId": "fake-application-id" - }, - "user": { - "userId": "amzn1.account.AM3B00000000000000000000000" - }, - "device": { - "supportedInterfaces": { - "AudioPlayer": {} - } - } - }, - "AudioPlayer": { - "offsetInMilliseconds": 0, - "playerActivity": "IDLE" - } - }, - "request": { - "type": "IntentRequest", - "requestId": "string", - "timestamp": "string", - "locale": "string", - "intent": { - "name": "TestPlay", - "slots": { - } - } - } -} - - -class AudioIntegrationTests(unittest.TestCase): - """ Integration tests of the Audio Directives """ - - def setUp(self): - self.app = Flask(__name__) - self.app.config['ASK_VERIFY_REQUESTS'] = False - self.ask = Ask(app=self.app, route='/ask') - self.client = self.app.test_client() - self.stream_url = 'https://fakestream' - self.custom_token = 'custom_uuid_{0}'.format(str(uuid.uuid4())) - - @self.ask.intent('TestPlay') - def play(): - return audio('playing').play(self.stream_url) - - @self.ask.intent('TestCustomTokenIntents') - def custom_token_intents(): - return audio('playing with custom token').play(self.stream_url, - opaque_token=self.custom_token) - - def tearDown(self): - pass - - def test_play_intent(self): - """ Test to see if we can properly play a stream """ - response = self.client.post('/ask', data=json.dumps(play_request)) - self.assertEqual(200, response.status_code) - - data = json.loads(response.data.decode('utf-8')) - self.assertEqual('playing', - data['response']['outputSpeech']['text']) - - directive = data['response']['directives'][0] - self.assertEqual('AudioPlayer.Play', directive['type']) - - stream = directive['audioItem']['stream'] - self.assertIsNotNone(stream['token']) - self.assertEqual(self.stream_url, stream['url']) - self.assertEqual(0, stream['offsetInMilliseconds']) - - def test_play_intent_with_custom_token(self): - """ Test to check that custom token supplied is returned """ - - # change the intent name to route to our custom token for play_request - original_intent_name = play_request['request']['intent']['name'] - play_request['request']['intent']['name'] = 'TestCustomTokenIntents' - - response = self.client.post('/ask', data=json.dumps(play_request)) - self.assertEqual(200, response.status_code) - - data = json.loads(response.data.decode('utf-8')) - self.assertEqual('playing with custom token', - data['response']['outputSpeech']['text']) - - directive = data['response']['directives'][0] - self.assertEqual('AudioPlayer.Play', directive['type']) - - stream = directive['audioItem']['stream'] - self.assertEqual(stream['token'], self.custom_token) - self.assertEqual(self.stream_url, stream['url']) - self.assertEqual(0, stream['offsetInMilliseconds']) - - # reset our play_request - play_request['request']['intent']['name'] = original_intent_name - - -if __name__ == '__main__': - unittest.main() diff --git a/f-ask/tests/test_integration_support_entity_resolution.py b/f-ask/tests/test_integration_support_entity_resolution.py deleted file mode 100644 index 959e702..0000000 --- a/f-ask/tests/test_integration_support_entity_resolution.py +++ /dev/null @@ -1,114 +0,0 @@ -import unittest -import json -import uuid - -from flask_ask import Ask, statement -from flask import Flask - - -play_request = { - "version": "1.0", - "session": { - "new": False, - "sessionId": "amzn1.echo-api.session.f6ebc0ba-9d7a-4c3f-b056-b6c3f9da0713", - "application": { - "applicationId": "amzn1.ask.skill.26338c44-65da-4d58-aa75-c86b21271eb7" - }, - "user": { - "userId": "amzn1.ask.account.AHR7KBC3MFCX7LYT6HJBGDLIGQUU3FLANWCZ", - } - }, - "context": { - "AudioPlayer": { - "playerActivity": "IDLE" - }, - "Display": { - "token": "" - }, - "System": { - "application": { - "applicationId": "amzn1.ask.skill.26338c44-65da-4d58-aa75-c86b21271eb7" - }, - "user": { - "userId": "amzn1.ask.account.AHR7KBC3MFCX7LYT6HJBGDLIGQUU3FLANWCZ", - }, - "device": { - "deviceId": "amzn1.ask.device.AELNXV4JQJMF5QALYUQXHOZJ", - "supportedInterfaces": { - "AudioPlayer": {}, - "Display": { - "templateVersion": "1.0", - "markupVersion": "1.0" - } - } - }, - "apiEndpoint": "https://api.amazonalexa.com", - } - }, - "request": { - "type": "IntentRequest", - "requestId": "amzn1.echo-api.request.4859a7e3-1960-4ed9-ac7b-854309346916", - "timestamp": "2018-04-04T06:28:23Z", - "locale": "en-US", - "intent": { - "name": "TestCustomSlotTypeIntents", - "confirmationStatus": "NONE", - "slots": { - "child_info": { - "name": "child_info", - "value": "friends info", - "resolutions": { - "resolutionsPerAuthority": [ - { - "authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill.26338c44-65da-4d58-aa75-c86b21271eb7.child_info_type", - "status": { - "code": "ER_SUCCESS_MATCH" - }, - "values": [ - { - "value": { - "name": "friend_info", - "id": "FRIEND_INFO" - } - } - ] - } - ] - }, - "confirmationStatus": "NONE" - } - } - }, - "dialogState": "STARTED" - } -} - - -class CustomSlotTypeIntegrationTests(unittest.TestCase): - """ Integration tests of the custom slot type """ - - def setUp(self): - self.app = Flask(__name__) - self.app.config['ASK_VERIFY_REQUESTS'] = False - self.ask = Ask(app=self.app, route='/ask') - self.client = self.app.test_client() - - @self.ask.intent('TestCustomSlotTypeIntents') - def custom_slot_type_intents(child_info): - return statement(child_info) - - def tearDown(self): - pass - - def test_custom_slot_type_intent(self): - """ Test to see if custom slot type value is correct """ - response = self.client.post('/ask', data=json.dumps(play_request)) - self.assertEqual(200, response.status_code) - - data = json.loads(response.data.decode('utf-8')) - self.assertEqual('friend_info', - data['response']['outputSpeech']['text']) - - -if __name__ == '__main__': - unittest.main() diff --git a/f-ask/tests/test_samples.py b/f-ask/tests/test_samples.py deleted file mode 100644 index 4926029..0000000 --- a/f-ask/tests/test_samples.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Smoke test using the samples. -""" - -import unittest -import os -import six -import sys -import time -import subprocess - -from requests import post - -import flask_ask - - -launch = { - "version": "1.0", - "session": { - "new": True, - "sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000", - "application": { - "applicationId": "fake-application-id" - }, - "attributes": {}, - "user": { - "userId": "amzn1.account.AM3B00000000000000000000000" - } - }, - "context": { - "System": { - "application": { - "applicationId": "fake-application-id" - }, - "user": { - "userId": "amzn1.account.AM3B00000000000000000000000" - }, - "device": { - "supportedInterfaces": { - "AudioPlayer": {} - } - } - }, - "AudioPlayer": { - "offsetInMilliseconds": 0, - "playerActivity": "IDLE" - } - }, - "request": { - "type": "LaunchRequest", - "requestId": "string", - "timestamp": "string", - "locale": "string", - "intent": { - "name": "TestPlay", - "slots": { - } - } - } -} - - -project_root = os.path.abspath(os.path.join(flask_ask.__file__, '../..')) - - -@unittest.skipIf(six.PY2, "Not yet supported on Python 2.x") -class SmokeTestUsingSamples(unittest.TestCase): - """ Try launching each sample and sending some requests to them. """ - - def setUp(self): - self.python = sys.executable - self.env = {'PYTHONPATH': project_root, - 'ASK_VERIFY_REQUESTS': 'false'} - if os.name == 'nt': - self.env['SYSTEMROOT'] = os.getenv('SYSTEMROOT') - self.env['PATH'] = os.getenv('PATH') - - def _launch(self, sample): - prefix = os.path.join(project_root, 'samples/') - path = prefix + sample - process = subprocess.Popen([self.python, path], env=self.env) - time.sleep(1) - self.assertIsNone(process.poll(), - msg='Poll should work,' - 'otherwise we failed to launch') - self.process = process - - def _post(self, route='/', data={}): - url = 'http://127.0.0.1:5000' + str(route) - print('POSTing to %s' % url) - response = post(url, json=data) - self.assertEqual(200, response.status_code) - return response - - @staticmethod - def _get_text(http_response): - data = http_response.json() - return data.get('response', {})\ - .get('outputSpeech', {})\ - .get('text', None) - - @staticmethod - def _get_reprompt(http_response): - data = http_response.json() - return data.get('response', {})\ - .get('reprompt', {})\ - .get('outputSpeech', {})\ - .get('text', None) - - def tearDown(self): - try: - self.process.terminate() - self.process.communicate(timeout=1) - except Exception as e: - try: - print('[%s]...trying to kill.' % str(e)) - self.process.kill() - self.process.communicate(timeout=1) - except Exception as e: - print('Error killing test python process: %s' % str(e)) - print('*** it is recommended you manually kill with PID %s', - self.process.pid) - - def test_helloworld(self): - """ Test the HelloWorld sample project """ - self._launch('helloworld/helloworld.py') - response = self._post(data=launch) - self.assertTrue('hello' in self._get_text(response)) - - def test_session_sample(self): - """ Test the Session sample project """ - self._launch('session/session.py') - response = self._post(data=launch) - self.assertTrue('favorite color' in self._get_text(response)) - - def test_audio_simple_demo(self): - """ Test the SimpleDemo Audio sample project """ - self._launch('audio/simple_demo/ask_audio.py') - response = self._post(data=launch) - self.assertTrue('audio example' in self._get_text(response)) - - def test_audio_playlist_demo(self): - """ Test the Playlist Audio sample project """ - self._launch('audio/playlist_demo/playlist.py') - response = self._post(data=launch) - self.assertTrue('playlist' in self._get_text(response)) - - def test_blueprints_demo(self): - """ Test the sample project using Flask Blueprints """ - self._launch('blueprint_demo/demo.py') - response = self._post(route='/ask', data=launch) - self.assertTrue('hello' in self._get_text(response)) - - def test_history_buff(self): - """ Test the History Buff sample """ - self._launch('historybuff/historybuff.py') - response = self._post(data=launch) - self.assertTrue('History buff' in self._get_text(response)) - - def test_spacegeek(self): - """ Test the Spacegeek sample """ - self._launch('spacegeek/spacegeek.py') - response = self._post(data=launch) - # response is random - self.assertTrue(len(self._get_text(response)) > 1) - - def test_tidepooler(self): - """ Test the Tide Pooler sample """ - self._launch('tidepooler/tidepooler.py') - response = self._post(data=launch) - self.assertTrue('Which city' in self._get_reprompt(response)) diff --git a/f-ask/tests/test_unicode.py b/f-ask/tests/test_unicode.py deleted file mode 100644 index 0c1f60f..0000000 --- a/f-ask/tests/test_unicode.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -import unittest -from flask_ask import statement, question - - -class UnicodeTests(unittest.TestCase): - """ Test using Unicode in responses. (Issue #147) """ - - unicode_string = u"Was kann ich für dich tun?" - - def test_unicode_statements(self): - """ Test unicode statement responses """ - stmt = statement(self.unicode_string) - speech = stmt._response['outputSpeech']['text'] - print(speech) - self.assertTrue(self.unicode_string in speech) - - def test_unicode_questions(self): - """ Test unicode in question responses """ - q = question(self.unicode_string) - speech = q._response['outputSpeech']['text'] - self.assertTrue(self.unicode_string in speech) diff --git a/f-ask/tox.ini b/f-ask/tox.ini deleted file mode 100644 index 3c6a9c1..0000000 --- a/f-ask/tox.ini +++ /dev/null @@ -1,8 +0,0 @@ -[tox] -envlist = py27,py3 -skipsdist = True - -[testenv] -deps = -rrequirements-dev.txt -commands = python setup.py test -