[Python-modules-commits] [python-lti] 01/02: importing python-lti_0.9.2.orig.tar.gz
Michael Fladischer
fladi at moszumanska.debian.org
Mon Oct 16 20:02:40 UTC 2017
This is an automated email from the git hooks/post-receive script.
fladi pushed a commit to branch debian/master
in repository python-lti.
commit 7d899873d244ff6f6aa31f9516f8c86c28e00925
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date: Fri Sep 22 08:02:10 2017 +0200
importing python-lti_0.9.2.orig.tar.gz
---
.gitignore | 33 +++
.travis.yml | 28 +++
AUTHORS.rst | 24 ++
HISTORY.rst | 85 +++++++
LICENSE.rst | 80 +++++++
README.rst | 173 ++++++++++++++
setup.cfg | 11 +
setup.py | 29 +++
src/lti/__init__.py | 15 ++
src/lti/contentitem_response.py | 11 +
src/lti/contrib/__init__.py | 0
src/lti/contrib/django/__init__.py | 1 +
src/lti/contrib/django/django_tool_provider.py | 38 +++
src/lti/contrib/flask/__init__.py | 1 +
src/lti/contrib/flask/flask_tool_provider.py | 16 ++
src/lti/launch_params.py | 209 ++++++++++++++++
src/lti/outcome_request.py | 234 ++++++++++++++++++
src/lti/outcome_response.py | 166 +++++++++++++
src/lti/tool_base.py | 80 +++++++
src/lti/tool_config.py | 286 ++++++++++++++++++++++
src/lti/tool_consumer.py | 19 ++
src/lti/tool_outbound.py | 58 +++++
src/lti/tool_provider.py | 187 +++++++++++++++
src/lti/tool_proxy.py | 33 +++
src/lti/utils.py | 32 +++
tests/test_contentitem_response.py | 130 ++++++++++
tests/test_launch_params.py | 82 +++++++
tests/test_outcome_request.py | 148 ++++++++++++
tests/test_outcome_response.py | 109 +++++++++
tests/test_tool_base.py | 94 ++++++++
tests/test_tool_config.py | 162 +++++++++++++
tests/test_tool_consumer.py | 142 +++++++++++
tests/test_tool_provider.py | 317 +++++++++++++++++++++++++
tests/test_tool_proxy.py | 216 +++++++++++++++++
tox.ini | 29 +++
35 files changed, 3278 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..38329fe
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# virtualenv
+venv
+
+# tox
+.eggs
+.tox
+
+# pytest
+.cache
+
+# Coverage
+.coverage*
+
+# Pycharm
+.idea
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..3b3d4ef
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,28 @@
+sudo: false
+language: python
+python:
+ - 2.7
+ - 3.5
+ - 3.6
+install: pip install tox-travis codecov
+script: tox
+after_success: codecov
+branches:
+ only:
+ - master
+ - /^\d+(\.\d+)*$/
+deploy:
+ provider: pypi
+ user: pylti
+ password:
+ secure: TQ0/wEA4Z96GcPPpW4+BwVLq1Oh8YDihD1ugelIwgqIfpVJNF3lSpahazocAn6Lu53Z+I+qReyFMLBpdKBZAQN4d8501V8X029uY7l2Dj7qyWsgOlJmud8BtJZizZ2jgmdg670sZSrEAipfxJ0Rm5f7ItrTWLEEDZG2FQ88T2Ao6CQuOejqqm+UP2AL9he+BjbKN4cOBiVDyYY4KaayzCwue02JMDjTrQdUUD2ebjCa1DuEMLlsiy0Qwvfi59GBM6TygF9Tp4r+S4oCSEP7IoEmOEdykG0vhRlnugMtItl4L/m1DGL/xoClBPHTRP29JJ6c4yaoC4CxH7uGbpeW1EKOrncNx2GxMLco17S7UiBmIQudrvMDSAeepj1BCPUK+QU++OZo9+ZVcA9sctMcz0EieCwh1gXeO8nXtlsbtDzwMUaHTI1C1voSDRXcYGs0UdayGqN/+Rxv26AmJUJymNmcCZdZTfKHR [...]
+ on:
+ tags: true
+ python: 3.6
+ condition: $TOXENV != desc
+ distributions: sdist bdist_wheel
+matrix:
+ fast_finish: true
+ include:
+ - python: 3.6
+ env: TOXENV=desc
diff --git a/AUTHORS.rst b/AUTHORS.rst
new file mode 100644
index 0000000..d8f8004
--- /dev/null
+++ b/AUTHORS.rst
@@ -0,0 +1,24 @@
+This project is the iterative work of several people,
+under several different project names.
+
+The current core contributors are:
+
+* Ryan Hiebert `@ryanhiebert`_
+* Jay Luker `@lbjay`_
+
+.. _`@ryanhiebert`: https://github.com/ryanhiebert
+.. _`@lbjay`: https://github.com/lbjay
+
+
+The project originally started life as ``ims_lti_py``.
+Then Jay Luker moved the project to ``dce_lti_py``,
+which was later brought under the mantle of
+the Python LTI Initiative with the name ``lti``.
+
+The core contributors to the original ``ims_lti_py`` are:
+
+* Anson MacKeracher `@amackera`_
+* Jero Sutlovic `@jsutlovic`_
+
+.. _`@amackera`: https://github.com/amackera
+.. _`@jsutlovic`: https://github.com/jsutlovic
diff --git a/HISTORY.rst b/HISTORY.rst
new file mode 100644
index 0000000..aedf464
--- /dev/null
+++ b/HISTORY.rst
@@ -0,0 +1,85 @@
+0.9.2 (2017-06-15)
+++++++++++++++++++
+
+* Pass through params with empty values (#47)
+
+0.9.1 (2017-06-15)
+++++++++++++++++++
+
+* Fix the PyPI long description.
+
+0.9.0 (2017-06-14)
+++++++++++++++++++
+
+* Add test of from_post_request (#45)
+* Code to allow registration of an LTI 2 proxy (#42)
+* Fix attribute name (#44)
+* Content item (#41)
+* Fix for bug in ToolConsumer urlencoding (#40)
+* PEP8 (#35)
+* Add Python 3.6 support (#34)
+
+0.8.4 (2016-07-26)
+++++++++++++++++++
+
+* Fix proxy validator (#30).
+
+0.8.3 (2016-07-21)
+++++++++++++++++++
+
+* Fix flask request initialization (#28).
+
+0.8.2 (2016-07-06)
+++++++++++++++++++
+
+* Do not require secret for tool provider (#26).
+
+0.8.1 (2016-06-22)
+++++++++++++++++++
+
+* Python 3 compatibility.
+
+0.8.0 (2016-05-15)
+++++++++++++++++++
+
+* Fork from dce_lti_py_, and rename to ``lti`` at version 0.7.4.
+* Convert text files to reStructured Text.
+* Use README as PyPI long description.
+
+.. _dce_lti_py: https://github.com/harvard-dce/dce_lti_py
+
+0.7.4 (2015-10-16)
+++++++++++++++++++
+
+* Include ``oauth_body_hash`` parameter in outcome request.
+
+0.7.3 (2015-05-28)
+++++++++++++++++++
+
+* Add some launch params specific to the Canvas editor.
+* Add contributor section to README.
+
+0.7.2 (2015-05-01)
+++++++++++++++++++
+
+* Use ``find_packages`` in ``setup.py`` to find contrib packages.
+
+0.7.1 (2015-04-30)
+++++++++++++++++++
+
+* Fork from _ims_lti_py_, and rename to ``dce_lti_py`` at version 0.6.
+* Update README and add HISTORY.
+
+.. _ims_lti_py: https://github.com/tophatmonocle/ims_lti_py
+
+0.7.0 (2015-04-30)
+++++++++++++++++++
+
+* Update project to utilize oauthlib.
+
+ * Convert from python-oauth2 to oauthlib and requests-oauthlib.
+ * Refactor out the use of mixin classes.
+ * Make ``LaunchParams`` a first-class object.
+ * Major rewrite and rename of the test suite.
+ * Use SemVer version identifier.
+ * Use pytest and tox for test running.
diff --git a/LICENSE.rst b/LICENSE.rst
new file mode 100644
index 0000000..0b52dd2
--- /dev/null
+++ b/LICENSE.rst
@@ -0,0 +1,80 @@
+This project is the iterative work of several people,
+under several different project names.
+Due to this, several licenses may apply to different
+parts of the code base.
+
+All licenses are the MIT license,
+but are granted from differing copyright holders.
+Each license is for the code that was created in the associated project.
+
+
+lti
+---
+
+Copyright (C) 2016 Python LTI Initiative
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+dce_lti_py
+----------
+
+Copyright (C) 2015 President and Fellows of Harvard College
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+ims_lti_py
+----------
+
+Copyright (C) 2011 Top Hat Monocle Corp.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..d079cc4
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,173 @@
+====================================
+lti: Learning Tools Interoperability
+====================================
+
+.. image:: https://travis-ci.org/pylti/lti.svg?branch=master
+ :target: https://travis-ci.org/pylti/lti
+
+.. image:: https://codecov.io/gh/pylti/lti/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/pylti/lti
+
+.. image:: https://badges.gitter.im/pylti/lti.svg
+ :alt: Join the chat at https://gitter.im/pylti/lti
+ :target: https://gitter.im/pylti/lti?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
+
+.. image:: https://requires.io/github/pylti/lti/requirements.svg?branch=master
+ :target: https://requires.io/github/pylti/lti/requirements/?branch=master
+ :alt: Requirements Status
+
+``lti`` is a Python library implementing the
+Learning Tools Interperability (LTI) standard.
+It is based on dce_lti_py_,
+which is based on ims_lti_py_.
+
+.. _dce_lti_py: https://github.com/harvard-dce/dce_lti_py
+.. _ims_lti_py: https://github.com/tophatmonocle/ims_lti_py
+
+
+Installation
+============
+
+.. code-block:: sh
+
+ pip install lti
+
+
+Dependencies
+============
+
+* lxml_
+* oauthlib_
+* requests-oauthlib_
+
+.. _lxml: https://github.com/lxml/lxml
+.. _oauthlib: https://github.com/idan/oauthlib
+.. _requests-oauthlib: https://github.com/requests/requests-oauthlib
+
+
+Usage
+=====
+
+The primary goal of this library is to provide classes
+for building Python LTI tool providers (LTI apps).
+To that end, the functionality that you're looking for
+is probably in the ``ToolConfig`` and ``ToolProvider`` classes (``ToolConsumer``
+is available too, if you want to consume LTI Providers).
+
+
+Tool Config Example (Django)
+----------------------------
+
+Here's an example of a Django view you might use as the
+configuration URL when registering your app with the LTI consumer.
+
+.. code-block:: python
+
+ from lti import ToolConfig
+ from django.http import HttpResponse
+
+
+ def tool_config(request):
+
+ # basic stuff
+ app_title = 'My App'
+ app_description = 'An example LTI App'
+ launch_view_name = 'lti_launch'
+ launch_url = request.build_absolute_uri(reverse('lti_launch'))
+
+ # maybe you've got some extensions
+ extensions = {
+ 'my_extensions_provider': {
+ # extension settings...
+ }
+ }
+
+ lti_tool_config = ToolConfig(
+ title=app_title,
+ launch_url=launch_url,
+ secure_launch_url=launch_url,
+ extensions=extensions,
+ description = app_description
+ )
+
+ return HttpResponse(lti_tool_config.to_xml(), content_type='text/xml')
+
+
+Tool Provider OAuth Request Validation Example (Django)
+-------------------------------------------------------
+
+.. code-block:: python
+
+ from lti.contrib.django import DjangoToolProvider
+ from my_app import RequestValidator
+
+
+ # create the tool provider instance
+ tool_provider = DjangoToolProvider.from_django_request(request=request)
+
+ # the tool provider uses the 'oauthlib' library which requires an instance
+ # of a validator class when doing the oauth request signature checking.
+ # see https://oauthlib.readthedocs.org/en/latest/oauth1/validator.html for
+ # info on how to create one
+ validator = RequestValidator()
+
+ # validate the oauth request signature
+ ok = tool_provider.is_valid_request(validator)
+
+ # do stuff if ok / not ok
+
+
+Tool Consumer Example (Django)
+------------------------------
+
+In your view:
+
+.. code-block:: python
+
+ def index(request):
+ consumer = ToolConsumer(
+ consumer_key='my_key_given_from_provider',
+ consumer_secret='super_secret',
+ launch_url='provider_url',
+ params={
+ 'lti_message_type': 'basic-lti-launch-request'
+ }
+ )
+
+ return render(
+ request,
+ 'lti_consumer/index.html',
+ {
+ 'launch_data': consumer.generate_launch_data(),
+ 'launch_url': consumer.launch_url
+ }
+ )
+
+At the template:
+
+.. code-block:: html
+
+ <form action="{{ launch_url }}"
+ name="ltiLaunchForm"
+ id="ltiLaunchForm"
+ method="POST"
+ encType="application/x-www-form-urlencoded">
+ {% for key, value in launch_data.items %}
+ <input type="hidden" name="{{ key }}" value="{{ value }}"/>
+ {% endfor %}
+ <button type="submit">Launch the tool</button>
+ </form>
+
+
+Testing
+=======
+
+Unit tests can be run by executing
+
+.. code-block:: sh
+
+ tox
+
+This uses tox_ to set up and run the test environment.
+
+.. _tox: https://tox.readthedocs.org/
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..57550ce
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,11 @@
+[wheel]
+universal = 1
+
+[coverage:run]
+branch = True
+source = lti
+
+[coverage:paths]
+source =
+ src/lti
+ .tox/*/lib/python*/site-packages/lti
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..96d7324
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+"""Set up the lti package."""
+from setuptools import setup, find_packages
+
+setup(
+ name='lti',
+ version='0.9.2',
+ description='A python library for building and/or consuming LTI apps',
+ long_description=open('README.rst', 'rb').read().decode('utf-8'),
+ maintainer='Ryan Hiebert',
+ maintainer_email='ryan at ryanhiebert.com',
+ url='https://github.com/pylti/lti',
+ package_dir={'': 'src'},
+ packages=find_packages('src'),
+ install_requires=[
+ 'lxml',
+ 'oauthlib',
+ 'requests-oauthlib',
+ ],
+ license='MIT License',
+ keywords='lti',
+ zip_safe=True,
+ classifiers=[
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ ],
+)
diff --git a/src/lti/__init__.py b/src/lti/__init__.py
new file mode 100644
index 0000000..623284b
--- /dev/null
+++ b/src/lti/__init__.py
@@ -0,0 +1,15 @@
+DEFAULT_LTI_VERSION = 'LTI-1.0'
+
+# Classes
+from .launch_params import LaunchParams
+from .tool_base import ToolBase
+from .tool_config import ToolConfig
+from .tool_consumer import ToolConsumer
+from .tool_provider import ToolProvider
+from .outcome_request import OutcomeRequest
+from .outcome_response import OutcomeResponse
+from .contentitem_response import ContentItemResponse
+from .tool_proxy import ToolProxy
+
+# Exceptions
+from .utils import InvalidLTIConfigError, InvalidLTIRequestError
diff --git a/src/lti/contentitem_response.py b/src/lti/contentitem_response.py
new file mode 100644
index 0000000..453f2de
--- /dev/null
+++ b/src/lti/contentitem_response.py
@@ -0,0 +1,11 @@
+
+from .launch_params import CONTENT_PARAMS_REQUIRED
+
+from .tool_outbound import ToolOutbound
+
+class ContentItemResponse(ToolOutbound):
+
+ def has_required_params(self):
+ return all([
+ self.launch_params.get(x) for x in CONTENT_PARAMS_REQUIRED
+ ])
diff --git a/src/lti/contrib/__init__.py b/src/lti/contrib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lti/contrib/django/__init__.py b/src/lti/contrib/django/__init__.py
new file mode 100644
index 0000000..bb5c2a7
--- /dev/null
+++ b/src/lti/contrib/django/__init__.py
@@ -0,0 +1 @@
+from .django_tool_provider import DjangoToolProvider
diff --git a/src/lti/contrib/django/django_tool_provider.py b/src/lti/contrib/django/django_tool_provider.py
new file mode 100644
index 0000000..a747a52
--- /dev/null
+++ b/src/lti/contrib/django/django_tool_provider.py
@@ -0,0 +1,38 @@
+
+from lti import ToolProvider
+from django.shortcuts import redirect
+
+
+class DjangoToolProvider(ToolProvider):
+ '''
+ ToolProvider that works with Django requests
+ '''
+ @classmethod
+ def from_django_request(cls, secret=None, request=None):
+ if request is None:
+ raise ValueError('request must be supplied')
+
+ params = request.POST.copy()
+ # django shoves a bunch of other junk in META that we don't care about
+ headers = dict([(k, request.META[k])
+ for k in request.META if
+ k.upper().startswith('HTTP_') or
+ k.upper().startswith('CONTENT_')])
+ url = request.build_absolute_uri()
+ return cls.from_unpacked_request(secret, params, url, headers)
+
+ def success_redirect(self, msg='', log=''):
+ '''
+ Shortcut for redirecting Django view to LTI Consumer with messages
+ '''
+ self.lti_msg = msg
+ self.lti_log = log
+ return redirect(self.build_return_url())
+
+ def error_redirect(self, errormsg='', errorlog=''):
+ '''
+ Shortcut for redirecting Django view to LTI Consumer with errors
+ '''
+ self.lti_errormsg = errormsg
+ self.lti_errorlog = errorlog
+ return redirect(self.build_return_url())
diff --git a/src/lti/contrib/flask/__init__.py b/src/lti/contrib/flask/__init__.py
new file mode 100644
index 0000000..52c34ff
--- /dev/null
+++ b/src/lti/contrib/flask/__init__.py
@@ -0,0 +1 @@
+from .flask_tool_provider import FlaskToolProvider
diff --git a/src/lti/contrib/flask/flask_tool_provider.py b/src/lti/contrib/flask/flask_tool_provider.py
new file mode 100644
index 0000000..340e204
--- /dev/null
+++ b/src/lti/contrib/flask/flask_tool_provider.py
@@ -0,0 +1,16 @@
+from lti import ToolProvider
+
+
+class FlaskToolProvider(ToolProvider):
+ '''
+ ToolProvider that works with Flask requests
+ '''
+ @classmethod
+ def from_flask_request(cls, secret=None, request=None):
+ if request is None:
+ raise ValueError('request must be supplied')
+
+ params = request.form.copy()
+ headers = dict(request.headers)
+ url = request.url
+ return cls.from_unpacked_request(secret, params, url, headers)
diff --git a/src/lti/launch_params.py b/src/lti/launch_params.py
new file mode 100644
index 0000000..46290c3
--- /dev/null
+++ b/src/lti/launch_params.py
@@ -0,0 +1,209 @@
+import sys
+from collections import MutableMapping
+
+from . import DEFAULT_LTI_VERSION
+
+py = sys.version_info
+if py < (2, 6, 0):
+ bytes = str
+
+
+def touni(s, enc='utf8', err='strict'):
+ return s.decode(enc, err) if isinstance(s, bytes) else unicode(s)
+
+
+LAUNCH_PARAMS_REQUIRED = [
+ 'lti_message_type',
+ 'lti_version',
+ 'resource_link_id'
+]
+
+LAUNCH_PARAMS_RECOMMENDED = [
+ 'resource_link_description',
+ 'resource_link_title',
+ 'user_id',
+ 'user_image',
+ 'roles',
+ 'lis_person_name_given',
+ 'lis_person_name_family',
+ 'lis_person_name_full',
+ 'lis_person_contact_email_primary',
+ 'role_scope_mentor',
+ 'context_id',
+ 'context_label',
+ 'context_title',
+ 'context_type',
+ 'launch_presentation_locale',
+ 'launch_presentation_document_target',
+ 'launch_presentation_css_url',
+ 'launch_presentation_width',
+ 'launch_presentation_height',
+ 'launch_presentation_return_url',
+ 'tool_consumer_info_product_family_code',
+ 'tool_consumer_info_version',
+ 'tool_consumer_instance_guid',
+ 'tool_consumer_instance_name',
+ 'tool_consumer_instance_description',
+ 'tool_consumer_instance_url',
+ 'tool_consumer_instance_contact_email',
+]
+
+LAUNCH_PARAMS_LIS = [
+ 'lis_course_section_sourcedid',
+ 'lis_course_offering_sourcedid',
+ 'lis_outcome_service_url',
+ 'lis_person_sourcedid',
+ 'lis_result_sourcedid',
+]
+
+LAUNCH_PARAMS_RETURN_URL = [
+ 'lti_errormsg',
+ 'lti_errorlog',
+ 'lti_msg',
+ 'lti_log'
+]
+
+LAUNCH_PARAMS_OAUTH = [
+ 'oauth_consumer_key',
+ 'oauth_signature_method',
+ 'oauth_timestamp',
+ 'oauth_nonce',
+ 'oauth_version',
+ 'oauth_signature',
+ 'oauth_callback'
+]
+
+LAUNCH_PARAMS_IS_LIST = [
+ 'roles',
+ 'role_scope_mentor',
+ 'context_type',
+ 'accept_media_types',
+ 'accept_presentation_document_targets'
+]
+
+LAUNCH_PARAMS_CANVAS = [
+ 'selection_directive',
+ 'text'
+]
+
+CONTENT_PARAMS_REQUEST = [
+ 'accept_media_types',
+ 'accept_presentation_document_targets',
+ 'content_item_return_url',
+ 'accept_unsigned',
+ 'accept_multiple',
+ 'accept_copy_advice',
+ 'auto_create',
+ 'title',
+ 'data',
+ 'can_confirm'
+]
+
+CONTENT_PARAMS_RESPONSE = [
+ 'content_items',
+ 'lti_msg',
+ 'lti_log',
+ 'lti_errormsg',
+ 'lti_errorlog'
+]
+
+CONTENT_PARAMS_REQUIRED = [
+ 'lti_message_type',
+ 'lti_version'
+]
+
+REGISTRATION_PARAMS = [
+ 'tc_profile_url',
+ 'reg_password',
+ 'reg_key'
+]
+
+LAUNCH_PARAMS = (
+ LAUNCH_PARAMS_REQUIRED +
+ LAUNCH_PARAMS_RECOMMENDED +
+ LAUNCH_PARAMS_RETURN_URL +
+ LAUNCH_PARAMS_OAUTH +
+ LAUNCH_PARAMS_LIS +
+ LAUNCH_PARAMS_CANVAS +
+ CONTENT_PARAMS_REQUEST +
+ CONTENT_PARAMS_RESPONSE +
+ REGISTRATION_PARAMS
+)
+
+
+def valid_param(param):
+ if param.startswith('custom_') or param.startswith('ext_'):
+ return True
+ elif param in LAUNCH_PARAMS:
+ return True
+ return False
+
+
+class InvalidLaunchParamError(ValueError):
+
+ def __init__(self, param):
+ message = "{} is not a valid launch param".format(param)
+ super(Exception, self).__init__(message)
+
+
+class LaunchParams(MutableMapping):
+ """
+ Represents the params for an LTI launch request. Provides dict-like
+ behavior through the use of the MutableMapping ABC mixin. Strictly
+ enforces that params are valid LTI params.
+ """
+
+ def __init__(self, *args, **kwargs):
+
+ self._params = dict()
+ self.update(*args, **kwargs)
+
+ # now verify we only got valid launch params
+ for k in self.keys():
+ if not valid_param(k):
+ raise InvalidLaunchParamError(k)
+
+ # enforce some defaults
+ if 'lti_version' not in self:
+ self['lti_version'] = DEFAULT_LTI_VERSION
+ if 'lti_message_type' not in self:
+ self['lti_message_type'] = 'basic-lti-launch-request'
+
+ def set_non_spec_param(self, param, val):
+ self._params[param] = val
+
+ def get_non_spec_param(self, param):
+ return self._params.get(param)
+
+ def _param_value(self, param):
+ if param in LAUNCH_PARAMS_IS_LIST:
+ return [x.strip() for x in self._params[param].split(',')]
+ else:
+ return self._params[param]
+
+ def __len__(self):
+ return len(self._params)
+
+ def __getitem__(self, item):
+ if not valid_param(item):
+ raise KeyError("{} is not a valid launch param".format(item))
+ try:
+ return self._param_value(item)
+ except KeyError:
+ # catch and raise new KeyError in the proper context
+ raise KeyError(item)
+
+ def __setitem__(self, key, value):
+ if not valid_param(key):
+ raise InvalidLaunchParamError(key)
+ if key in LAUNCH_PARAMS_IS_LIST:
+ if isinstance(value, list):
+ value = ','.join([x.strip() for x in value])
+ self._params[key] = value
+
+ def __delitem__(self, key):
+ if key in self._params:
+ del self._params[key]
+
+ def __iter__(self):
+ return iter(self._params)
diff --git a/src/lti/outcome_request.py b/src/lti/outcome_request.py
new file mode 100644
index 0000000..124d755
--- /dev/null
+++ b/src/lti/outcome_request.py
@@ -0,0 +1,234 @@
+from collections import defaultdict
+from lxml import etree, objectify
+
+import requests
+from requests_oauthlib import OAuth1
+from requests_oauthlib.oauth1_auth import SIGNATURE_TYPE_AUTH_HEADER
+
+from .outcome_response import OutcomeResponse
+from .utils import InvalidLTIConfigError
+
+REPLACE_REQUEST = 'replaceResult'
+DELETE_REQUEST = 'deleteResult'
+READ_REQUEST = 'readResult'
+
+VALID_ATTRIBUTES = [
+ 'operation',
+ 'score',
+ 'result_data',
+ 'outcome_response',
+ 'message_identifier',
+ 'lis_outcome_service_url',
+ 'lis_result_sourcedid',
+ 'consumer_key',
+ 'consumer_secret',
+ 'post_request'
+]
+
+
+class OutcomeRequest(object):
+ '''
+ Class for consuming & generating LTI Outcome Requests.
+
+ Outcome Request documentation:
+ http://www.imsglobal.org/LTI/v1p1/ltiIMGv1p1.html#_Toc319560472
+
+ This class can be used both by Tool Providers and Tool Consumers, though
+ they each use it differently. The TP will use it to POST an OAuth-signed
+ request to the TC. A TC will use it to parse such a request from a TP.
+ '''
+ def __init__(self, opts=defaultdict(lambda: None)):
+ # Initialize all our accessors to None
+ for attr in VALID_ATTRIBUTES:
+ setattr(self, attr, None)
+
+ # Store specified options in our accessors
+ for (key, val) in opts.items():
+ if key in VALID_ATTRIBUTES:
+ setattr(self, key, val)
+ else:
+ raise InvalidLTIConfigError(
+ "Invalid outcome request option: {}".format(key)
+ )
+
+ @staticmethod
+ def from_post_request(post_request):
+ '''
+ Convenience method for creating a new OutcomeRequest from a request
+ object.
+
+ post_request is assumed to be a Django HttpRequest object
+ '''
+ request = OutcomeRequest()
+ request.post_request = post_request
+ request.process_xml(post_request.body)
+ return request
+
+ def post_replace_result(self, score, result_data=None):
+ '''
+ POSTs the given score to the Tool Consumer with a replaceResult.
+
+ OPTIONAL:
+ result_data must be a dictionary
+ Note: ONLY ONE of these values can be in the dict at a time,
+ due to the Canvas specification.
+
+ 'text' : str text
+ 'url' : str url
+ '''
+ self.operation = REPLACE_REQUEST
+ self.score = score
+ self.result_data = result_data
+ if result_data is not None:
+ if len(result_data) > 1:
+ error_msg = ('Dictionary result_data can only have one entry. '
+ '{0} entries were found.'.format(len(result_data)))
+ raise InvalidLTIConfigError(error_msg)
+ elif 'text' not in result_data and 'url' not in result_data:
+ error_msg = ('Dictionary result_data can only have the key '
+ '"text" or the key "url".')
+ raise InvalidLTIConfigError(error_msg)
+ else:
+ return self.post_outcome_request()
+ else:
+ return self.post_outcome_request()
+
+ def post_delete_result(self):
+ '''
+ POSTs a deleteRequest to the Tool Consumer.
+ '''
+ self.operation = DELETE_REQUEST
+ return self.post_outcome_request()
+
+ def post_read_result(self):
+ '''
... 2529 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-lti.git
More information about the Python-modules-commits
mailing list