Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-analytics-data-api-client
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
OpenEdx
edx-analytics-data-api-client
Commits
3f674cc9
Commit
3f674cc9
authored
Jul 23, 2014
by
Clinton Blackburn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added support for CSV output
parent
f3b8c56f
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
77 additions
and
15 deletions
+77
-15
.pylintrc
+4
-1
README.rst
+3
-1
analyticsclient/client.py
+16
-6
analyticsclient/course.py
+9
-5
analyticsclient/data_format.py
+2
-0
analyticsclient/tests/test_client.py
+29
-2
analyticsclient/tests/test_course.py
+13
-0
requirements.txt
+1
-0
No files found.
.pylintrc
View file @
3f674cc9
...
@@ -62,7 +62,10 @@ disable=
...
@@ -62,7 +62,10 @@ disable=
# E1103: maybe no member
# E1103: maybe no member
E1103,
E1103,
# C0111: missing docstring (handled by pep257)
# C0111: missing docstring (handled by pep257)
C0111
C0111,
# We can decide if names are invalid on our own
invalid-name,
[REPORTS]
[REPORTS]
...
...
README.rst
View file @
3f674cc9
...
@@ -2,7 +2,9 @@
...
@@ -2,7 +2,9 @@
edX Analytics API Client
edX Analytics API Client
========================
========================
The edX Analytics API Client (henceforth, client) allows users to retrieve data from the edX data warehouse.
The edX Analytics API Client (henceforth, client) allows users to retrieve data from the edX data warehouse. Currently,
the client supports retrieving course activity and enrollment data. By default, all data is returned in the JSON format.
Enrollment data may also be retrieved in the CSV format by changing the data_format argument.
Testing
Testing
=======
=======
...
...
analyticsclient/client.py
View file @
3f674cc9
...
@@ -2,6 +2,7 @@ import logging
...
@@ -2,6 +2,7 @@ import logging
import
requests
import
requests
import
requests.exceptions
import
requests.exceptions
from
analyticsclient
import
data_format
as
DF
from
analyticsclient.course
import
Course
from
analyticsclient.course
import
Course
from
analyticsclient.exceptions
import
ClientError
,
InvalidRequestError
,
NotFoundError
,
TimeoutError
from
analyticsclient.exceptions
import
ClientError
,
InvalidRequestError
,
NotFoundError
,
TimeoutError
...
@@ -36,7 +37,7 @@ class Client(object):
...
@@ -36,7 +37,7 @@ class Client(object):
self
.
status
=
Status
(
self
)
self
.
status
=
Status
(
self
)
self
.
courses
=
lambda
course_id
:
Course
(
self
,
course_id
)
self
.
courses
=
lambda
course_id
:
Course
(
self
,
course_id
)
def
get
(
self
,
resource
,
timeout
=
None
):
def
get
(
self
,
resource
,
timeout
=
None
,
data_format
=
DF
.
JSON
):
"""
"""
Retrieve the data for a resource.
Retrieve the data for a resource.
...
@@ -45,13 +46,17 @@ class Client(object):
...
@@ -45,13 +46,17 @@ class Client(object):
resource (str): Path in the form of slash separated strings.
resource (str): Path in the form of slash separated strings.
timeout (float): Continue to attempt to retrieve a resource for this many seconds before giving up and
timeout (float): Continue to attempt to retrieve a resource for this many seconds before giving up and
raising an error.
raising an error.
data_format (str): Format in which data should be returned
Returns: A
structure consisting of simple python types (dict, list, int, str etc).
Returns: A
PI response data in specified data_format
Raises: ClientError if the resource cannot be retrieved for any reason.
Raises: ClientError if the resource cannot be retrieved for any reason.
"""
"""
response
=
self
.
_request
(
resource
,
timeout
=
timeout
)
response
=
self
.
_request
(
resource
,
timeout
=
timeout
,
data_format
=
data_format
)
if
data_format
==
DF
.
CSV
:
return
response
.
text
try
:
try
:
return
response
.
json
()
return
response
.
json
()
...
@@ -82,13 +87,18 @@ class Client(object):
...
@@ -82,13 +87,18 @@ class Client(object):
return
False
return
False
# pylint: disable=no-member
# pylint: disable=no-member
def
_request
(
self
,
resource
,
timeout
=
None
):
def
_request
(
self
,
resource
,
timeout
=
None
,
data_format
=
DF
.
JSON
):
if
timeout
is
None
:
if
timeout
is
None
:
timeout
=
self
.
timeout
timeout
=
self
.
timeout
accept_format
=
'application/json'
if
data_format
==
DF
.
CSV
:
accept_format
=
'text/csv'
headers
=
{
headers
=
{
'Accept'
:
'application/json'
,
'Accept'
:
accept_format
,
}
}
if
self
.
auth_token
:
if
self
.
auth_token
:
headers
[
'Authorization'
]
=
'Token '
+
self
.
auth_token
headers
[
'Authorization'
]
=
'Token '
+
self
.
auth_token
...
@@ -114,7 +124,7 @@ class Client(object):
...
@@ -114,7 +124,7 @@ class Client(object):
return
response
return
response
except
requests
.
exceptions
.
Timeout
:
except
requests
.
exceptions
.
Timeout
:
message
=
"Response from {0} exceeded timeout of {1}s."
message
=
"Response from {0} exceeded timeout of {1}s."
.
format
(
resource
,
timeout
)
log
.
exception
(
message
)
log
.
exception
(
message
)
raise
TimeoutError
(
message
)
raise
TimeoutError
(
message
)
...
...
analyticsclient/course.py
View file @
3f674cc9
import
urllib
import
urllib
import
analyticsclient.activity_type
as
at
import
analyticsclient.activity_type
as
AT
import
analyticsclient.data_format
as
DF
class
Course
(
object
):
class
Course
(
object
):
...
@@ -18,7 +19,7 @@ class Course(object):
...
@@ -18,7 +19,7 @@ class Course(object):
self
.
client
=
client
self
.
client
=
client
self
.
course_id
=
unicode
(
course_id
)
self
.
course_id
=
unicode
(
course_id
)
def
enrollment
(
self
,
demographic
=
None
,
start_date
=
None
,
end_date
=
None
):
def
enrollment
(
self
,
demographic
=
None
,
start_date
=
None
,
end_date
=
None
,
data_format
=
DF
.
JSON
):
"""
"""
Get course enrollment data.
Get course enrollment data.
...
@@ -33,6 +34,7 @@ class Course(object):
...
@@ -33,6 +34,7 @@ class Course(object):
demographic (str): Demographic by which enrollment data should be grouped.
demographic (str): Demographic by which enrollment data should be grouped.
start_date (str): Minimum date for returned enrollment data
start_date (str): Minimum date for returned enrollment data
end_date (str): Maxmimum date for returned enrollment data
end_date (str): Maxmimum date for returned enrollment data
data_format (str): Format in which data should be returned
"""
"""
path
=
'courses/{0}/enrollment/'
.
format
(
self
.
course_id
)
path
=
'courses/{0}/enrollment/'
.
format
(
self
.
course_id
)
if
demographic
:
if
demographic
:
...
@@ -49,13 +51,15 @@ class Course(object):
...
@@ -49,13 +51,15 @@ class Course(object):
if
querystring
:
if
querystring
:
path
+=
'?{0}'
.
format
(
querystring
)
path
+=
'?{0}'
.
format
(
querystring
)
return
self
.
client
.
get
(
path
)
return
self
.
client
.
get
(
path
,
data_format
=
data_format
)
def
recent_activity
(
self
,
activity_type
=
at
.
ANY
):
def
recent_activity
(
self
,
activity_type
=
AT
.
ANY
,
data_format
=
DF
.
JSON
):
"""
"""
Get the recent course activity.
Get the recent course activity.
Arguments:
Arguments:
activity_type (str): The type of recent activity to return. Defaults to ANY.
activity_type (str): The type of recent activity to return. Defaults to ANY.
data_format (str): Format in which data should be returned
"""
"""
return
self
.
client
.
get
(
'courses/{0}/recent_activity/?activity_type={1}'
.
format
(
self
.
course_id
,
activity_type
))
path
=
'courses/{0}/recent_activity/?activity_type={1}'
.
format
(
self
.
course_id
,
activity_type
)
return
self
.
client
.
get
(
path
,
data_format
=
data_format
)
analyticsclient/data_format.py
0 → 100644
View file @
3f674cc9
CSV
=
'csv'
JSON
=
'json'
analyticsclient/tests/test_client.py
View file @
3f674cc9
...
@@ -3,7 +3,9 @@ import json
...
@@ -3,7 +3,9 @@ import json
import
httpretty
import
httpretty
import
mock
import
mock
import
requests.exceptions
import
requests.exceptions
from
testfixtures
import
log_capture
from
analyticsclient
import
data_format
from
analyticsclient.client
import
Client
from
analyticsclient.client
import
Client
from
analyticsclient.exceptions
import
ClientError
,
TimeoutError
from
analyticsclient.exceptions
import
ClientError
,
TimeoutError
from
analyticsclient.tests
import
ClientTestCase
from
analyticsclient.tests
import
ClientTestCase
...
@@ -18,6 +20,7 @@ class ClientTests(ClientTestCase):
...
@@ -18,6 +20,7 @@ class ClientTests(ClientTestCase):
def
tearDown
(
self
):
def
tearDown
(
self
):
httpretty
.
disable
()
httpretty
.
disable
()
httpretty
.
reset
()
def
test_has_resource
(
self
):
def
test_has_resource
(
self
):
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
test_url
,
body
=
''
)
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
test_url
,
body
=
''
)
...
@@ -58,14 +61,38 @@ class ClientTests(ClientTestCase):
...
@@ -58,14 +61,38 @@ class ClientTests(ClientTestCase):
# pylint: disable=protected-access
# pylint: disable=protected-access
@mock.patch
(
'requests.get'
,
side_effect
=
requests
.
exceptions
.
Timeout
)
@mock.patch
(
'requests.get'
,
side_effect
=
requests
.
exceptions
.
Timeout
)
def
test_request_timeout
(
self
,
mock_get
):
@log_capture
()
def
test_request_timeout
(
self
,
mock_get
,
lc
):
url
=
self
.
test_url
url
=
self
.
test_url
timeout
=
None
timeout
=
None
self
.
assertRaises
(
TimeoutError
,
self
.
client
.
_request
,
self
.
test_endpoint
,
timeout
=
timeout
)
headers
=
{
'Accept'
:
'application/json'
}
headers
=
{
'Accept'
:
'application/json'
}
self
.
assertRaises
(
TimeoutError
,
self
.
client
.
_request
,
self
.
test_endpoint
,
timeout
=
timeout
)
msg
=
'Response from {0} exceeded timeout of {1}s.'
.
format
(
self
.
test_endpoint
,
self
.
client
.
timeout
)
lc
.
check
((
'analyticsclient.client'
,
'ERROR'
,
msg
))
lc
.
clear
()
mock_get
.
assert_called_once_with
(
url
,
headers
=
headers
,
timeout
=
self
.
client
.
timeout
)
mock_get
.
assert_called_once_with
(
url
,
headers
=
headers
,
timeout
=
self
.
client
.
timeout
)
mock_get
.
reset_mock
()
mock_get
.
reset_mock
()
timeout
=
10
timeout
=
10
self
.
assertRaises
(
TimeoutError
,
self
.
client
.
_request
,
self
.
test_endpoint
,
timeout
=
timeout
)
self
.
assertRaises
(
TimeoutError
,
self
.
client
.
_request
,
self
.
test_endpoint
,
timeout
=
timeout
)
mock_get
.
assert_called_once_with
(
url
,
headers
=
headers
,
timeout
=
timeout
)
mock_get
.
assert_called_once_with
(
url
,
headers
=
headers
,
timeout
=
timeout
)
msg
=
'Response from {0} exceeded timeout of {1}s.'
.
format
(
self
.
test_endpoint
,
timeout
)
lc
.
check
((
'analyticsclient.client'
,
'ERROR'
,
msg
))
def
test_request_format
(
self
):
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
test_url
,
body
=
'{}'
)
response
=
self
.
client
.
get
(
self
.
test_endpoint
)
self
.
assertEquals
(
httpretty
.
last_request
()
.
headers
[
'Accept'
],
'application/json'
)
self
.
assertDictEqual
(
response
,
{})
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
test_url
,
body
=
'not-json'
)
response
=
self
.
client
.
get
(
self
.
test_endpoint
,
data_format
=
data_format
.
CSV
)
self
.
assertEquals
(
httpretty
.
last_request
()
.
headers
[
'Accept'
],
'text/csv'
)
self
.
assertEqual
(
response
,
'not-json'
)
httpretty
.
register_uri
(
httpretty
.
GET
,
self
.
test_url
,
body
=
'{}'
)
response
=
self
.
client
.
get
(
self
.
test_endpoint
,
data_format
=
data_format
.
JSON
)
self
.
assertEquals
(
httpretty
.
last_request
()
.
headers
[
'Accept'
],
'application/json'
)
self
.
assertDictEqual
(
response
,
{})
analyticsclient/tests/test_course.py
View file @
3f674cc9
...
@@ -3,6 +3,7 @@ import json
...
@@ -3,6 +3,7 @@ import json
import
httpretty
import
httpretty
import
re
import
re
from
analyticsclient
import
activity_type
as
at
from
analyticsclient
import
activity_type
as
at
from
analyticsclient
import
data_format
from
analyticsclient
import
demographic
as
demo
from
analyticsclient
import
demographic
as
demo
from
analyticsclient.exceptions
import
NotFoundError
,
InvalidRequestError
from
analyticsclient.exceptions
import
NotFoundError
,
InvalidRequestError
...
@@ -91,3 +92,15 @@ class CoursesTests(ClientTestCase):
...
@@ -91,3 +92,15 @@ class CoursesTests(ClientTestCase):
self
.
assertCorrectEnrollmentUrl
(
self
.
course
,
demo
.
EDUCATION
)
self
.
assertCorrectEnrollmentUrl
(
self
.
course
,
demo
.
EDUCATION
)
self
.
assertCorrectEnrollmentUrl
(
self
.
course
,
demo
.
GENDER
)
self
.
assertCorrectEnrollmentUrl
(
self
.
course
,
demo
.
GENDER
)
self
.
assertCorrectEnrollmentUrl
(
self
.
course
,
demo
.
LOCATION
)
self
.
assertCorrectEnrollmentUrl
(
self
.
course
,
demo
.
LOCATION
)
def
test_enrollment_data_format
(
self
):
uri
=
self
.
get_api_url
(
'courses/{0}/enrollment/'
.
format
(
self
.
course
.
course_id
))
httpretty
.
register_uri
(
httpretty
.
GET
,
uri
,
body
=
'{}'
)
self
.
course
.
enrollment
()
self
.
assertEquals
(
httpretty
.
last_request
()
.
headers
[
'Accept'
],
'application/json'
)
httpretty
.
register_uri
(
httpretty
.
GET
,
uri
,
body
=
'not-json'
)
self
.
course
.
enrollment
(
data_format
=
data_format
.
CSV
)
self
.
assertEquals
(
httpretty
.
last_request
()
.
headers
[
'Accept'
],
'text/csv'
)
requirements.txt
View file @
3f674cc9
...
@@ -8,3 +8,4 @@ pep8==1.5.7
...
@@ -8,3 +8,4 @@ pep8==1.5.7
pylint
==1.2.1
pylint
==1.2.1
pep257
==0.3.2
pep257
==0.3.2
mock
==1.0.1
mock
==1.0.1
testfixtures
==4.0.0
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment