Skip to content

Commit e5a6caa

Browse files
committed
[FEATURE] Add SDK language and version to request metadata - LRN-48968
1 parent 6b1c9c1 commit e5a6caa

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

docs/quickstart/assessment/standalone_assessment.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# Include server side Learnosity SDK, and set up variables related to user access
88
from learnosity_sdk.request import Init, DataApi
99
from learnosity_sdk.utils import Uuid
10+
from learnosity_sdk._version import __version__
1011
from .. import config # Load consumer key and secret from config.py
1112
# Include web server and Jinja templating libraries.
1213
from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -547,7 +548,6 @@ def do_GET(self) -> None:
547548
<body>
548549
<h1>{{ name }}</h1>
549550
<p>This demo shows how to use the Data API to retrieve items from the Learnosity itembank.</p>
550-
551551
<div class="demo-section">
552552
<h2>Demo 1: Manual Iteration (5 items)</h2>
553553
<p>Using <code>request()</code> method with manual pagination via the 'next' pointer.</p>
@@ -565,7 +565,10 @@ def do_GET(self) -> None:
565565
<p>Using <code>results_iter()</code> method to automatically iterate over individual items.</p>
566566
{{ demo3_output }}
567567
</div>
568-
568+
<div class="demo-section">
569+
<h2>Request Metadata</h2>
570+
{{ metadata_info }}
571+
</div>
569572
<p><a href="/">Back to API Examples</a></p>
570573
</body>
571574
</html>
@@ -579,6 +582,22 @@ def do_GET(self) -> None:
579582
}
580583
data_api = DataApi()
581584

585+
# Extract and display metadata that will be sent with requests
586+
consumer = data_api._extract_consumer(security_packet)
587+
action = data_api._derive_action(itembank_uri, 'get')
588+
sdk_version = __version__.lstrip('v')
589+
sdk_info = f'Python:{sdk_version}'
590+
591+
metadata_html = f"""
592+
<div>
593+
<strong>SDK:</strong> {sdk_info}
594+
<br>
595+
<strong>Consumer:</strong> {consumer}
596+
<br>
597+
<strong>Action:</strong> {action}
598+
</div>
599+
"""
600+
582601
# Demo 1: Manual iteration
583602
demo1_html = ""
584603
try:
@@ -652,6 +671,7 @@ def do_GET(self) -> None:
652671

653672
response = template.render(
654673
name='Data API Example',
674+
metadata_info=metadata_html,
655675
demo1_output=demo1_html,
656676
demo2_output=demo2_html,
657677
demo3_output=demo3_html

learnosity_sdk/request/dataapi.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from learnosity_sdk.exceptions import DataApiException
99
from learnosity_sdk.request import Init
10+
from learnosity_sdk._version import __version__
1011

1112

1213
class DataApi(object):
@@ -46,9 +47,9 @@ def _derive_action(self, endpoint: str, action: str) -> str:
4647
path_parts = path.split('/')
4748
if len(path_parts) > 1:
4849
first_segment = path_parts[1].lower()
49-
# Match version patterns: v1, v2, v2023.1.lts, etc.
50+
# Match version patterns: v1, v2, v2023.1.lts, v2025.3.preview1, etc.
5051
# Also match: latest, latest-lts, developer
51-
if (re.fullmatch(r"v[\d.]+(?:\.lts)?", first_segment) or
52+
if (re.fullmatch(r"v[\d.]+(?:\.(?:lts|preview\d+))?", first_segment) or
5253
first_segment in ("latest", "latest-lts", "developer")):
5354
path = '/' + '/'.join(path_parts[2:])
5455

@@ -84,9 +85,11 @@ def request(self, endpoint: str, security_packet: Dict[str, str],
8485
derived_action = self._derive_action(endpoint, action)
8586

8687
# Add metadata as HTTP headers for ALB routing
88+
sdk_version = __version__.lstrip('v')
8789
headers = {
8890
'X-Learnosity-Consumer': consumer,
89-
'X-Learnosity-Action': derived_action
91+
'X-Learnosity-Action': derived_action,
92+
'X-Learnosity-SDK': f'Python:{sdk_version}'
9093
}
9194

9295
return requests.post(endpoint, data=init.generate(), headers=headers)

tests/unit/test_dataapi.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ def test_request(self) -> None:
6363
assert responses.calls[0].request.headers['X-Learnosity-Consumer'] == 'yis0TYCu7U9V4o7M'
6464
assert 'X-Learnosity-Action' in responses.calls[0].request.headers
6565
assert responses.calls[0].request.headers['X-Learnosity-Action'] == 'get_/itembank/items'
66+
assert 'X-Learnosity-SDK' in responses.calls[0].request.headers
67+
# Verify SDK header format is "Python:X.Y.Z" (without 'v' prefix)
68+
sdk_header = responses.calls[0].request.headers['X-Learnosity-SDK']
69+
assert sdk_header.startswith('Python:')
70+
assert not sdk_header.startswith('Python:v')
6671

6772
@responses.activate
6873
def test_request_iter(self) -> None:
@@ -203,6 +208,19 @@ def test_derive_action_with_developer(self) -> None:
203208
action = client._derive_action('https://data.learnosity.com/developer/sessions/responses', 'get')
204209
assert action == 'get_/sessions/responses'
205210

211+
def test_derive_action_with_preview_version(self) -> None:
212+
"""Verify that preview version format like v2025.3.preview1 is correctly stripped"""
213+
client = DataApi()
214+
action = client._derive_action('https://data.learnosity.com/v2025.3.preview1/itembank/items', 'get')
215+
assert action == 'get_/itembank/items'
216+
217+
def test_derive_action_with_preview_version_multi_digit(self) -> None:
218+
"""Verify that preview version with multi-digit preview number is correctly stripped"""
219+
client = DataApi()
220+
action = client._derive_action('https://data.learnosity.com/v2025.1.preview123/itembank/questions', 'get')
221+
assert action == 'get_/itembank/questions'
222+
223+
206224
@responses.activate
207225
def test_metadata_headers_in_paginated_requests(self) -> None:
208226
"""Verify that metadata headers are sent in all paginated requests"""
@@ -220,3 +238,8 @@ def test_metadata_headers_in_paginated_requests(self) -> None:
220238
assert call.request.headers['X-Learnosity-Consumer'] == 'yis0TYCu7U9V4o7M'
221239
assert 'X-Learnosity-Action' in call.request.headers
222240
assert call.request.headers['X-Learnosity-Action'] == 'get_/itembank/items'
241+
assert 'X-Learnosity-SDK' in call.request.headers
242+
# Verify SDK header format is "Python:X.Y.Z" (without 'v' prefix)
243+
sdk_header = call.request.headers['X-Learnosity-SDK']
244+
assert sdk_header.startswith('Python:')
245+
assert not sdk_header.startswith('Python:v')

0 commit comments

Comments
 (0)