Skip to content

Commit eb22e4b

Browse files
authored
Added custom error class and removed/refactored endpoints affected by new MR release (#20)
* Added tests for existing methods * Updated tests * Added custom error class, refactored endpoints that changed in new release * Updated examples * Fixed example * Removed extra line * Added new error handling * Addressed comments, changed method name * First pass at new test * Added tests for exceptions * Added new tests and separated into two files
1 parent a9ee0dc commit eb22e4b

7 files changed

Lines changed: 390 additions & 56 deletions

File tree

examples/create_challenge_from_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
print(challenge_data.overpassQL)
2323

2424
# Create challenge
25-
print(json.dumps(api.create_challenge_from_model(challenge_data)))
25+
print(json.dumps(api.create_challenge(challenge_data)))

examples/get_project_children.py

Lines changed: 0 additions & 14 deletions
This file was deleted.

maproulette/api/api.py

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,45 +35,23 @@ def get_project_by_name(self, project_name):
3535
)
3636
return response
3737

38-
def find_project(self, matcher="", parent=-1, limit=10, page=0, only_enabled="true"):
38+
def find_project(self, matcher="", limit=10, page=0, only_enabled="true"):
3939
"""Method to search for projects based on a specific search criteria
4040
4141
:param matcher: the search string used to match the project names. Default is empty string
42-
:param parent: the parent ID. This field is ignored for this request. Default is -1.
4342
:param limit: the limit to the number of results returned in the response. Default is 10
4443
:param page: used in conjunction with the limit parameter to page through X number of responses. Default is 0.
4544
:param only_enabled: flag to set if only wanting enabled projects returned. Default is True.
4645
:returns: the API response from the GET request
4746
"""
4847
query_params = {
49-
"q": matcher,
50-
"parentId": str(parent),
48+
"search": matcher,
5149
"limit": str(limit),
5250
"page": str(page),
5351
"onlyEnabled": only_enabled
5452
}
5553
response = self.server.get(
56-
endpoint="/projects/find",
57-
params=query_params
58-
)
59-
return response
60-
61-
def get_project_children(self, project_id, limit=10, page=0):
62-
"""Method to fetch a project's children in an expanded list. Unlike the GET request /project/{id}/challenges,
63-
this function will wrap the JSON array list inside of the parent project object, showing the full hierarchy. It
64-
will not include the children tasks of the challenges
65-
66-
:param project_id: the id of the parent project
67-
:param limit: the limit to the number of results returned in the response. Default is 10
68-
:param page: used in conjunction with the limit parameter to page through X number of responses. Default is 0.
69-
:returns: the API response from the GET request
70-
"""
71-
query_params = {
72-
"limit": str(limit),
73-
"page": str(page)
74-
}
75-
response = self.server.get(
76-
endpoint=f"/project/{project_id}/children",
54+
endpoint="/projects",
7755
params=query_params
7856
)
7957
return response
@@ -109,7 +87,7 @@ def create_project(self, data):
10987
body=data)
11088
return response
11189

112-
def create_challenge_from_model(self, data):
90+
def create_challenge(self, data):
11391
"""Method to create a new challenge
11492
11593
:param data: a JSON input containing challenge details

maproulette/api/errors.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import json
2+
"""This module contains the set of MapRoulette's exceptions"""
3+
4+
5+
class MapRouletteBaseException(Exception):
6+
"""MapRoulette Base Exception"""
7+
def __init__(self, message, status=None, payload=None):
8+
self.message = message
9+
self.status = status
10+
self.payload = payload
11+
12+
def __str__(self):
13+
return repr({
14+
"status": self.status,
15+
"message": self.message,
16+
"payload": self.payload
17+
})
18+
19+
20+
class NotFoundError(MapRouletteBaseException):
21+
"""Resource cannot be found"""
22+
23+
24+
class ConnectionUnavailableError(MapRouletteBaseException):
25+
"""A connection error occurred"""
26+
27+
28+
class UnauthorizedError(MapRouletteBaseException):
29+
"""The user is not authorized to make this request"""
30+
31+
32+
class HttpError(MapRouletteBaseException):
33+
"""An HTTP error occurred"""
34+
35+
36+
class InvalidJsonError(MapRouletteBaseException):
37+
"""Errors produced from an invalid JSON object"""

maproulette/api/maproulette_server.py

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""This module contains the basic methods that handle API calls to the MapRoulette API. It uses the requests library to
22
accomplish this."""
33

4-
import logging
54
import requests
65
import json
6+
from .errors import HttpError, InvalidJsonError, ConnectionUnavailableError, UnauthorizedError, NotFoundError
77

88

99
class MapRouletteServer:
@@ -27,7 +27,24 @@ def get(self, endpoint, params=None):
2727
try:
2828
response.raise_for_status()
2929
except requests.exceptions.HTTPError as e:
30-
logging.critical(str(e))
30+
if e.response.status_code == 404:
31+
raise NotFoundError(
32+
message='Resource not found',
33+
status=e.response.status_code,
34+
payload=e.response
35+
) from None
36+
else:
37+
raise HttpError(
38+
message='An HTTP error occurred',
39+
status=e.response.status_code,
40+
payload=e.response
41+
) from None
42+
except (requests.ConnectionError, requests.Timeout) as e:
43+
raise ConnectionUnavailableError(
44+
message="Connection Unavailable",
45+
status=e.response.status_code,
46+
payload=response
47+
) from None
3148
try:
3249
return {
3350
"data": response.json(),
@@ -53,7 +70,26 @@ def post(self, endpoint, body=None):
5370
try:
5471
response.raise_for_status()
5572
except requests.exceptions.HTTPError as e:
56-
logging.critical(str(e))
73+
if e.response.status_code == 400:
74+
raise InvalidJsonError(
75+
message='Invalid JSON payload',
76+
status=e.response.status_code,
77+
payload=e.response
78+
) from None
79+
elif e.response.status_code == 401:
80+
raise UnauthorizedError(
81+
message='The user is not authorized to make this request',
82+
status=e.response.status_code,
83+
payload=e.response
84+
) from None
85+
else:
86+
raise HttpError(
87+
message='An HTTP error occurred',
88+
status=e.response.status_code,
89+
payload=e.response
90+
) from None
91+
except (requests.ConnectionError, requests.Timeout) as e:
92+
raise ConnectionUnavailableError(e) from None
5793
try:
5894
return {
5995
"data": response.json(),
@@ -79,7 +115,26 @@ def put(self, endpoint, body=None):
79115
try:
80116
response.raise_for_status()
81117
except requests.exceptions.HTTPError as e:
82-
logging.critical(str(e))
118+
if e.response.status_code == 400:
119+
raise InvalidJsonError(
120+
message='Invalid JSON payload',
121+
status=e.response.status_code,
122+
payload=e.response
123+
) from None
124+
elif e.response.status_code == 401:
125+
raise UnauthorizedError(
126+
message='The user is not authorized to make this request',
127+
status=e.response.status_code,
128+
payload=e.response
129+
) from None
130+
else:
131+
raise HttpError(
132+
message='An HTTP error occurred',
133+
status=e.response.status_code,
134+
payload=e.response
135+
) from None
136+
except (requests.ConnectionError, requests.Timeout) as e:
137+
raise ConnectionUnavailableError(e) from None
83138
try:
84139
return {
85140
"data": response.json(),
@@ -102,7 +157,26 @@ def delete(self, endpoint):
102157
try:
103158
response.raise_for_status()
104159
except requests.exceptions.HTTPError as e:
105-
logging.critical(str(e))
160+
if e.response.status_code == 401:
161+
raise UnauthorizedError(
162+
message='The user is not authorized to make this request',
163+
status=e.response.status_code,
164+
payload=e.response
165+
) from None
166+
elif e.response.status_code == 404:
167+
raise NotFoundError(
168+
message='Resource not found',
169+
status=e.response.status_code,
170+
payload=e.response
171+
) from None
172+
else:
173+
raise HttpError(
174+
message='An HTTP error occurred',
175+
status=e.response.status_code,
176+
payload=e.response
177+
) from None
178+
except (requests.ConnectionError, requests.Timeout) as e:
179+
raise ConnectionUnavailableError(e) from None
106180
try:
107181
return {
108182
"data": response.json(),

tests/test_api.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import maproulette
22
import unittest
33
from tests.sample_data import test_geojson, test_overpassQL_query
4-
import pytest
54
from unittest.mock import patch
65

76

@@ -31,13 +30,6 @@ def test_find_project(self, mock_request, api_instance=api):
3130
response = api_instance.find_project(test_search)
3231
self.assertEqual(response['status'], '200')
3332

34-
@patch('maproulette.api.maproulette_server.requests.get')
35-
def test_get_project_children(self, mock_request, api_instance=api):
36-
test_project_id = '32922'
37-
mock_request.return_value.status_code = '200'
38-
response = api_instance.get_project_children(test_project_id)
39-
self.assertEqual(response['status'], '200')
40-
4133
@patch('maproulette.api.maproulette_server.requests.get')
4234
def test_get_project_challenges(self, mock_request, api_instance=api):
4335
test_project_id = '12974'
@@ -82,13 +74,13 @@ def test_get_challenge_tasks(self, mock_request, api_instance=api):
8274
self.assertEqual(response['status'], '200')
8375

8476
@patch('maproulette.api.maproulette_server.requests.post')
85-
def test_create_challenge_from_model(self, mock_request, api_instance=api):
77+
def test_create_challenge(self, mock_request, api_instance=api):
8678
test_challenge_model = maproulette.ChallengeModel(name='Test_Challenge_Name',
8779
instruction='Do something',
8880
description='This is a test challenge',
8981
overpassQL=test_overpassQL_query)
9082
mock_request.return_value.status_code = '200'
91-
response = api_instance.create_challenge_from_model(test_challenge_model)
83+
response = api_instance.create_challenge(test_challenge_model)
9284
self.assertEqual(response['status'], '200')
9385

9486
@patch('maproulette.api.maproulette_server.requests.put')

0 commit comments

Comments
 (0)