Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
*.egg-info
*.pyc
.tox
Expand Down
8 changes: 6 additions & 2 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic
Versioning](http://semver.org/spec/v2.0.0.html).
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- Fixed an issue where the `DataApi` class's `results_iter` method would return no data
when receiving responses from Data API endpoints that set the "`data`" field of the
response to an object (like the [itembank/questions endpoint](https://reference.learnosity.com/data-api/endpoints/itembank_endpoints#getQuestions)
when `item_references` is included in the request).

## [v0.3.0] - 2019-06-17
### Added
Expand Down
32 changes: 29 additions & 3 deletions learnosity_sdk/request/dataapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import requests
import copy

Expand Down Expand Up @@ -60,8 +59,12 @@ def results_iter(self, endpoint, security_packet,
for response in self.request_iter(endpoint, security_packet,
secret, request_packet,
action):
for result in response['data']:
yield result
if type(response['data']) == dict:
for key, value in self.__iteritems(response['data']):
yield {key: value}
else:
for result in response['data']:
yield result

def request_iter(self, endpoint, security_packet,
secret, request_packet={},
Expand Down Expand Up @@ -92,7 +95,9 @@ def request_iter(self, endpoint, security_packet,
# are modified between yields
security_packet = copy.deepcopy(security_packet)
request_packet = copy.deepcopy(request_packet)

data_end = False

while not data_end:
res = self.request(
endpoint,
Expand All @@ -101,10 +106,12 @@ def request_iter(self, endpoint, security_packet,
request_packet,
action
)

if not res.ok:
raise DataApiException(
'server returned HTTP status ' + str(res.status_code)
+ ': ' + res.text)

try:
data = res.json()
except ValueError:
Expand All @@ -122,4 +129,23 @@ def request_iter(self, endpoint, security_packet,
else:
yield data

"""
This helper method allows the .items method of Python 3
to be wrapped to the equivalent .iteritems method
of Python 2.7

The code for this method was sourced from Python Future:
https://python-future.org/

Python Future is licenced under the MIT Licence. Full licence details
and credits can be found here:
https://python-future.org/credits.html
"""
@staticmethod
def __iteritems(obj, **kwargs):
func = getattr(obj, "iteritems", None)

if not func:
func = obj.items

return func(**kwargs)
2 changes: 1 addition & 1 deletion learnosity_sdk/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .lrnuuid import Uuid

__all__ = [
"Uuid",
"Uuid"
]
88 changes: 58 additions & 30 deletions tests/integration/test_dataapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,38 @@
import os
from learnosity_sdk.request import DataApi


# This test uses the consumer key and secret for the demos consumer
# this is the only consumer with publicly available keys
security = {
'consumer_key': 'yis0TYCu7U9V4o7M',
'domain': 'demos.learnosity.com'
}

# WARNING: Normally the consumer secret should not be committed to a public
# repository like this one. Only this specific key is publically available.
consumer_secret = '74c5fd430cf1242a527f6223aebd42d30464be22'
request = {
action = 'get'

items_request = {
# These items should already exist for the demos consumer
'references': ['item_2', 'item_3'],
'limit': 1
}
action = 'get'
endpoint = '/itembank/items'
dummy_responses = [{
'meta': {
'status': True,
'timestamp': 1514874527,
'records': 2,
'next': '1'
},
'data': [{'id': 'a'}]
}, {
'meta': {
'status': True,
'timestamp': 1514874527,
'records': 2
},
'data': [{'id': 'b'}]
}]
items_endpoint = '/itembank/items'

questions_request = {
# These items should already exist for the demos consumer
'item_references': ['py-sdk-test-2019-1', 'py-sdk-test-2019-2'],
'limit': 1
}
questions_endpoint = '/itembank/questions'


class IntegrationTestDataApiClient(unittest.TestCase):

def _build_base_url(self):
@staticmethod
def __build_base_url():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to a static method as it's only used internally by the integration tests and does not access any class variables.

env = os.environ
env_domain = ''
region_domain = '.learnosity.com'
Expand All @@ -55,34 +50,67 @@ def _build_base_url(self):

base_url = "https://data%s%s/%s" % (env_domain, region_domain, version_path)

print('Using base URL: ' + base_url)

return base_url


def test_real_request(self):
"""Make a request against Data Api to ensure the SDK works"""
client = DataApi()
res = client.request(self._build_base_url() + endpoint, security, consumer_secret, request,
res = client.request(self.__build_base_url() + items_endpoint, security, consumer_secret, items_request,
action)
print(res.request.url)
print(res.request.body)
print(res.content)
returned_json = res.json()

assert len(returned_json['data']) > 0

returned_ref = returned_json['data'][0]['reference']
assert returned_ref in request['references']
assert returned_ref in items_request['references']

def test_paging(self):
"""Verify that paging works"""
client = DataApi()
pages = client.request_iter(self._build_base_url() + endpoint, security, consumer_secret,
request, action)
pages = client.request_iter(self.__build_base_url() + items_endpoint, security, consumer_secret,
items_request, action)
results = set()

for page in pages:
if page['data']:
results.add(page['data'][0]['reference'])

assert len(results) == 2
assert results == {'item_2', 'item_3'}

def test_real_question_request(self):
"""Make a request against Data Api to ensure the SDK works"""
client = DataApi()

questions_request['limit'] = 3
res = client.request(self.__build_base_url() + questions_endpoint, security, consumer_secret, questions_request,
action)

returned_json = res.json()
assert len(returned_json['data']) > 0

keys = set()
for key in returned_json['data']:
keys.add(key)

assert keys == {'py-sdk-test-2019-1', 'py-sdk-test-2019-2'}

def test_question_paging(self):
"""Verify that paging works"""
client = DataApi()

pages = client.request_iter(self.__build_base_url() + questions_endpoint, security, consumer_secret,
questions_request, action)

results = []
for page in pages:
if page['data']:
results.append(page['data'])

keys = set()
for row in results:
for key in row.keys():
keys.add(key)

assert len(results) == 3
assert keys == {'py-sdk-test-2019-1', 'py-sdk-test-2019-2'}