From 2b963569bb2c60eab735f7bc66bc59ee86490252 Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Thu, 25 Sep 2025 09:34:53 +0100 Subject: [PATCH 1/6] new metadata --- README.rst | 22 ++++++++------- pyproject.toml | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 pyproject.toml diff --git a/README.rst b/README.rst index 653c7fc..9a233ac 100644 --- a/README.rst +++ b/README.rst @@ -1,24 +1,26 @@ LTI 1.3 Advantage Tool implementation in Python =============================================== -.. image:: https://img.shields.io/pypi/v/PyLTI1p3 +This is a fork of the `pylti1p3 `_ package, originally written by Dmitry Viskov. + +.. image:: https://img.shields.io/pypi/v/pylti1p3next :scale: 100% - :target: https://pypi.python.org/pypi/PyLTI1p3 + :target: https://pypi.python.org/pypi/pylti1p3next :alt: PyPI -.. image:: https://img.shields.io/pypi/pyversions/PyLTI1p3 +.. image:: https://img.shields.io/pypi/pyversions/pylti1p3next :scale: 100% :target: https://www.python.org/ :alt: Python -.. image:: https://github.com/dmitry-viskov/pylti1.3/actions/workflows/tox.yml/badge.svg +.. image:: https://github.com/pymsglobal/pylti1p3next/actions/workflows/tox.yml/badge.svg :scale: 100% - :target: https://github.com/dmitry-viskov/pylti1.3/actions + :target: https://github.com/pymsglobal/pylti1p3next/actions :alt: Build Status -.. image:: https://img.shields.io/github/license/dmitry-viskov/pylti1.3 +.. image:: https://img.shields.io/github/license/pymsglobal/pylti1p3next :scale: 100% - :target: https://raw.githubusercontent.com/dmitry-viskov/pylti1.3/master/LICENSE + :target: https://raw.githubusercontent.com/pymsglobal/pylti1p3next/master/LICENSE :alt: MIT @@ -30,9 +32,9 @@ This library contains adapters for use with the Django and Flask web frameworks. Usage Examples ================= -Django: https://github.com/dmitry-viskov/pylti1.3-django-example +Django: https://github.com/pymsglobal/pylti1p3next-django-example -Flask: https://github.com/dmitry-viskov/pylti1.3-flask-example +Flask: https://github.com/pymsglobal/pylti1p3next-flask-example Configuration ============= @@ -581,7 +583,7 @@ After this, the special JS code will try to write and then read test cookie inst `special page`_ that will ask them to open the current URL in the new window if cookies are unavailable. If cookies are allowed, the user will be transparently redirected to the next page. All texts are configurable with passing arguments: -.. _special page: https://raw.githubusercontent.com/dmitry-viskov/repos-assets/master/pylti1p3/examples/cookies-check/001.png +.. _special page: https://raw.githubusercontent.com/pymsglobal/repos-assets/master/pylti1p3/examples/cookies-check/001.png .. code-block:: python diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ac92321 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,72 @@ +[project] +name = "pylti1p3next" +dynamic = ["version"] +authors = [ + { name="Christian Lawson-Perfect", email="christian.perfect@ncl.ac.uk"}, + { name="Dmitry Viskov", email="dmitry.viskov@webenterprise.ru"} +] +maintainers = [ + { name="Sébastien Philippot", email="sebastien@philippot.co" }, + { name="Christian Lawson-Perfect", email="christian.perfect@ncl.ac.uk"} +] +description = "LTI 1.3 Advantage Tool implementation in Python" +readme = "README.rst" +license = "MIT" +license-files = ["LICENSE"] +requires-python = ">=3.8" +keywords = [ + "pylti", + "pylti1p3", + "lti", + "lti1.3", + "lti1p3", + "django", + "flask" +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Flask", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: Education", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI", + "Topic :: Security", + "Topic :: Software Development :: Libraries :: Application Frameworks", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "jwcrypto~=1.5", + "pyjwt~=1.5", + "requests~=2.32", + "typing_extensions~=4.2", +] + +[project.urls] +Homepage = "https://github.com/pymsglobal/pylti1p3next#readme" +Issues = "https://github.com/pymsglobal/pylti1p3next/issues" +Source = "https://github.com/pymsglobal/pylti1p3next" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +version = {attr = "pylti1p3.__version__"} + +[tool.coverage.paths] +pylti1p3next = ["pylti1p3"] +tests = ["tests"] From 06146f26b6edfef25bc46bd5766990d4eba6287f Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Thu, 25 Sep 2025 09:34:53 +0100 Subject: [PATCH 2/6] move to src layout --- setup.py | 72 ------------------- {pylti1p3 => src/pylti1p3}/__init__.py | 0 {pylti1p3 => src/pylti1p3}/actions.py | 0 .../pylti1p3}/assignments_grades.py | 6 +- .../pylti1p3}/contrib/__init__.py | 0 .../pylti1p3}/contrib/django/__init__.py | 0 .../pylti1p3}/contrib/django/cookie.py | 0 .../django/launch_data_storage/__init__.py | 0 .../django/launch_data_storage/cache.py | 0 .../django/lti1p3_tool_config/__init__.py | 0 .../django/lti1p3_tool_config/admin.py | 0 .../contrib/django/lti1p3_tool_config/apps.py | 0 .../migrations/0001_initial.py | 0 .../lti1p3_tool_config/migrations/__init__.py | 0 .../django/lti1p3_tool_config/models.py | 0 .../contrib/django/message_launch.py | 0 .../pylti1p3}/contrib/django/oidc_login.py | 0 .../pylti1p3}/contrib/django/redirect.py | 0 .../pylti1p3}/contrib/django/request.py | 0 .../pylti1p3}/contrib/django/session.py | 0 .../pylti1p3}/contrib/flask/__init__.py | 0 .../pylti1p3}/contrib/flask/cookie.py | 0 .../flask/launch_data_storage/__init__.py | 0 .../flask/launch_data_storage/cache.py | 0 .../pylti1p3}/contrib/flask/message_launch.py | 0 .../pylti1p3}/contrib/flask/oidc_login.py | 0 .../pylti1p3}/contrib/flask/redirect.py | 0 .../pylti1p3}/contrib/flask/request.py | 0 .../pylti1p3}/contrib/flask/session.py | 0 {pylti1p3 => src/pylti1p3}/contrib/py.typed | 0 {pylti1p3 => src/pylti1p3}/cookie.py | 0 .../pylti1p3}/cookies_allowed_check.py | 0 {pylti1p3 => src/pylti1p3}/course_groups.py | 0 {pylti1p3 => src/pylti1p3}/deep_link.py | 0 .../pylti1p3}/deep_link_resource.py | 0 {pylti1p3 => src/pylti1p3}/deployment.py | 0 {pylti1p3 => src/pylti1p3}/exception.py | 0 {pylti1p3 => src/pylti1p3}/grade.py | 0 .../pylti1p3}/launch_data_storage/__init__.py | 0 .../pylti1p3}/launch_data_storage/base.py | 0 .../pylti1p3}/launch_data_storage/cache.py | 0 .../pylti1p3}/launch_data_storage/session.py | 0 {pylti1p3 => src/pylti1p3}/lineitem.py | 0 {pylti1p3 => src/pylti1p3}/message_launch.py | 0 .../pylti1p3}/message_validators/__init__.py | 0 .../pylti1p3}/message_validators/abstract.py | 0 .../pylti1p3}/message_validators/deep_link.py | 0 .../message_validators/privacy_launch.py | 0 .../message_validators/resource_message.py | 0 .../message_validators/submission_review.py | 0 {pylti1p3 => src/pylti1p3}/names_roles.py | 0 {pylti1p3 => src/pylti1p3}/oidc_login.py | 0 {pylti1p3 => src/pylti1p3}/py.typed | 0 {pylti1p3 => src/pylti1p3}/redirect.py | 0 {pylti1p3 => src/pylti1p3}/registration.py | 0 {pylti1p3 => src/pylti1p3}/request.py | 0 {pylti1p3 => src/pylti1p3}/roles.py | 0 .../pylti1p3}/service_connector.py | 0 {pylti1p3 => src/pylti1p3}/session.py | 0 .../pylti1p3}/tool_config/__init__.py | 0 .../pylti1p3}/tool_config/abstract.py | 0 .../pylti1p3}/tool_config/dict.py | 0 .../pylti1p3}/tool_config/json_file.py | 0 .../pylti1p3}/tool_config/py.typed | 0 {pylti1p3 => src/pylti1p3}/utils.py | 0 tox.ini | 4 +- 66 files changed, 5 insertions(+), 77 deletions(-) delete mode 100644 setup.py rename {pylti1p3 => src/pylti1p3}/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/actions.py (100%) rename {pylti1p3 => src/pylti1p3}/assignments_grades.py (98%) rename {pylti1p3 => src/pylti1p3}/contrib/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/cookie.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/launch_data_storage/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/launch_data_storage/cache.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/lti1p3_tool_config/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/lti1p3_tool_config/admin.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/lti1p3_tool_config/apps.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/lti1p3_tool_config/migrations/0001_initial.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/lti1p3_tool_config/migrations/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/lti1p3_tool_config/models.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/message_launch.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/oidc_login.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/redirect.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/request.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/django/session.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/cookie.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/launch_data_storage/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/launch_data_storage/cache.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/message_launch.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/oidc_login.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/redirect.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/request.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/flask/session.py (100%) rename {pylti1p3 => src/pylti1p3}/contrib/py.typed (100%) rename {pylti1p3 => src/pylti1p3}/cookie.py (100%) rename {pylti1p3 => src/pylti1p3}/cookies_allowed_check.py (100%) rename {pylti1p3 => src/pylti1p3}/course_groups.py (100%) rename {pylti1p3 => src/pylti1p3}/deep_link.py (100%) rename {pylti1p3 => src/pylti1p3}/deep_link_resource.py (100%) rename {pylti1p3 => src/pylti1p3}/deployment.py (100%) rename {pylti1p3 => src/pylti1p3}/exception.py (100%) rename {pylti1p3 => src/pylti1p3}/grade.py (100%) rename {pylti1p3 => src/pylti1p3}/launch_data_storage/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/launch_data_storage/base.py (100%) rename {pylti1p3 => src/pylti1p3}/launch_data_storage/cache.py (100%) rename {pylti1p3 => src/pylti1p3}/launch_data_storage/session.py (100%) rename {pylti1p3 => src/pylti1p3}/lineitem.py (100%) rename {pylti1p3 => src/pylti1p3}/message_launch.py (100%) rename {pylti1p3 => src/pylti1p3}/message_validators/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/message_validators/abstract.py (100%) rename {pylti1p3 => src/pylti1p3}/message_validators/deep_link.py (100%) rename {pylti1p3 => src/pylti1p3}/message_validators/privacy_launch.py (100%) rename {pylti1p3 => src/pylti1p3}/message_validators/resource_message.py (100%) rename {pylti1p3 => src/pylti1p3}/message_validators/submission_review.py (100%) rename {pylti1p3 => src/pylti1p3}/names_roles.py (100%) rename {pylti1p3 => src/pylti1p3}/oidc_login.py (100%) rename {pylti1p3 => src/pylti1p3}/py.typed (100%) rename {pylti1p3 => src/pylti1p3}/redirect.py (100%) rename {pylti1p3 => src/pylti1p3}/registration.py (100%) rename {pylti1p3 => src/pylti1p3}/request.py (100%) rename {pylti1p3 => src/pylti1p3}/roles.py (100%) rename {pylti1p3 => src/pylti1p3}/service_connector.py (100%) rename {pylti1p3 => src/pylti1p3}/session.py (100%) rename {pylti1p3 => src/pylti1p3}/tool_config/__init__.py (100%) rename {pylti1p3 => src/pylti1p3}/tool_config/abstract.py (100%) rename {pylti1p3 => src/pylti1p3}/tool_config/dict.py (100%) rename {pylti1p3 => src/pylti1p3}/tool_config/json_file.py (100%) rename {pylti1p3 => src/pylti1p3}/tool_config/py.typed (100%) rename {pylti1p3 => src/pylti1p3}/utils.py (100%) diff --git a/setup.py b/setup.py deleted file mode 100644 index 8758cb4..0000000 --- a/setup.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import print_function - -import sys - -from setuptools import find_packages, setup - -from pylti1p3 import __version__ - -if sys.version_info < (3, 6): - error = "ERROR: PyLTI1p3 requires Python 3.6+ ... exiting." - print(error, file=sys.stderr) - sys.exit(1) - - -install_requires = [ - "jwcrypto", - "pyjwt>=1.5", - "requests", - "typing_extensions", -] - -with open("README.rst", "rt") as readme: - long_description = readme.read().strip() - -packages = find_packages(exclude=["examples", "tests"]) - -setup( - name="PyLTI1p3", - version=__version__, - description="LTI 1.3 Advantage Tool implementation in Python", - keywords="pylti,pylti1p3,lti,lti1.3,lti1p3,django,flask", - author="Dmitry Viskov", - author_email="dmitry.viskov@webenterprise.ru", - maintainer="Dmitry Viskov", - long_description=long_description, - install_requires=install_requires, - license="MIT", - url="https://github.com/dmitry-viskov/pylti1.3", - packages=packages, - zip_safe=False, - include_package_data=True, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Environment :: Web Environment", - "Framework :: Django", - "Framework :: Flask", - "Intended Audience :: Developers", - "Intended Audience :: Information Technology", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3 :: Only", - "Topic :: Internet :: WWW/HTTP", - "Topic :: Internet :: WWW/HTTP :: WSGI", - "Topic :: Security", - "Topic :: Software Development :: Libraries :: Application Frameworks", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content", - "Topic :: Software Development :: Libraries :: Python Modules", - ], - package_data={ - "pylti1p3": ["py.typed"], - "pylti1p3.tool_config": ["py.typed"], - "pylti1p3.contrib": ["py.typed"], - }, -) diff --git a/pylti1p3/__init__.py b/src/pylti1p3/__init__.py similarity index 100% rename from pylti1p3/__init__.py rename to src/pylti1p3/__init__.py diff --git a/pylti1p3/actions.py b/src/pylti1p3/actions.py similarity index 100% rename from pylti1p3/actions.py rename to src/pylti1p3/actions.py diff --git a/pylti1p3/assignments_grades.py b/src/pylti1p3/assignments_grades.py similarity index 98% rename from pylti1p3/assignments_grades.py rename to src/pylti1p3/assignments_grades.py index d0db759..2b492b6 100644 --- a/pylti1p3/assignments_grades.py +++ b/src/pylti1p3/assignments_grades.py @@ -249,7 +249,7 @@ def find_or_create_lineitem( if not self.can_create_lineitem(): raise LtiException("Can't create lineitem: Missing required scope") - created_lineitem = self._service_connector.make_service_request( + response = self._service_connector.make_service_request( self._service_data["scope"], self._service_data["lineitems"], is_post=True, @@ -257,9 +257,9 @@ def find_or_create_lineitem( content_type="application/vnd.ims.lis.v2.lineitem+json", accept="application/vnd.ims.lis.v2.lineitem+json", ) - if not isinstance(created_lineitem["body"], dict): + if not isinstance(response["body"], dict): raise LtiException("Unknown response type received for create line item") - return LineItem(t.cast(TLineItem, created_lineitem["body"])) + return LineItem(t.cast(TLineItem, response["body"])) def get_grades(self, lineitem: t.Optional[LineItem] = None) -> list: """ diff --git a/pylti1p3/contrib/__init__.py b/src/pylti1p3/contrib/__init__.py similarity index 100% rename from pylti1p3/contrib/__init__.py rename to src/pylti1p3/contrib/__init__.py diff --git a/pylti1p3/contrib/django/__init__.py b/src/pylti1p3/contrib/django/__init__.py similarity index 100% rename from pylti1p3/contrib/django/__init__.py rename to src/pylti1p3/contrib/django/__init__.py diff --git a/pylti1p3/contrib/django/cookie.py b/src/pylti1p3/contrib/django/cookie.py similarity index 100% rename from pylti1p3/contrib/django/cookie.py rename to src/pylti1p3/contrib/django/cookie.py diff --git a/pylti1p3/contrib/django/launch_data_storage/__init__.py b/src/pylti1p3/contrib/django/launch_data_storage/__init__.py similarity index 100% rename from pylti1p3/contrib/django/launch_data_storage/__init__.py rename to src/pylti1p3/contrib/django/launch_data_storage/__init__.py diff --git a/pylti1p3/contrib/django/launch_data_storage/cache.py b/src/pylti1p3/contrib/django/launch_data_storage/cache.py similarity index 100% rename from pylti1p3/contrib/django/launch_data_storage/cache.py rename to src/pylti1p3/contrib/django/launch_data_storage/cache.py diff --git a/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py similarity index 100% rename from pylti1p3/contrib/django/lti1p3_tool_config/__init__.py rename to src/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py diff --git a/pylti1p3/contrib/django/lti1p3_tool_config/admin.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/admin.py similarity index 100% rename from pylti1p3/contrib/django/lti1p3_tool_config/admin.py rename to src/pylti1p3/contrib/django/lti1p3_tool_config/admin.py diff --git a/pylti1p3/contrib/django/lti1p3_tool_config/apps.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/apps.py similarity index 100% rename from pylti1p3/contrib/django/lti1p3_tool_config/apps.py rename to src/pylti1p3/contrib/django/lti1p3_tool_config/apps.py diff --git a/pylti1p3/contrib/django/lti1p3_tool_config/migrations/0001_initial.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/migrations/0001_initial.py similarity index 100% rename from pylti1p3/contrib/django/lti1p3_tool_config/migrations/0001_initial.py rename to src/pylti1p3/contrib/django/lti1p3_tool_config/migrations/0001_initial.py diff --git a/pylti1p3/contrib/django/lti1p3_tool_config/migrations/__init__.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/migrations/__init__.py similarity index 100% rename from pylti1p3/contrib/django/lti1p3_tool_config/migrations/__init__.py rename to src/pylti1p3/contrib/django/lti1p3_tool_config/migrations/__init__.py diff --git a/pylti1p3/contrib/django/lti1p3_tool_config/models.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/models.py similarity index 100% rename from pylti1p3/contrib/django/lti1p3_tool_config/models.py rename to src/pylti1p3/contrib/django/lti1p3_tool_config/models.py diff --git a/pylti1p3/contrib/django/message_launch.py b/src/pylti1p3/contrib/django/message_launch.py similarity index 100% rename from pylti1p3/contrib/django/message_launch.py rename to src/pylti1p3/contrib/django/message_launch.py diff --git a/pylti1p3/contrib/django/oidc_login.py b/src/pylti1p3/contrib/django/oidc_login.py similarity index 100% rename from pylti1p3/contrib/django/oidc_login.py rename to src/pylti1p3/contrib/django/oidc_login.py diff --git a/pylti1p3/contrib/django/redirect.py b/src/pylti1p3/contrib/django/redirect.py similarity index 100% rename from pylti1p3/contrib/django/redirect.py rename to src/pylti1p3/contrib/django/redirect.py diff --git a/pylti1p3/contrib/django/request.py b/src/pylti1p3/contrib/django/request.py similarity index 100% rename from pylti1p3/contrib/django/request.py rename to src/pylti1p3/contrib/django/request.py diff --git a/pylti1p3/contrib/django/session.py b/src/pylti1p3/contrib/django/session.py similarity index 100% rename from pylti1p3/contrib/django/session.py rename to src/pylti1p3/contrib/django/session.py diff --git a/pylti1p3/contrib/flask/__init__.py b/src/pylti1p3/contrib/flask/__init__.py similarity index 100% rename from pylti1p3/contrib/flask/__init__.py rename to src/pylti1p3/contrib/flask/__init__.py diff --git a/pylti1p3/contrib/flask/cookie.py b/src/pylti1p3/contrib/flask/cookie.py similarity index 100% rename from pylti1p3/contrib/flask/cookie.py rename to src/pylti1p3/contrib/flask/cookie.py diff --git a/pylti1p3/contrib/flask/launch_data_storage/__init__.py b/src/pylti1p3/contrib/flask/launch_data_storage/__init__.py similarity index 100% rename from pylti1p3/contrib/flask/launch_data_storage/__init__.py rename to src/pylti1p3/contrib/flask/launch_data_storage/__init__.py diff --git a/pylti1p3/contrib/flask/launch_data_storage/cache.py b/src/pylti1p3/contrib/flask/launch_data_storage/cache.py similarity index 100% rename from pylti1p3/contrib/flask/launch_data_storage/cache.py rename to src/pylti1p3/contrib/flask/launch_data_storage/cache.py diff --git a/pylti1p3/contrib/flask/message_launch.py b/src/pylti1p3/contrib/flask/message_launch.py similarity index 100% rename from pylti1p3/contrib/flask/message_launch.py rename to src/pylti1p3/contrib/flask/message_launch.py diff --git a/pylti1p3/contrib/flask/oidc_login.py b/src/pylti1p3/contrib/flask/oidc_login.py similarity index 100% rename from pylti1p3/contrib/flask/oidc_login.py rename to src/pylti1p3/contrib/flask/oidc_login.py diff --git a/pylti1p3/contrib/flask/redirect.py b/src/pylti1p3/contrib/flask/redirect.py similarity index 100% rename from pylti1p3/contrib/flask/redirect.py rename to src/pylti1p3/contrib/flask/redirect.py diff --git a/pylti1p3/contrib/flask/request.py b/src/pylti1p3/contrib/flask/request.py similarity index 100% rename from pylti1p3/contrib/flask/request.py rename to src/pylti1p3/contrib/flask/request.py diff --git a/pylti1p3/contrib/flask/session.py b/src/pylti1p3/contrib/flask/session.py similarity index 100% rename from pylti1p3/contrib/flask/session.py rename to src/pylti1p3/contrib/flask/session.py diff --git a/pylti1p3/contrib/py.typed b/src/pylti1p3/contrib/py.typed similarity index 100% rename from pylti1p3/contrib/py.typed rename to src/pylti1p3/contrib/py.typed diff --git a/pylti1p3/cookie.py b/src/pylti1p3/cookie.py similarity index 100% rename from pylti1p3/cookie.py rename to src/pylti1p3/cookie.py diff --git a/pylti1p3/cookies_allowed_check.py b/src/pylti1p3/cookies_allowed_check.py similarity index 100% rename from pylti1p3/cookies_allowed_check.py rename to src/pylti1p3/cookies_allowed_check.py diff --git a/pylti1p3/course_groups.py b/src/pylti1p3/course_groups.py similarity index 100% rename from pylti1p3/course_groups.py rename to src/pylti1p3/course_groups.py diff --git a/pylti1p3/deep_link.py b/src/pylti1p3/deep_link.py similarity index 100% rename from pylti1p3/deep_link.py rename to src/pylti1p3/deep_link.py diff --git a/pylti1p3/deep_link_resource.py b/src/pylti1p3/deep_link_resource.py similarity index 100% rename from pylti1p3/deep_link_resource.py rename to src/pylti1p3/deep_link_resource.py diff --git a/pylti1p3/deployment.py b/src/pylti1p3/deployment.py similarity index 100% rename from pylti1p3/deployment.py rename to src/pylti1p3/deployment.py diff --git a/pylti1p3/exception.py b/src/pylti1p3/exception.py similarity index 100% rename from pylti1p3/exception.py rename to src/pylti1p3/exception.py diff --git a/pylti1p3/grade.py b/src/pylti1p3/grade.py similarity index 100% rename from pylti1p3/grade.py rename to src/pylti1p3/grade.py diff --git a/pylti1p3/launch_data_storage/__init__.py b/src/pylti1p3/launch_data_storage/__init__.py similarity index 100% rename from pylti1p3/launch_data_storage/__init__.py rename to src/pylti1p3/launch_data_storage/__init__.py diff --git a/pylti1p3/launch_data_storage/base.py b/src/pylti1p3/launch_data_storage/base.py similarity index 100% rename from pylti1p3/launch_data_storage/base.py rename to src/pylti1p3/launch_data_storage/base.py diff --git a/pylti1p3/launch_data_storage/cache.py b/src/pylti1p3/launch_data_storage/cache.py similarity index 100% rename from pylti1p3/launch_data_storage/cache.py rename to src/pylti1p3/launch_data_storage/cache.py diff --git a/pylti1p3/launch_data_storage/session.py b/src/pylti1p3/launch_data_storage/session.py similarity index 100% rename from pylti1p3/launch_data_storage/session.py rename to src/pylti1p3/launch_data_storage/session.py diff --git a/pylti1p3/lineitem.py b/src/pylti1p3/lineitem.py similarity index 100% rename from pylti1p3/lineitem.py rename to src/pylti1p3/lineitem.py diff --git a/pylti1p3/message_launch.py b/src/pylti1p3/message_launch.py similarity index 100% rename from pylti1p3/message_launch.py rename to src/pylti1p3/message_launch.py diff --git a/pylti1p3/message_validators/__init__.py b/src/pylti1p3/message_validators/__init__.py similarity index 100% rename from pylti1p3/message_validators/__init__.py rename to src/pylti1p3/message_validators/__init__.py diff --git a/pylti1p3/message_validators/abstract.py b/src/pylti1p3/message_validators/abstract.py similarity index 100% rename from pylti1p3/message_validators/abstract.py rename to src/pylti1p3/message_validators/abstract.py diff --git a/pylti1p3/message_validators/deep_link.py b/src/pylti1p3/message_validators/deep_link.py similarity index 100% rename from pylti1p3/message_validators/deep_link.py rename to src/pylti1p3/message_validators/deep_link.py diff --git a/pylti1p3/message_validators/privacy_launch.py b/src/pylti1p3/message_validators/privacy_launch.py similarity index 100% rename from pylti1p3/message_validators/privacy_launch.py rename to src/pylti1p3/message_validators/privacy_launch.py diff --git a/pylti1p3/message_validators/resource_message.py b/src/pylti1p3/message_validators/resource_message.py similarity index 100% rename from pylti1p3/message_validators/resource_message.py rename to src/pylti1p3/message_validators/resource_message.py diff --git a/pylti1p3/message_validators/submission_review.py b/src/pylti1p3/message_validators/submission_review.py similarity index 100% rename from pylti1p3/message_validators/submission_review.py rename to src/pylti1p3/message_validators/submission_review.py diff --git a/pylti1p3/names_roles.py b/src/pylti1p3/names_roles.py similarity index 100% rename from pylti1p3/names_roles.py rename to src/pylti1p3/names_roles.py diff --git a/pylti1p3/oidc_login.py b/src/pylti1p3/oidc_login.py similarity index 100% rename from pylti1p3/oidc_login.py rename to src/pylti1p3/oidc_login.py diff --git a/pylti1p3/py.typed b/src/pylti1p3/py.typed similarity index 100% rename from pylti1p3/py.typed rename to src/pylti1p3/py.typed diff --git a/pylti1p3/redirect.py b/src/pylti1p3/redirect.py similarity index 100% rename from pylti1p3/redirect.py rename to src/pylti1p3/redirect.py diff --git a/pylti1p3/registration.py b/src/pylti1p3/registration.py similarity index 100% rename from pylti1p3/registration.py rename to src/pylti1p3/registration.py diff --git a/pylti1p3/request.py b/src/pylti1p3/request.py similarity index 100% rename from pylti1p3/request.py rename to src/pylti1p3/request.py diff --git a/pylti1p3/roles.py b/src/pylti1p3/roles.py similarity index 100% rename from pylti1p3/roles.py rename to src/pylti1p3/roles.py diff --git a/pylti1p3/service_connector.py b/src/pylti1p3/service_connector.py similarity index 100% rename from pylti1p3/service_connector.py rename to src/pylti1p3/service_connector.py diff --git a/pylti1p3/session.py b/src/pylti1p3/session.py similarity index 100% rename from pylti1p3/session.py rename to src/pylti1p3/session.py diff --git a/pylti1p3/tool_config/__init__.py b/src/pylti1p3/tool_config/__init__.py similarity index 100% rename from pylti1p3/tool_config/__init__.py rename to src/pylti1p3/tool_config/__init__.py diff --git a/pylti1p3/tool_config/abstract.py b/src/pylti1p3/tool_config/abstract.py similarity index 100% rename from pylti1p3/tool_config/abstract.py rename to src/pylti1p3/tool_config/abstract.py diff --git a/pylti1p3/tool_config/dict.py b/src/pylti1p3/tool_config/dict.py similarity index 100% rename from pylti1p3/tool_config/dict.py rename to src/pylti1p3/tool_config/dict.py diff --git a/pylti1p3/tool_config/json_file.py b/src/pylti1p3/tool_config/json_file.py similarity index 100% rename from pylti1p3/tool_config/json_file.py rename to src/pylti1p3/tool_config/json_file.py diff --git a/pylti1p3/tool_config/py.typed b/src/pylti1p3/tool_config/py.typed similarity index 100% rename from pylti1p3/tool_config/py.typed rename to src/pylti1p3/tool_config/py.typed diff --git a/pylti1p3/utils.py b/src/pylti1p3/utils.py similarity index 100% rename from pylti1p3/utils.py rename to src/pylti1p3/utils.py diff --git a/tox.ini b/tox.ini index 51058bd..b046360 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,8 @@ envlist = py36, py37, py38, py39, py310, py311 commands = flake8 . pylint --rcfile=pylintrc pylti1p3 tests - mypy pylti1p3 - black . --check --diff + mypy src + black src --check --diff coverage run -m unittest -v tests coverage report -m deps = From f68144523e339f22ce406a5cd6b7e9ac2438e003 Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Fri, 7 Jun 2024 16:41:35 +0100 Subject: [PATCH 3/6] flake8 and pylint ignore migrations They're automatically generated, so I don't think we need to lint them. --- pylintrc | 4 ++-- setup.cfg | 2 +- tox.ini | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pylintrc b/pylintrc index 948bbe4..450a5aa 100644 --- a/pylintrc +++ b/pylintrc @@ -42,13 +42,13 @@ fail-under=10 #from-stdin= # Files or directories to be skipped. They should be base names, not paths. -ignore=CVS +ignore= # Add files or directories matching the regular expressions patterns to the # ignore-list. The regex matches against paths and can be in Posix or Windows # format. Because '\' represents the directory delimiter on Windows systems, it # can't be used as an escape character. -ignore-paths= +ignore-paths=^.*/migrations/.*$ # Files or directories matching the regular expression patterns are skipped. # The regex matches against base names, not paths. The default value ignores diff --git a/setup.cfg b/setup.cfg index 2cd4d0c..769f74c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ license_file = LICENSE [flake8] max_line_length=120 -exclude=.git,.idea,.tox,build,venv,venv3,env +exclude=.git,.idea,.tox,build,venv,venv3,env,migrations extend-ignore=E203 [coverage:run] diff --git a/tox.ini b/tox.ini index b046360..427cf1f 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ commands = flake8 . pylint --rcfile=pylintrc pylti1p3 tests mypy src - black src --check --diff + black src --check --diff --extend-exclude ".*/migrations/.*" coverage run -m unittest -v tests coverage report -m deps = From 7c43bade60d65b532c7aaeed82aefe28987a6e34 Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Fri, 7 Jun 2024 16:41:35 +0100 Subject: [PATCH 4/6] pylintrc: fix overgeneral-exceptions Apparently pylint wants you to get BaseException and Exception from builtins --- pylintrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pylintrc b/pylintrc index 450a5aa..ae37809 100644 --- a/pylintrc +++ b/pylintrc @@ -307,8 +307,8 @@ min-public-methods=2 [EXCEPTIONS] # Exceptions that will emit a warning when caught. -overgeneral-exceptions=BaseException, - Exception +overgeneral-exceptions=builtins.BaseException, + builtins.Exception [FORMAT] From 551ad022fcb8774da00356578aae5ffaa602060a Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Fri, 7 Jun 2024 16:41:35 +0100 Subject: [PATCH 5/6] Changes to appease pylint As well as formatting changes, there are a few methods which pylint said had too many positional arguments, so I've changed them to keyword arguments. Anything using those methods will have to make sure the keyword arguments are named. --- pylintrc | 3 ++- src/pylti1p3/assignments_grades.py | 4 ++-- .../django/lti1p3_tool_config/__init__.py | 1 - .../django/lti1p3_tool_config/models.py | 6 +++--- src/pylti1p3/contrib/django/message_launch.py | 13 +++++++------ src/pylti1p3/contrib/django/oidc_login.py | 11 ++++++----- src/pylti1p3/contrib/django/request.py | 19 ++++++++----------- src/pylti1p3/contrib/flask/cookie.py | 16 ++++++++-------- src/pylti1p3/contrib/flask/message_launch.py | 13 +++++++------ src/pylti1p3/contrib/flask/oidc_login.py | 7 ++++++- src/pylti1p3/cookies_allowed_check.py | 2 +- src/pylti1p3/message_launch.py | 2 ++ src/pylti1p3/oidc_login.py | 3 ++- src/pylti1p3/service_connector.py | 1 + src/pylti1p3/session.py | 2 +- src/pylti1p3/tool_config/abstract.py | 12 ++++++------ tests/base.py | 1 + tests/django_mixin.py | 2 ++ tests/flask_mixin.py | 2 ++ tests/request.py | 4 +++- tests/test_grades.py | 2 +- tests/test_resource_link.py | 19 ++++++++++++------- tox.ini | 2 ++ 23 files changed, 85 insertions(+), 62 deletions(-) diff --git a/pylintrc b/pylintrc index ae37809..c1e3810 100644 --- a/pylintrc +++ b/pylintrc @@ -77,7 +77,7 @@ limit-inference-results=100 # List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. -load-plugins= +load-plugins=pylint_django # Pickle collected data for later comparisons. persistent=yes @@ -428,6 +428,7 @@ disable=bad-inline-option, use-symbolic-message-instead, useless-option-value, useless-suppression, + django-not-configured, # Enable the message, report, category or checker with the given id(s). You can diff --git a/src/pylti1p3/assignments_grades.py b/src/pylti1p3/assignments_grades.py index 2b492b6..c7ad738 100644 --- a/src/pylti1p3/assignments_grades.py +++ b/src/pylti1p3/assignments_grades.py @@ -250,8 +250,8 @@ def find_or_create_lineitem( raise LtiException("Can't create lineitem: Missing required scope") response = self._service_connector.make_service_request( - self._service_data["scope"], - self._service_data["lineitems"], + scopes=self._service_data["scope"], + url=self._service_data["lineitems"], is_post=True, data=new_lineitem.get_value(), content_type="application/vnd.ims.lis.v2.lineitem+json", diff --git a/src/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py index ed5a3bc..0754f45 100644 --- a/src/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py +++ b/src/pylti1p3/contrib/django/lti1p3_tool_config/__init__.py @@ -1,5 +1,4 @@ import json - from pylti1p3.deployment import Deployment from pylti1p3.exception import LtiException from pylti1p3.registration import Registration diff --git a/src/pylti1p3/contrib/django/lti1p3_tool_config/models.py b/src/pylti1p3/contrib/django/lti1p3_tool_config/models.py index 6e5ff73..617768c 100644 --- a/src/pylti1p3/contrib/django/lti1p3_tool_config/models.py +++ b/src/pylti1p3/contrib/django/lti1p3_tool_config/models.py @@ -170,9 +170,9 @@ def to_dict(self): "auth_audience": self.auth_audience, "key_set_url": self.key_set_url, "key_set": json.loads(self.key_set) if self.key_set else None, - "deployment_ids": json.loads(self.deployment_ids) - if self.deployment_ids - else [], + "deployment_ids": ( + json.loads(self.deployment_ids) if self.deployment_ids else [] + ), } return data diff --git a/src/pylti1p3/contrib/django/message_launch.py b/src/pylti1p3/contrib/django/message_launch.py index 06c4cd1..c6dda08 100644 --- a/src/pylti1p3/contrib/django/message_launch.py +++ b/src/pylti1p3/contrib/django/message_launch.py @@ -10,6 +10,7 @@ def __init__( self, request, tool_config, + *, session_service=None, cookie_service=None, launch_data_storage=None, @@ -27,12 +28,12 @@ def __init__( session_service if session_service else DjangoSessionService(request) ) super().__init__( - django_request, - tool_config, - session_service, - cookie_service, - launch_data_storage, - requests_session, + request=django_request, + tool_config=tool_config, + session_service=session_service, + cookie_service=cookie_service, + launch_data_storage=launch_data_storage, + requests_session=requests_session, ) def _get_request_param(self, key): diff --git a/src/pylti1p3/contrib/django/oidc_login.py b/src/pylti1p3/contrib/django/oidc_login.py index fd6a828..b75546c 100644 --- a/src/pylti1p3/contrib/django/oidc_login.py +++ b/src/pylti1p3/contrib/django/oidc_login.py @@ -13,6 +13,7 @@ def __init__( self, request, tool_config, + *, session_service=None, cookie_service=None, launch_data_storage=None, @@ -27,11 +28,11 @@ def __init__( session_service if session_service else DjangoSessionService(request) ) super().__init__( - django_request, - tool_config, - session_service, - cookie_service, - launch_data_storage, + request=django_request, + tool_config=tool_config, + session_service=session_service, + cookie_service=cookie_service, + launch_data_storage=launch_data_storage, ) def get_redirect(self, url): diff --git a/src/pylti1p3/contrib/django/request.py b/src/pylti1p3/contrib/django/request.py index bc314c3..28763a6 100644 --- a/src/pylti1p3/contrib/django/request.py +++ b/src/pylti1p3/contrib/django/request.py @@ -2,31 +2,28 @@ class DjangoRequest(Request): - _request = None + request = None _post_only = False _default_params = None @property def session(self): - return self._request.session + return self.request.session def __init__(self, request, post_only=False, default_params=None): - self.set_request(request) + self.request = request self._post_only = post_only self._default_params = default_params if default_params else {} - def set_request(self, request): - self._request = request - def get_param(self, key): if self._post_only: - return self._request.POST.get(key, self._default_params.get(key)) - return self._request.GET.get( - key, self._request.POST.get(key, self._default_params.get(key)) + return self.request.POST.get(key, self._default_params.get(key)) + return self.request.GET.get( + key, self.request.POST.get(key, self._default_params.get(key)) ) def get_cookie(self, key): - return self._request.COOKIES.get(key) + return self.request.COOKIES.get(key) def is_secure(self): - return self._request.is_secure() + return self.request.is_secure() diff --git a/src/pylti1p3/contrib/flask/cookie.py b/src/pylti1p3/contrib/flask/cookie.py index 775855f..b9973fc 100644 --- a/src/pylti1p3/contrib/flask/cookie.py +++ b/src/pylti1p3/contrib/flask/cookie.py @@ -20,14 +20,14 @@ def set_cookie(self, name, value, exp=3600): def update_response(self, response): for key, cookie_data in self._cookie_data_to_set.items(): - cookie_kwargs = dict( - key=key, - value=cookie_data["value"], - max_age=cookie_data["exp"], - secure=self._request.is_secure(), - path="/", - httponly=True, - ) + cookie_kwargs = { + "key": key, + "value": cookie_data["value"], + "max_age": cookie_data["exp"], + "secure": self._request.is_secure(), + "path": "/", + "httponly": True, + } if self._request.is_secure(): cookie_kwargs["samesite"] = "None" diff --git a/src/pylti1p3/contrib/flask/message_launch.py b/src/pylti1p3/contrib/flask/message_launch.py index b811995..427a8b0 100644 --- a/src/pylti1p3/contrib/flask/message_launch.py +++ b/src/pylti1p3/contrib/flask/message_launch.py @@ -8,6 +8,7 @@ def __init__( self, request, tool_config, + *, session_service=None, cookie_service=None, launch_data_storage=None, @@ -20,12 +21,12 @@ def __init__( session_service if session_service else FlaskSessionService(request) ) super().__init__( - request, - tool_config, - session_service, - cookie_service, - launch_data_storage, - requests_session, + request=request, + tool_config=tool_config, + session_service=session_service, + cookie_service=cookie_service, + launch_data_storage=launch_data_storage, + requests_session=requests_session, ) def _get_request_param(self, key): diff --git a/src/pylti1p3/contrib/flask/oidc_login.py b/src/pylti1p3/contrib/flask/oidc_login.py index b0fee37..f0a2a94 100644 --- a/src/pylti1p3/contrib/flask/oidc_login.py +++ b/src/pylti1p3/contrib/flask/oidc_login.py @@ -10,6 +10,7 @@ def __init__( self, request, tool_config, + *, session_service=None, cookie_service=None, launch_data_storage=None, @@ -21,7 +22,11 @@ def __init__( session_service if session_service else FlaskSessionService(request) ) super().__init__( - request, tool_config, session_service, cookie_service, launch_data_storage + request=request, + tool_config=tool_config, + session_service=session_service, + cookie_service=cookie_service, + launch_data_storage=launch_data_storage, ) def get_redirect(self, url): diff --git a/src/pylti1p3/cookies_allowed_check.py b/src/pylti1p3/cookies_allowed_check.py index 388d924..5b75e0c 100644 --- a/src/pylti1p3/cookies_allowed_check.py +++ b/src/pylti1p3/cookies_allowed_check.py @@ -18,7 +18,7 @@ def __init__( click_text: str, loading_text: str, *args, - **kwargs + **kwargs, ): # pylint: disable=unused-argument self._params = params diff --git a/src/pylti1p3/message_launch.py b/src/pylti1p3/message_launch.py index 4143900..a5f2c04 100644 --- a/src/pylti1p3/message_launch.py +++ b/src/pylti1p3/message_launch.py @@ -204,6 +204,7 @@ def __init__( self, request: REQ, tool_config: TCONF, + *, session_service: t.Optional[SES] = None, cookie_service: t.Optional[COOK] = None, launch_data_storage: t.Optional[LaunchDataStorage[t.Any]] = None, @@ -279,6 +280,7 @@ def from_cache( launch_id: str, request: REQ, tool_config: TCONF, + *, session_service: t.Optional[SES] = None, cookie_service: t.Optional[COOK] = None, launch_data_storage: t.Optional[LaunchDataStorage[t.Any]] = None, diff --git a/src/pylti1p3/oidc_login.py b/src/pylti1p3/oidc_login.py index 6d23e01..3496176 100644 --- a/src/pylti1p3/oidc_login.py +++ b/src/pylti1p3/oidc_login.py @@ -45,6 +45,7 @@ def __init__( self, request: REQ, tool_config: TCONF, + *, session_service: SES, cookie_service: COOK, launch_data_storage: t.Optional[LaunchDataStorage[t.Any]] = None, @@ -204,7 +205,7 @@ def enable_check_cookies( main_msg: t.Optional[str] = None, click_msg: t.Optional[str] = None, loading_msg: t.Optional[str] = None, - **kwargs + **kwargs, ) -> "OIDCLogin": # pylint: disable=unused-argument self._cookies_check = True diff --git a/src/pylti1p3/service_connector.py b/src/pylti1p3/service_connector.py index d50149f..c391039 100644 --- a/src/pylti1p3/service_connector.py +++ b/src/pylti1p3/service_connector.py @@ -108,6 +108,7 @@ def make_service_request( self, scopes: t.Sequence[str], url: str, + *, is_post: bool = False, data: t.Optional[str] = None, content_type: str = "application/json", diff --git a/src/pylti1p3/session.py b/src/pylti1p3/session.py index e28078b..75a26ab 100644 --- a/src/pylti1p3/session.py +++ b/src/pylti1p3/session.py @@ -66,5 +66,5 @@ def set_launch_data_lifetime(self, time_sec: int): else: raise Exception( f"{self.data_storage.__class__.__name__} launch storage doesn't support " - f"manual change expiration of the keys" + f"changing the expiration time of keys" ) diff --git a/src/pylti1p3/tool_config/abstract.py b/src/pylti1p3/tool_config/abstract.py index e97d07e..84847fc 100644 --- a/src/pylti1p3/tool_config/abstract.py +++ b/src/pylti1p3/tool_config/abstract.py @@ -39,14 +39,14 @@ def check_iss_has_many_clients(self, iss: str) -> bool: return iss_type == IssuerToClientRelation.MANY_CLIENTS_IDS_PER_ISSUER def set_iss_has_one_client(self, iss: str): - self.issuers_relation_types[ - iss - ] = IssuerToClientRelation.ONE_CLIENT_ID_PER_ISSUER + self.issuers_relation_types[iss] = ( + IssuerToClientRelation.ONE_CLIENT_ID_PER_ISSUER + ) def set_iss_has_many_clients(self, iss: str): - self.issuers_relation_types[ - iss - ] = IssuerToClientRelation.MANY_CLIENTS_IDS_PER_ISSUER + self.issuers_relation_types[iss] = ( + IssuerToClientRelation.MANY_CLIENTS_IDS_PER_ISSUER + ) def find_registration(self, iss: str, *args, **kwargs) -> Registration: """ diff --git a/tests/base.py b/tests/base.py index f9ea55d..5456f75 100644 --- a/tests/base.py +++ b/tests/base.py @@ -20,6 +20,7 @@ def _launch( self, request, tool_conf, + *, key_set_url_response=None, force_validation=False, cache=False, diff --git a/tests/django_mixin.py b/tests/django_mixin.py index aa6d7c9..7fba0a7 100644 --- a/tests/django_mixin.py +++ b/tests/django_mixin.py @@ -10,6 +10,7 @@ class DjangoMixin: def _get_request( self, + *, login_request, login_response, request_is_secure=False, @@ -29,6 +30,7 @@ def _get_request( def _make_oidc_login( self, + *, uuid_val=None, tool_conf_cls=None, secure=False, diff --git a/tests/flask_mixin.py b/tests/flask_mixin.py index b2bb514..7bd3beb 100644 --- a/tests/flask_mixin.py +++ b/tests/flask_mixin.py @@ -18,6 +18,7 @@ def _get_request( self, login_request, login_response, + *, request_is_secure=False, post_data=None, empty_session=False, @@ -37,6 +38,7 @@ def _get_request( def _make_oidc_login( self, + *, uuid_val=None, tool_conf_cls=None, secure=False, diff --git a/tests/request.py b/tests/request.py index a2e1076..e5bdaaa 100644 --- a/tests/request.py +++ b/tests/request.py @@ -8,7 +8,9 @@ class FakeRequest: session = None secure = False - def __init__(self, get=None, post=None, cookies=None, session=None, secure=False): + def __init__( + self, *, get=None, post=None, cookies=None, session=None, secure=False + ): self.GET = get if get else {} self.POST = post if post else {} self.COOKIES = cookies if cookies else {} diff --git a/tests/test_grades.py b/tests/test_grades.py index e83a0d4..a16fae1 100644 --- a/tests/test_grades.py +++ b/tests/test_grades.py @@ -91,7 +91,7 @@ def test_get_grades( line_item = ags.find_or_create_lineitem(score_line_item) self.assertIsNotNone(line_item) - scores = ags.get_grades(line_item) + scores = list(ags.get_grades(line_item)) self.assertEqual(len(scores), 1) self.assertDictEqual( scores[0], diff --git a/tests/test_resource_link.py b/tests/test_resource_link.py index cb669c4..e402e78 100644 --- a/tests/test_resource_link.py +++ b/tests/test_resource_link.py @@ -176,6 +176,7 @@ class ResourceLinkBase(TestLinkBase): def _launch_success( self, + *, tool_conf_cls=None, secure=False, tool_conf_extended=False, @@ -204,9 +205,11 @@ def _launch_success( ] ) def test_res_link_launch_success( - self, name, secure, tool_conf_extended # pylint: disable=unused-argument - ): - self._launch_success(None, secure, tool_conf_extended) + self, name, secure, tool_conf_extended + ): # pylint: disable=unused-argument,too-many-positional-arguments,too-many-function-args + self._launch_success( + tool_conf_cls=None, secure=secure, tool_conf_extended=tool_conf_extended + ) def test_res_link_check_cookies_page(self): self._launch_success(enable_check_cookies=True) @@ -219,7 +222,9 @@ def test_res_link_launch_invalid_public_key(self): launch_request = self._get_request(login_request, login_response) with self.assertRaisesRegex(LtiException, "Invalid response"): - self._launch(launch_request, tool_conf, "invalid_key_set") + self._launch( + launch_request, tool_conf, key_set_url_response="invalid_key_set" + ) def test_res_link_launch_invalid_state(self): tool_conf, login_request, login_response = self._make_oidc_login() @@ -293,9 +298,9 @@ def _get_data_with_invalid_deployment( def _get_data_with_invalid_message(self, *args): # pylint: disable=unused-argument message_launch_data = self.expected_message_launch_data.copy() - message_launch_data[ - "https://purl.imsglobal.org/spec/lti/claim/version" - ] = "1.2.0" + message_launch_data["https://purl.imsglobal.org/spec/lti/claim/version"] = ( + "1.2.0" + ) return message_launch_data def test_res_link_launch_invalid_nonce(self): diff --git a/tox.ini b/tox.ini index 427cf1f..3e066eb 100644 --- a/tox.ini +++ b/tox.ini @@ -24,3 +24,5 @@ deps = requests requests-mock types-requests + django-types + pylint-django From a4b4ceebf00b2901873ae97dcda923eae9e32afa Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Fri, 16 Feb 2024 10:09:07 +0000 Subject: [PATCH 6/6] deep link: support HTML content type The DeepLink object now has a `get_accept_types` method which will return the list of content types that the platform will accept for this launch. The DeepLinkResource object now has `get_html` and `set_html` methods, which are used when the type is `"html"`. --- src/pylti1p3/deep_link.py | 3 +++ src/pylti1p3/deep_link_resource.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/pylti1p3/deep_link.py b/src/pylti1p3/deep_link.py index fdce52c..8a4498a 100644 --- a/src/pylti1p3/deep_link.py +++ b/src/pylti1p3/deep_link.py @@ -66,6 +66,9 @@ def get_message_jwt( } return message_jwt + def get_accept_types(self) -> t.Sequence[str]: + return self._deep_link_settings.get("accept_types", []) + def encode_jwt(self, message): headers = None kid = self._registration.get_kid() diff --git a/src/pylti1p3/deep_link_resource.py b/src/pylti1p3/deep_link_resource.py index 131606f..9ec6a4b 100644 --- a/src/pylti1p3/deep_link_resource.py +++ b/src/pylti1p3/deep_link_resource.py @@ -7,6 +7,7 @@ class DeepLinkResource: _title: t.Optional[str] = None _url: t.Optional[str] = None _lineitem: t.Optional[LineItem] = None + _html: t.Optional[str] = None _custom_params: t.Mapping[str, str] = {} _target: str = "iframe" _icon_url: t.Optional[str] = None @@ -60,6 +61,13 @@ def set_icon_url(self, value: str) -> "DeepLinkResource": self._icon_url = value return self + def set_html(self, value: str) -> "DeepLinkResource": + self._html = value + return self + + def get_html(self) -> t.Optional[str]: + return self._html + def to_dict(self) -> t.Dict[str, object]: res: t.Dict[str, object] = { "type": self._type, @@ -90,6 +98,9 @@ def to_dict(self) -> t.Dict[str, object]: res["lineItem"] = line_item + if self._html: + res["html"] = self._html + if self._icon_url: res["icon"] = {"url": self._icon_url}