Skip to content
This repository has been archived by the owner on Feb 13, 2022. It is now read-only.

Commit

Permalink
finishing up v0.2.5
Browse files Browse the repository at this point in the history
  • Loading branch information
mvexel committed Jul 20, 2015
1 parent 95e4fa5 commit de7ecff
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 20 deletions.
6 changes: 6 additions & 0 deletions docs/index.rst
Expand Up @@ -90,3 +90,9 @@ MapRoulette Task

.. automodule:: maproulette.task
:members:

A Task Collection
=================

.. automodule:: maproulette.taskcollection
:members:
4 changes: 1 addition & 3 deletions maproulette/challenge.py
@@ -1,13 +1,11 @@
#!/usr/bin/env python

"""
Describes the maproulette challenge
A challenge for MapRoulette.
"""

class MapRouletteChallenge(object):
"""
A MapRoulette challenge.
Typical usage::
challenge = MapRouletteChallenge(
Expand Down
8 changes: 3 additions & 5 deletions maproulette/server.py
@@ -1,24 +1,22 @@
#!/usr/bin/env python

"""
A MapRoulette Server
A MapRoulette server.
"""

import requests
import json

class MapRouletteServer(object):
"""
A MapRoulette server.
Typical usage::
Typical usage::
server = MapRouletteServer(
url='http://dev.maproulette.org/api')
:param url: The URL for the MapRoulette API server you want to use
:type url: String
:rtype: A :class:`MapRouletteServer`
:rtype: A :py:class:`MapRouletteServer`
"""

ENDPOINTS = {
Expand Down
4 changes: 1 addition & 3 deletions maproulette/task.py
@@ -1,16 +1,14 @@
#!/usr/bin/env python

"""
MapRoulette Tasks
A task for MapRoulette.
"""

import json
from maproulette.challenge import MapRouletteChallenge

class MapRouletteTask(object):
"""
A task for MapRoulette
Typical usage::
task = MapRouletteTask(
Expand Down
57 changes: 50 additions & 7 deletions maproulette/taskcollection.py
@@ -1,14 +1,36 @@
#!/usr/bin/env python

"""
A collection of Tasks
A collection of tasks for MapRoulette.
This is not a native MapRoulette object, but rather a convenience object
to leverage the bulk insert / update calls in the MapRoulette API. The
MapRouletteTaskCollection class contains one notable method that is not native
to the MapRoulette API: :py:func:`.reconcile`, which reconciles a task collection
with the corresponding challenge on the server.
"""

from .challenge import MapRouletteChallenge
from .task import MapRouletteTask

class MapRouletteTaskCollection(object):
"""A collection of tasks for MapRoulette."""
"""
Typical usage::
task = MapRouletteTask(
challenge=challenge_obj,
identifier=identifier,
geometries=geometries)
task.create(server_instance)
:param challenge: An instance of MapRouletteChallenge
:param identifer: A valid Task identifer
:param geometries: One or more geometries serialized as a GeoJSON FeatureCollection
:type geometries: FeatureCollection
:param instruction: A task-level instruction
:param status: The task's initial status (defaults to 'created' in MapRoulette)
"""

MAX_TASKS = 5000
tasks = None
Expand Down Expand Up @@ -47,28 +69,49 @@ def reconcile(self, server):
"""
if not self.challenge.exists(server):
raise Exception('Challenge does not exist on server')

existing = MapRouletteTaskCollection.from_server(server, self.challenge)

same = []
new = []
changed = []
deleted = []

# reconcile the new tasks with the existing tasks:
for task in self.tasks:
# if the task exists on the server...
if task.identifier in [existing_task.identifier for existing_task in existing.tasks]:
# and they are equal...
if task == existing.get_by_identifier(task.identifier):
# add to 'same' list
same.append(task)
# if they are not equal, add to 'changed' list
else:
changed.append(task)
# if the task does not exist on the server, add to 'new' list
else:
new.append(task)

# next, check for tasks on the server that don't exist in the new collection...
for task in existing.tasks:
if task.identifier not in [task.identifier for task in self.tasks]:
# ... and add those to the 'deleted' list.
deleted.append(task)
print '\nsame: {same}\nnew: {new}\nchanged: {changed}\ndeleted: {deleted}'.format(
same=len(same),
new=len(new),
changed=len(changed),
deleted=len(deleted))

# update the server with new, changed, and deleted tasks
if new:
newCollection = MapRouletteTaskCollection(self.challenge, tasks=new)
newCollection.create(server)
if changed:
changedCollection = MapRouletteTaskCollection(self.challenge, tasks=changed)
changedCollection.update(server)
if deleted:
deletedCollection = MapRouletteTaskCollection(self.challenge, tasks=deleted)
for task in deletedCollection.tasks:
task.status = 'deleted'
deletedCollection.update(server)
# return same, new, changed and deleted tasks
return {'same': same, 'new': new, 'changed': changed, 'deleted': deleted}

def add(self, server):
"""Add task colleciton to the Collection."""
Expand Down
35 changes: 33 additions & 2 deletions run_tests.py
Expand Up @@ -11,6 +11,7 @@

class APITests(unittest.TestCase):

# how much is A_TON?
A_TON = 100

test_challenge_slug = 'test-{}'.format(uuid.uuid4())
Expand All @@ -19,9 +20,15 @@ class APITests(unittest.TestCase):
server = MapRouletteServer(url=test_server_url)

def test_001_init(self):
"""
Assert that the server is indeed alive.
"""
self.assertTrue(isinstance(self.server, MapRouletteServer))

def test_002_challenges(self):
"""
Assert that the server returns a list of challenges
"""
challenges = self.server.challenges()
self.assertTrue(isinstance(challenges, list))

Expand Down Expand Up @@ -85,9 +92,19 @@ def test_009_retrieve_taskcollection_from_server(self):
# We already created 1 task in test 006, then A_TON more in test 008

def test_010_reconcile_task_collections(self):
"""
In this test case, we will reconcile a task collection with an
existing one on the server (created in 008).
Compared to the existing task collection, we will remove one task,
add one task, and change one task.
"""

# get the challenge from server
challenge = MapRouletteChallenge.from_server(
self.server,
self.test_challenge_slug)
# get the task collection to reconcile, start out with the
# existing one on the server
task_collection = MapRouletteTaskCollection.from_server(
self.server,
challenge)
Expand All @@ -101,14 +118,28 @@ def test_010_reconcile_task_collections(self):
geometries=self.__random_point()))
# and finally change one task so it appears 'updated'
task_collection.tasks[0].geometries = self.__random_point()
task_collection.tasks[0].status = 'updated'
task_collection.reconcile(self.server)
task_collection.tasks[0].status = 'changed'

# reconcile the two collections
result = task_collection.reconcile(self.server)

# assert that we indeed have one new, one changed and one deleted task.
self.assertTrue(len(result['new']) == 1)
self.assertTrue(len(result['changed']) == 1)
self.assertTrue(len(result['deleted']) == 1)

def __random_point(self):
"""
return a random geographic Point, wrapped in a Feature, wrapped in a
FeatureCollection. It's like the Turducken of geometries.
"""
return FeatureCollection([
Feature(geometry=Point((random(), random())))])

def __create_task_collection(self, challenge):
"""
Return a collection of A_TON of tasks with random Point geometries
"""
task_collection = MapRouletteTaskCollection(challenge)
i = 0
while i < self.A_TON:
Expand Down

0 comments on commit de7ecff

Please sign in to comment.