This commit is contained in:
Patrice 2019-05-13 12:27:53 +02:00
parent 83fa1b7156
commit 16e1b97810
92 changed files with 2 additions and 6866 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
reader/ngrok\.exe

102
f-ask/.gitignore vendored
View File

@ -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

View File

View File

@ -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.

View File

@ -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

View File

@ -1 +0,0 @@
GST

View File

@ -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 <http://flask.pocoo.org/extensions/>`_ that makes building Alexa skills for the Amazon Echo easier and much more fun.
* `Flask-Ask quickstart on Amazon's Developer Blog <https://developer.amazon.com/public/community/post/Tx14R0IYYGH3SKT/Flask-Ask-A-New-Python-Framework-for-Rapid-Alexa-Skills-Kit-Development>`_.
* `Level Up with our Alexa Skills Kit Video Tutorial <https://alexatutorial.com/>`_
* `Chat on Gitter.im <https://gitter.im/johnwheeler/flask-ask/>`_
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 <https://github.com/johnwheeler/flask-ask/tree/master/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 <http://jinja.pocoo.org/docs/dev/api/#loaders>`_ 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 <https://github.com/johnwheeler/flask-ask/tree/master/samples/tidepooler>`_ 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 <https://www.youtube.com/watch?v=cXL8FDUag-s>`_
* `Full online documentation <https://alexatutorial.com/flask-ask/>`_
Fantastic 3-part tutorial series by Harrison Kinsley
* `Intro and Skill Logic - Alexa Skills w/ Python and Flask-Ask Part 1 <https://pythonprogramming.net/intro-alexa-skill-flask-ask-python-tutorial/>`_
* `Headlines Function - Alexa Skills w/ Python and Flask-Ask Part 2 <https://pythonprogramming.net/headlines-function-alexa-skill-flask-ask-python-tutorial/>`_
* `Testing our Skill - Alexa Skills w/ Python and Flask-Ask Part 3 <https://pythonprogramming.net/testing-deploying-alexa-skill-flask-ask-python-tutorial/>`_
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 <https://github.com/johnwheeler/flask-live-starter>`_.
To deploy on AWS Lambda, you have two options. Use `Zappa <https://github.com/Miserlou/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 <https://developer.amazon.com/blogs/post/8e8ad73a-99e9-4c0f-a7b3-60f92287b0bf/new-alexa-tutorial-deploy-flask-ask-skills-to-aws-lambda-with-zappa>`_ 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 <https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html>`_ your Lambda function yourself and use `virtualenv <http://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html>`_ 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 <https://github.com/chrisvoncsefalvay/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 <https://github.com/treethought/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 <https://github.com/johnwheeler/flask-ask/issues>`_ so we can make Flask-Ask better.
Special thanks to `@kennethreitz <https://github.com/kennethreitz>`_ for his `sense <http://docs.python-requests.org/en/master/>`_ of `style <https://github.com/kennethreitz/records/blob/master/README.rst>`_, and of course, `@mitsuhiko <https://github.com/mitsuhiko>`_ for `Flask <https://www.palletsprojects.com/p/flask/>`_

View File

@ -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 <target>' where <target> 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."

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,31 +0,0 @@
<br>
<h3>Resources</h3>
<ul>
<li>
<a href="https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference">ASK JSON Reference</a>
</li>
<li>
<a href="https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/speech-synthesis-markup-language-ssml-reference">ASK SSML Reference</a>
</li>
<li>
<a href="https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference">ASK Interaction Model Reference</a>
</li>
</ul>
<ul>
<li><a href="http://flask.readthedocs.io/en/latest/">Flask Documentation</a></li>
<li><a href="http://jinja2.readthedocs.io/en/latest/">Jinja2 Documentation</a></li>
</ul>
<br>
<h3>Project Links</h3>
<ul>
<li><a href="http://pypi.python.org/pypi/flask-ask">Flask-Ask @ PyPI</a></li>
<li><a href="http://github.com/johnwheeler/flask-ask">Flask-Ask @ GitHub</a></li>
<li><a href="http://github.com/johnwheeler/flask-ask/issues">Issue Tracker</a></li>
</ul>
<br>

View File

@ -1,9 +0,0 @@
<p class="logo">
<a href="{{ pathto(master_doc) }}">
<img src="{{ pathto('_static/logo-sm.png', 1) }}" alt="Logo">
</a>
<p>
<small>
Alexa Skills Kit Development for Amazon Echo Devices with Python
</small>

View File

@ -1,20 +0,0 @@
<h3>Stay Informed</h3>
<a class="github-button" href="https://github.com/johnwheeler/flask-ask" data-icon="octicon-star" data-style="mega" data-count-href="/johnwheeler/flask-ask/stargazers" data-count-api="/repos/johnwheeler/flask-ask#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star johnwheeler/flask-ask on GitHub">Star</a>
<br>
<p>
<small>Receive updates on new releases and upcoming projects.</small>
</p>
<p>
<a class="github-button" href="https://github.com/johnwheeler" aria-label="Follow @johnwheeler on GitHub">Follow @johnwheeler</a>
</p>
<p>
<a class="twitter-follow-button" data-show-count="false" href="https://twitter.com/_johnwheeler">Follow @_johnwheeler</a>
</p>
<br>

View File

@ -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.

View File

@ -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

View File

@ -1,36 +0,0 @@
{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
<!-- Twitter Buttons -->
<script>
window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
t._e = [];
t.ready = function(f) {
t._e.push(f);
};
return t;
}(document, "script", "twitter-wjs"));
</script>
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
{% if theme_touch_icon %}
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" /> {% endif %} {% endblock %} {%- block relbar2 %} {% if theme_github_fork %}
<a href="http://github.com/{{ theme_github_fork }}">
<img style="position: fixed; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" />
</a>
{% endif %} {% endblock %} {%- block footer %}
<div class="footer">
&copy; Copyright {{ copyright }}. Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
</div>
{%- endblock %}

View File

@ -1,19 +0,0 @@
<h3>Related Topics</h3>
<ul>
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
{%- for parent in parents %}
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
{%- endfor %}
{%- if prev %}
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
}}">{{ prev.title }}</a></li>
{%- endif %}
{%- if next %}
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
}}">{{ next.title }}</a></li>
{%- endif %}
{%- for parent in parents %}
</ul></li>
{%- endfor %}
</ul></li>
</ul>

View File

@ -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;
}

View File

@ -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 = ''

View File

@ -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'
}

View File

@ -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
# "<project> v<release> 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 <link> 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

View File

@ -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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/developing-an-alexa-skill-as-a-web-service#checking-the-signature-of-the-request>`_,
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 <https://en.wikipedia.org/wiki/Replay_attack>`_. 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)

View File

@ -1,11 +0,0 @@
Table Of Contents
-----------------
.. toctree::
:maxdepth: 2
getting_started
requests
responses
configuration
user_contributions

View File

@ -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)

View File

@ -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 <http://jinja.pocoo.org/docs/dev/api/#loaders>`_ 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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/overviews/understanding-custom-skills>`_ in the Alexa Skills Kit documentation.
Additionally, more code and template examples are in the `samples <https://github.com/johnwheeler/flask-ask/tree/master/samples>`_ directory.

View File

@ -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 <https://alexatutorial.com/>`_.
.. raw:: html
<p>
<a class="github-button" href="https://github.com/johnwheeler/flask-ask" data-icon="octicon-star" data-style="mega" data-count-href="/johnwheeler/flask-ask/stargazers" data-count-api="/repos/johnwheeler/flask-ask#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star johnwheeler/flask-ask on GitHub">Star</a>
</p>
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 <https://ngrok.com/>`_ 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
<br>
<iframe width="560" height="315" src="https://www.youtube.com/embed/cXL8FDUag-s" frameborder="0" allowfullscreen></iframe>
Follow along with this quickstart on `Amazon
<https://developer.amazon.com/public/community/post/Tx14R0IYYGH3SKT/Flask-Ask-A-New-Python-Framework-for-Rapid-Alexa-Skills-Kit-Development>`_.
.. include:: contents.rst.inc
.. raw:: html
<br>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">If you have an Alexa, Flask-Ask looks fantastic! <a href="https://t.co/vpoFsVNqI5">https://t.co/vpoFsVNqI5</a></p>&mdash; Kenneth Reitz (@kennethreitz) <a href="https://twitter.com/kennethreitz/status/736234634292297729">May 27, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

View File

@ -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 ^<target^>` where ^<target^> 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

View File

@ -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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/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 <https://youtu.be/6RoSi3G1chk>`_.
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 <https://youtu.be/AnyZG2AJE4o>`_.
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 <https://youtu.be/gP86uugBico>`_.
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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference#Slot%20Types>`_,
just pass the strings in the second column below:
📼 Here is a video demo on `Slot Conversion Helpers with Flask-Ask video <https://youtu.be/2RoRoABK_VE>`_.
=================== =============== ======================
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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#Request%20Format>`_
has four top-level elements: ``session``, ``context``, ``request`` and ``version``. Like Flask, Flask-Ask provides `context
locals <http://werkzeug.pocoo.org/docs/0.11/local/>`_ 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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference>`_
in the Alexa Skills Kit documentation.

View File

@ -1,152 +0,0 @@
Building Responses
==================
📼 Here is a video demo on `Building Responses with Flask-Ask video <https://youtu.be/mObuAlfxnl8>`_ .
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 <https://docs.python.org/2/library/json.html#json.dump>`_ 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 <https://en.wikipedia.org/wiki/Speech_Synthesis_Markup_Language>`_ 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: |
<speak>
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.
<audio src='https://s3.amazonaws.com/ask-storage/tidePooler/OceanWaves.mp3'/>
</speak>
You can also use a custom templates file passed into the Ask object::
ask = Ask(app, '/', None, 'custom-templates.yml')

View File

@ -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 <https://developer.amazon.com/public/community/post/Tx14R0IYYGH3SKT/Flask-Ask-A-New-Python-Framework-for-Rapid-Alexa-Skills-Kit-Development>`_
by John Wheeler
`Running with Alexa Part I. <http://www.timkl.com/posts/running-with-alexa>`_
by Tim Kjær Lange
`Intro and Skill Logic - Alexa Skills w/ Python and Flask-Ask Part 1 <https://pythonprogramming.net/intro-alexa-skill-flask-ask-python-tutorial/>`_
by Harrison Kinsley
`Headlines Function - Alexa Skills w/ Python and Flask-Ask Part 2 <https://pythonprogramming.net/headlines-function-alexa-skill-flask-ask-python-tutorial/>`_
by Harrison Kinsley
`Testing our Skill - Alexa Skills w/ Python and Flask-Ask Part 3 <https://pythonprogramming.net/testing-deploying-alexa-skill-flask-ask-python-tutorial/>`_
by Harrison Kinsley
`Flask-AskA tutorial on a simple and easy way to build complex Alexa Skills <https://blog.craftworkz.co/flask-ask-a-tutorial-on-a-simple-and-easy-way-to-build-complex-alexa-skills-426a6b3ff8bc#.70eay9n07>`_
by Bjorn Vuylsteker

View File

@ -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
)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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}

View File

@ -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

Binary file not shown.

View File

@ -1,5 +0,0 @@
-r requirements.txt
mock==2.0.0
requests==2.13.0
tox==2.7.0

View File

@ -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

View File

@ -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)

View File

@ -1,16 +0,0 @@
{
"intents": [
{
"intent": "AMAZON.PauseIntent"
},
{
"intent": "PlaylistDemoIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.ResumeIntent"
}
]
}

View File

@ -1 +0,0 @@
PlaylistDemoIntent start the playlist

View File

@ -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)

View File

@ -1,19 +0,0 @@
{
"intents": [
{
"intent": "AMAZON.PauseIntent"
},
{
"intent": "DemoIntent"
},
{
"intent": "SaxIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.ResumeIntent"
}
]
}

View File

@ -1,3 +0,0 @@
DemoIntent begin demo
SaxIntent play the sax
SaxIntent play sax

View File

@ -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)

View File

@ -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

View File

@ -1,10 +0,0 @@
{
"intents": [
{
"intent": "HelloWorldIntent"
},
{
"intent": "AMAZON.HelpIntent"
}
]
}

View File

@ -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

View File

@ -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!"

View File

@ -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)

View File

@ -1,10 +0,0 @@
{
"intents": [
{
"intent": "HelloWorldIntent"
},
{
"intent": "AMAZON.HelpIntent"
}
]
}

View File

@ -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

View File

@ -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('<speak>{}</speak>'.format(speech_output))
else:
card_title = "Events on {} {}".format(month_name, day_number)
speech_output = "<p>For {} {}</p>".format(month_name, day_number)
card_output = ""
for i in range(PAGINATION_SIZE):
speech_output += "<p>{}</p>".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 = '<speak>{}</speak>'.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 += "<p>{}</p>".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 = '<speak>{}</speak>'.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)

View File

@ -1,25 +0,0 @@
{
"intents": [
{
"intent": "GetFirstEventIntent",
"slots": [
{
"name": "day",
"type": "AMAZON.DATE"
}
]
},
{
"intent": "GetNextEventIntent"
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}

View File

@ -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

View File

@ -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"
}
}
]
}
]
}
}
}

View File

@ -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

View File

@ -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)

View File

@ -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}}

View File

@ -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()

View File

@ -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": []
}
}
}

View File

@ -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

View File

@ -1,4 +0,0 @@
Chest
Legs
Torso
Upper body

View File

@ -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()

View File

@ -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°"

View File

@ -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)

View File

@ -1,16 +0,0 @@
{
"intents": [
{
"intent": "MyColorIsIntent",
"slots": [
{
"name": "Color",
"type": "LIST_OF_COLORS"
}
]
},
{
"intent": "WhatsMyColorIntent"
}
]
}

View File

@ -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

View File

@ -1,6 +0,0 @@
green
blue
purple
red
orange
yellow

View File

@ -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

View File

@ -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)

View File

@ -1,16 +0,0 @@
{
"intents": [
{
"intent": "GetNewFactIntent"
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}

View File

@ -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

View File

@ -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

View File

@ -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"
}
]
}

View File

@ -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

View File

@ -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

View File

@ -1,10 +0,0 @@
california
florida
louisiana
massachusetts
new york
north carolina
south carolina
texas
virginia
washington

View File

@ -1,41 +0,0 @@
welcome: |
<speak>
Welcome to Tide Pooler.
<audio src='https://s3.amazonaws.com/ask-storage/tidePooler/OceanWaves.mp3'/>
Which city would you like tide information for?
</speak>
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

View File

@ -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)

View File

@ -1,5 +0,0 @@
[bdist_wheel]
universal = 1
[metadata]
description-file = README.md

View File

@ -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'
]
)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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))

View File

@ -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)

View File

@ -1,8 +0,0 @@
[tox]
envlist = py27,py3
skipsdist = True
[testenv]
deps = -rrequirements-dev.txt
commands = python setup.py test