Skip to content

Commit 00e78ea

Browse files
author
Daniel B
authored
Merge pull request #39 from osmlab/dev
Merging dev -> master
2 parents 0b8baf3 + 016f899 commit 00e78ea

7 files changed

Lines changed: 191 additions & 13 deletions

File tree

docs/usage/functionality.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ Module: configuration
2727

2828
.. automodule:: maproulette.api.configuration
2929
:members:
30-
:show-inheritance:
30+
:special-members:

docs/usage/getting_started.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ Import the package:
1414
import maproulette
1515
1616
17-
From there, create a configuration object. Depending on your use case, you may need to pass your API key. Specify
18-
that when you create your configuration. For example:
17+
From there, create a configuration object. When creating the configuration you can specify a number of of parameters
18+
depending on your needs including the hostname, protocol, and client-side certificates. Depending on your use case, you
19+
may need to obtain and pass your API key as well. For example:
1920

2021
.. code-block:: python
2122

maproulette/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
from .api.task import Task
1313
from .api.user import User
1414

15-
__version__ = '0.1.0rc3'
15+
__version__ = '1.0.0'

maproulette/api/configuration.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,41 @@
11
"""This module contains the basic configuration object that is used to communicate with the MapRoulette API."""
22

3-
DEFAULT_URL = 'maproulette.org'
3+
from typing import Union
4+
5+
DEFAULT_HOSTNAME = 'maproulette.org'
46
DEFAULT_PROTOCOL = 'https'
57
DEFAULT_API_VERSION = '/api/v2'
68

79

810
class Configuration:
911
"""Class for storing the configuration of the MapRoulette server"""
10-
def __init__(self, url=DEFAULT_URL, protocol=DEFAULT_PROTOCOL, api_version=DEFAULT_API_VERSION, api_key=None):
11-
self.url = f"{protocol}://{url}{api_version}"
12-
self.base_url = f"{protocol}://{url}"
12+
def __init__(self, hostname=DEFAULT_HOSTNAME, protocol=DEFAULT_PROTOCOL, api_version=DEFAULT_API_VERSION,
13+
api_key=None, certs=None, verify=True):
14+
"""Create a new configuration object to connect to the MapRoulette server.
15+
16+
:param hostname: Optional parameter to specify the hostname of the MapRoulette instance being addressed. Do
17+
not include the protocol (https, http). Default value is 'maproulette.org'.
18+
:type hostname: str, optional
19+
:param protocol: Optional parameter to specify the protocol to use for the connection to the MapRoulette
20+
instance being addressed such as https or http. Default value is 'https'.
21+
:type protocol: str, optional
22+
:param api_version: Optional parameter to specify the API version to use. The default is '/api/v2'.
23+
:type api_version: str, optional
24+
:param api_key: Optional parameter to pass the user-specific API key. This key is necessary for some actions.
25+
:type api_key: str, optional
26+
:param certs: Optional parameter to pass the client-side certificate and key if necessary to make
27+
connection with the MapRoulette instance being addressed. Can be either a tuple containing the cert and key
28+
file paths (in that order) or a string representing the filepath to both the cert and key stored in a single
29+
file.
30+
:type certs: str or tuple, optional
31+
:param verify: Optional parameter to specify whether to verify SSL certificates for HTTPS requests. Default
32+
is True.
33+
:type verify: bool, optional
34+
"""
35+
self.api_url = f"{protocol}://{hostname}{api_version}"
36+
self.base_url = f"{protocol}://{hostname}"
37+
self.protocol = protocol
1338
self.headers = dict()
1439
self.headers['apikey'] = api_key
15-
self.protocol = protocol
40+
self.certs = certs
41+
self.verify = verify

maproulette/api/maproulette_server.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@
1010
class MapRouletteServer:
1111
"""Class that holds the basic requests that can be made to the MapRoulette API."""
1212
def __init__(self, configuration):
13-
self.url = configuration.url
13+
self.url = configuration.api_url
1414
self.base_url = configuration.base_url
1515
self.headers = configuration.headers
16+
self.certs = configuration.certs
17+
self.verify = configuration.verify
1618
if self.__check_health():
1719
self.session = requests.Session()
1820
self.session.headers = self.headers
19-
self.session.verify = False
21+
self.session.verify = self.verify
22+
self.session.cert = self.certs
2023

2124
def __check_health(self, retries=3, delay=5):
2225
"""Checks health of connection to host by pinging the URL set in the configuration
@@ -30,7 +33,8 @@ def __check_health(self, retries=3, delay=5):
3033
response = requests.get(
3134
self.base_url + '/ping',
3235
headers=self.headers,
33-
verify=False
36+
verify=self.verify,
37+
cert=self.certs
3438
)
3539
if not response.ok:
3640
print(f"Unsuccessful connection. Retrying in {str(delay)} seconds")

maproulette/api/project.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""This module contains the methods that the user will use directly to interact with MapRoulette projects"""
22

3-
import json
43
from maproulette.api.maproulette_server import MapRouletteServer
54
from maproulette.models.project import ProjectModel
65

@@ -85,6 +84,96 @@ def create_project(self, data):
8584
body=data)
8685
return response
8786

87+
def add_challenge_to_project(self, project_id, challenge_id):
88+
"""
89+
Method to add a challenge to a virtual project.
90+
91+
:param project_id: the id of the virtual project
92+
:param challenge_id: the id of the challenge being added
93+
:return: the API response from the POST request
94+
"""
95+
response = self.post(
96+
endpoint=f"/project/{project_id}/challenge/{challenge_id}/add"
97+
)
98+
return response
99+
100+
def remove_challenge_from_project(self, project_id, challenge_id):
101+
"""
102+
Method to remove a challenge from a virtual project.
103+
104+
:param project_id: the id of the virtual project
105+
:param challenge_id: the id of the challenge being removed
106+
:return: the API response from the POST request
107+
"""
108+
response = self.post(
109+
endpoint=f"/project/{project_id}/challenge/{challenge_id}/remove"
110+
)
111+
return response
112+
113+
def delete_project(self, project_id):
114+
"""
115+
Method to delete a project.
116+
117+
:param project_id: the id of the project being deleted
118+
:return: the API response form the DELETE request
119+
"""
120+
response = self.delete(
121+
endpoint=f"/project/{project_id}"
122+
)
123+
return response
124+
125+
def update_project(self, project_id, data):
126+
"""
127+
Method to update an existing project
128+
129+
:param project_id: the id of the project being updated
130+
:param data: the data to use to update the project
131+
:return: the API response from the PUT request
132+
"""
133+
if self.is_project_model(data):
134+
data = ProjectModel.to_dict(data)
135+
response = self.put(
136+
endpoint=f"/project/{project_id}",
137+
body=data)
138+
return response
139+
140+
def get_projects_by_ids(self, project_ids):
141+
"""
142+
Method to retrieve projects from comma separated list of ids
143+
144+
:param project_ids: comma separated list of project ids to be retrieved
145+
:return: the API response from the GET request
146+
"""
147+
query_params = {
148+
"projectIds": project_ids
149+
}
150+
response = self.get(
151+
endpoint=f"/projectsById",
152+
params=query_params
153+
)
154+
return response
155+
156+
def get_random_tasks(self, project_id, limit=1, proximity=-1, search=''):
157+
"""
158+
Method to retrieve random tasks from a project.
159+
160+
:param project_id: the id of the parent project of tasks
161+
:param limit: limit amount of results returned
162+
:param proximity: task to find based on proximity of that task
163+
:param search: a search parameter object stored in a cookie
164+
:return: the API response form the GET request
165+
"""
166+
query_params = {
167+
"limit": str(limit),
168+
"proximity": str(proximity),
169+
"search": str(search)
170+
}
171+
response = self.get(
172+
endpoint=f"/project/{project_id}/tasks",
173+
params=query_params
174+
)
175+
return response
176+
88177
@staticmethod
89178
def is_project_model(input_object):
90179
"""Method to determine whether user input is a valid project model

tests/test_project_api.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,61 @@ def test_create_project(self, mock_request, api_instance=api):
4444
mock_request.return_value.status_code = '200'
4545
response = api_instance.create_project(test_project_model)
4646
self.assertEqual(response['status'], '200')
47+
48+
@patch('maproulette.api.maproulette_server.requests.Session.post')
49+
def test_add_challenge_to_project(self, mock_request, api_instance=api):
50+
test_virtual_project_model = maproulette.ProjectModel(name='Test Virtual Project Name',
51+
id=1234,
52+
is_virtual=True)
53+
test_challenge_model = maproulette.ChallengeModel(name='Test Challenge Name',
54+
id=246)
55+
test_virtual_project_id = test_virtual_project_model.id
56+
test_challenge_id = test_challenge_model.id
57+
mock_request.return_value.status_code = '200'
58+
response = api_instance.add_challenge_to_project(test_virtual_project_id, test_challenge_id)
59+
self.assertEqual(response['status'], '200')
60+
61+
@patch('maproulette.api.maproulette_server.requests.Session.post')
62+
def test_remove_challenge_from_project(self, mock_request, api_instance=api):
63+
test_virtual_project_model = maproulette.ProjectModel(name='Test Virtual Project Name',
64+
id=1234,
65+
is_virtual=True)
66+
test_challenge_model = maproulette.ChallengeModel(name='Test Challenge Name', id=246)
67+
test_virtual_project_id = test_virtual_project_model.id
68+
test_challenge_id = test_challenge_model.id
69+
mock_request.return_value.status_code = '200'
70+
response = api_instance.remove_challenge_from_project(test_virtual_project_id, test_challenge_id)
71+
self.assertEqual(response['status'], '200')
72+
73+
@patch('maproulette.api.maproulette_server.requests.Session.delete')
74+
def test_delete_project(self, mock_request, api_instance=api):
75+
test_project_model = maproulette.ProjectModel(name='Test Project Name', id=1234)
76+
test_project_id = test_project_model.id
77+
mock_request.return_value.status_code = '200'
78+
response = api_instance.delete_project(test_project_id)
79+
self.assertEqual(response['status'], '200')
80+
81+
@patch('maproulette.api.maproulette_server.requests.Session.put')
82+
def test_update_project(self, mock_request, api_instance=api):
83+
test_project_model = maproulette.ProjectModel(name='Test Project Name', id=1234)
84+
test_updated_project_model = maproulette.ProjectModel(name='Test Updated Project Name')
85+
test_project_model_id = test_project_model.id
86+
mock_request.return_value.status_code = '200'
87+
response = api_instance.update_project(test_project_model_id, test_updated_project_model)
88+
self.assertEqual(response['status'], '200')
89+
90+
@patch('maproulette.api.maproulette_server.requests.Session.get')
91+
def test_get_project_by_ids(self, mock_request, api_instance=api):
92+
test_project_ids = '1234,2468,1356'
93+
mock_request.return_value.status_code = '200'
94+
response = api_instance.get_projects_by_ids(test_project_ids)
95+
self.assertEqual(response['status'], '200')
96+
97+
@patch('maproulette.api.maproulette_server.requests.Session.get')
98+
def test_get_random_tasks(self, mock_request, api_instance=api):
99+
test_project_model = maproulette.ProjectModel(name='Test Project Name',
100+
id=1234)
101+
test_project_id = test_project_model.id
102+
mock_request.return_value.status_code = '200'
103+
response = api_instance.get_random_tasks(test_project_id)
104+
self.assertEqual(response['status'], '200')

0 commit comments

Comments
 (0)