[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