{"id":9224,"library":"pylti1p3","title":"pylti1p3: LTI 1.3 Advantage Tool","description":"pylti1p3 is a Python library implementing the LTI 1.3 Advantage Tool specification, enabling seamless integration with LTI 1.3 platforms. It handles OAuth 2.0, JWT validation, deep linking, and various LTI services. The current version is 2.0.0, with an active release cadence addressing new LTI features, bug fixes, and Python compatibility.","status":"active","version":"2.0.0","language":"en","source_language":"en","source_url":"https://github.com/dmitry-viskov/pylti1.3","tags":["LTI","LTI 1.3","Education","e-learning","OAuth","Security","IMS Global"],"install":[{"cmd":"pip install pylti1p3","lang":"bash","label":"Install stable release"}],"dependencies":[{"reason":"Required for JWT (JSON Web Token) handling, used in LTI 1.3 security and message signing/verification.","package":"PyJWT","optional":false},{"reason":"Used for making HTTP requests to LTI platforms for services like Assignments and Grades, Names and Role Provisioning.","package":"requests","optional":false},{"reason":"Provides cryptographic primitives for secure operations, including key handling for JWT signing.","package":"cryptography","optional":false},{"reason":"Used for parsing XML, specifically for IMS Global's OneRoster and other XML-based LTI specifications if applicable.","package":"lxml","optional":false}],"imports":[{"symbol":"ToolConfig","correct":"from pylti1p3.tool_config import ToolConfig"},{"symbol":"MessageLaunch","correct":"from pylti1p3.message_launch import MessageLaunch"},{"symbol":"ServiceConnector","correct":"from pylti1p3.service_connector import ServiceConnector"},{"symbol":"AssignmentsGradesService","correct":"from pylti1p3.grade import AssignmentsGradesService"},{"symbol":"NamesRolesProvisioningService","correct":"from pylti1p3.grade import NamesRolesProvisioningService"}],"quickstart":{"code":"import os\nimport json\nfrom pylti1p3.tool_config import ToolConfig\nfrom pylti1p3.message_launch import MessageLaunch\n\n# Mock a minimal request object for demonstration\n# In a real application, this would come from your web framework (e.g., Flask, Django)\nclass MockRequest:\n    def __init__(self, method='POST', headers=None, form=None, data=None):\n        self.method = method\n        self.headers = headers or {}\n        self.form = form or {}\n        self.data = data # raw body for content_type application/json, etc.\n\n    def get_json(self):\n        return json.loads(self.data) if self.data and 'application/json' in self.headers.get('Content-Type', '') else None\n\n    def get_param(self, key, default=None):\n        return self.form.get(key, default)\n\n\n# 1. Configure the LTI Tool\n# These values would typically come from environment variables, database, or a configuration file\niss = os.environ.get('LTI_ISS', 'https://example.com')\nclient_id = os.environ.get('LTI_CLIENT_ID', 'your-client-id')\njwks_url = os.environ.get('LTI_JWKS_URL', 'https://example.com/platform/.well-known/jwks.json')\nauth_login_url = os.environ.get('LTI_AUTH_LOGIN_URL', 'https://example.com/platform/login_initiations')\nauth_token_url = os.environ.get('LTI_AUTH_TOKEN_URL', 'https://example.com/platform/access_token')\ndeployment_id = os.environ.get('LTI_DEPLOYMENT_ID', '1') # Example deployment ID\n\n# Your tool's private key (for signing messages sent *to* the platform)\n# In a real app, this would be loaded from a file or secure store\nprivate_key = os.environ.get('LTI_TOOL_PRIVATE_KEY', '-----BEGIN RSA PRIVATE KEY-----\\n...\\n-----END RSA PRIVATE KEY-----')\n\n# Your tool's public key (to be registered with the platform)\n# In a real app, this would be loaded from a file or secure store\npublic_key = os.environ.get('LTI_TOOL_PUBLIC_KEY', '-----BEGIN PUBLIC KEY-----\\n...\\n-----END PUBLIC KEY-----')\n\n\ntool_config = ToolConfig({\n    'key_set_url': jwks_url,\n    'iss': iss,\n    'client_id': client_id,\n    'deployment_ids': [deployment_id],\n    'auth_login_url': auth_login_url,\n    'auth_token_url': auth_token_url,\n    'private_key': private_key,\n    'public_key': public_key\n})\n\n\n# 2. Simulate an LTI 1.3 Message Launch request\n# This is a highly simplified mock. A real LTI launch involves a POST request\n# with a 'id_token' parameter (a signed JWT) and possibly 'state' for CSRF protection.\n# For a basic example, we'll just demonstrate setting up the MessageLaunch object.\n\n# In a real scenario, the 'id_token' would be extracted from the incoming request's form data.\n# Here, we'll use a placeholder JWT string that would normally be generated by the Platform.\n# Note: This placeholder JWT will NOT be valid for actual verification.\n# A valid JWT needs to be signed by the platform's private key and match the JWKS.\nexample_id_token = os.environ.get('LTI_EXAMPLE_ID_TOKEN', 'eyJhbGciOiJSUzI1NiIsImtpZCI6IkExMjMifQ.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwic3ViIjoiMTIzNDUifQ.S0meS1gnedT0ken')\n\nmock_form_data = {\n    'id_token': example_id_token,\n    'state': 'a_random_state_string'\n}\n\nmock_request = MockRequest(method='POST', form=mock_form_data)\n\n\n# 3. Process the LTI Message Launch\n# The MessageLaunch object requires a 'request' (your web framework's request object)\n# and the 'ToolConfig' you just created.\n\ntry:\n    message_launch = MessageLaunch(mock_request, tool_config)\n    is_valid_launch = message_launch.validate()\n\n    if is_valid_launch:\n        print(\"LTI 1.3 Message Launch validated successfully!\")\n        launch_data = message_launch.get_launch_data()\n        print(\"Launch Data (Payload):\\n\", json.dumps(launch_data, indent=2))\n\n        # Example: Accessing specific claims\n        print(\"User ID:\", message_launch.get_sub()) # 'sub' is the user ID\n        print(\"Context Title:\", message_launch.get_context_title()) # Course title\n\n        # Access LTI 1.3 services (e.g., Assignment and Grades Service)\n        # This requires the launch to contain the appropriate service context and claims\n        # if message_launch.has_ags():\n        #     print(\"Assignments and Grades Service available!\")\n        #     ags = message_launch.get_ags()\n        #     # Example: Get line items\n        #     # line_items = ags.get_lineitems()\n        #     # print(\"Line items:\", line_items)\n\n    else:\n        print(\"LTI 1.3 Message Launch validation failed.\")\n\nexcept Exception as e:\n    print(f\"An error occurred during LTI launch processing: {e}\")\n    # In a real app, handle authentication/authorization errors gracefully\n\n","lang":"python","description":"This quickstart demonstrates how to configure the `ToolConfig` and process an incoming LTI 1.3 Message Launch. It uses mock objects for the web request to make it runnable without a full web framework. In a real application, you would integrate `ToolConfig` and `MessageLaunch` with your chosen framework (e.g., Flask, Django, FastAPI) and handle the actual HTTP request object. The `id_token` in the mock request must be a valid JWT signed by the LTI platform for successful validation."},"warnings":[{"fix":"Upgrade your Python environment to 3.6 or newer. Python 3.8+ is recommended for security and continued support.","message":"Version 2.0.0 dropped support for Python 2.7 and 3.5. Applications running on these Python versions will break.","severity":"breaking","affected_versions":"2.0.0 and above"},{"fix":"Before calling `put_grade` or `get_grades`, ensure the line item exists. Use `ags.find_lineitem_by_resource_id()` or `ags.find_lineitem_by_resource_link_id()` to check, and if not found, use `ags.post_lineitem()` to create it.","message":"In version 1.12.0, the `AssignmentsGradesService.put_grade` and `AssignmentsGradesService.get_grades` methods no longer automatically create new line items if they don't exist. You must explicitly create line items before attempting to put grades or retrieve grades for them.","severity":"breaking","affected_versions":"1.12.0 and above"},{"fix":"Double-check that your `ToolConfig` reflects the exact values provided by the LTI platform, including correct URLs, client IDs, and ensuring that your private key is correctly formatted (PEM, including headers/footers) and matches the public key registered with the platform.","message":"Incorrect configuration of `ToolConfig` parameters, especially `private_key` and `public_key`, `key_set_url`, `auth_login_url`, and `auth_token_url`, is a common source of LTI launch failures.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Verify the exact casing and module name. Reinstall the library using `pip uninstall pylti1p3 && pip install pylti1p3`. If using an older version, consult its specific documentation for import paths.","cause":"This usually indicates an incorrect import path or a corrupted installation.","error":"from pylti1p3.message_launch import MessageLaunch\nImportError: cannot import name 'MessageLaunch' from 'pylti1p3.message_launch'"},{"fix":"Ensure the `key_set_url` in your `ToolConfig` is correct and accessible. Verify that the platform's public key (JWKS) has not changed and that your tool is correctly registered with the platform.","cause":"The JWT received from the LTI platform could not be verified. This often means your public key URL (JWKS URL) or the platform's JWKS is incorrect/unavailable, or the token was signed with a different key.","error":"jwt.exceptions.InvalidSignatureError: Signature verification failed"},{"fix":"Access `ToolConfig` attributes directly, e.g., `tool_config.get('key_set_url')` or `tool_config.key_set_url` if the property is exposed directly. The `ToolConfig` constructor expects a dictionary, but once instantiated, it's an object.","cause":"You are trying to access attributes of `ToolConfig` using dictionary-style access (e.g., `tool_config['key_set_url']`) instead of object-style access.","error":"TypeError: 'ToolConfig' object is not subscriptable"},{"fix":"Access claims via `message_launch.get_launch_data()` and then parse the dictionary, or use specific helper methods like `message_launch.get_target_link_uri()` if they exist in your version. For `target_link_uri`, ensure your `id_token` contains this claim. For earlier versions, you might need to manually extract from `launch_data['https://purl.imsglobal.org/spec/lti/claim/target_link_uri']`.","cause":"You are trying to access a method or attribute that either does not exist in your installed version or is not directly exposed by the `MessageLaunch` object. Specifically `target_link_uri` is a claim within the launch data.","error":"AttributeError: 'MessageLaunch' object has no attribute 'get_target_link_uri'"}]}