This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository python-osmapi.
commit 1f5d976f9e9f110460896d3ba24d75ffa539e7ee Author: Bas Couwenberg <[email protected]> Date: Sat May 30 16:25:35 2015 +0200 Imported Upstream version 0.6.0 --- .travis.yml | 1 + CHANGELOG.md | 11 ++ CONTRIBUTING.md | 55 +++++++++ osmapi/OsmApi.py | 245 ++++++++++++++++++++++++++++++++++++---- osmapi/__init__.py | 2 +- requirements.txt | 1 + setup.py | 2 +- tests/changeset_tests.py | 30 ++--- tests/fixtures/passwordfile.txt | 1 + tests/helper_tests.py | 18 +++ tests/node_tests.py | 53 ++++++++- tests/relation_tests.py | 32 ++++++ tests/way_tests.py | 22 ++++ tox.ini | 2 +- 14 files changed, 429 insertions(+), 46 deletions(-) diff --git a/.travis.yml b/.travis.yml index f4beaa1..568e8d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - '2.7' - '3.2' - '3.3' +- '3.4' before_install: - sudo apt-get update -qq diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d3060..b07258f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ This project follows [Semantic Versioning](http://semver.org/). ## [Unreleased][unreleased] +## 0.6.0 - 2015-05-26 +### Added +- SSL support for the API calls (thanks [Austin Hartzheim](http://austinhartzheim.me/)!) +- Run tests on Python 3.4 as well +- A bunch of new *Error classes (see below) +- Dependency to 'Pygments' to enable syntax highlighting for [online documentation](http://osmapi.divshot.io) +- [Contributing guidelines](https://github.com/metaodi/osmapi/blob/master/CONTRIBUTING.md) + +### Changed +- Changed generic `Exception` with more specific ones, so a client can catch those and react accordingly (no BC-break!) + ## 0.5.0 - 2015-01-03 ### Changed - BC-break: all dates are now parsed as datetime objects diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9031492 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing + +If you want to participate in this project, please follow this guidline. + +Fork and clone this repository: + +```bash +git clone [email protected]:your-username/osmapi.git +``` + +Install the dependencies using `pip`: + +```bash +pip install -r requirements.txt +pip install -r test-requirements.txt +``` + +Make sure the tests pass: + +```bash +nosetests --verbose +``` + +You can even run the tests on different versions of Python with `tox`: + +```bash +tox +``` + +To ensure a good quality of the code use `flake8` to check the code style: + +```bash +flake8 --install-hook +``` + +## Create a pull request + +1. Choose the `develop` branch as a target for new/changed functionality, `master` should only be targeted for urgent bugfixes. +2. While it's not strictly required, it's highly recommended to create a new branch on your fork for each pull request. +3. Push to your fork and [submit a pull request][pr]. +4. Check if the [build ran successfully][ci] and try to improve your code if not. + +At this point you're waiting for my review. +I might suggest some changes or improvements or alternatives. + +Some things that will increase the chance that your pull request is accepted: + +* Write tests. +* Follow the Python style guide ([PEP-8][pep8]). +* Write a [good commit message][commit]. + +[pr]: https://github.com/metaodi/osmapi/compare/ +[ci]: https://travis-ci.org/metaodi/osmapi +[pep8]: https://www.python.org/dev/peps/pep-0008/ +[commit]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html diff --git a/osmapi/OsmApi.py b/osmapi/OsmApi.py index 74affbb..8ca5c57 100644 --- a/osmapi/OsmApi.py +++ b/osmapi/OsmApi.py @@ -39,21 +39,56 @@ import sys import urllib from datetime import datetime +from osmapi import __version__ + # Python 3.x if getattr(urllib, 'urlencode', None) is None: urllib.urlencode = urllib.parse.urlencode -from osmapi import __version__ + +class OsmApiError(Exception): + """ + General OsmApi error class to provide a superclass for all other errors + """ -class UsernamePasswordMissingError(Exception): +class MaximumRetryLimitReachedError(OsmApiError): + """ + Error when the maximum amount of retries is reached and we have to give up + """ + + +class UsernamePasswordMissingError(OsmApiError): """ Error when username or password is missing for an authenticated request """ pass -class ApiError(Exception): +class NoChangesetOpenError(OsmApiError): + """ + Error when an operation requires an open changeset, but currently + no changeset _is_ open + """ + pass + + +class ChangesetAlreadyOpenError(OsmApiError): + """ + Error when a user tries to open a changeset when there is already + an open changeset + """ + pass + + +class OsmTypeAlreadyExistsError(OsmApiError): + """ + Error when a user tries to create an object that already exsits + """ + pass + + +class ApiError(OsmApiError): """ Error class, is thrown when an API request fails """ @@ -76,10 +111,18 @@ class ApiError(Exception): class AlreadySubscribedApiError(ApiError): + """ + Error when a user tries to subscribe to a changeset + that she is already subscribed to + """ pass class NotSubscribedApiError(ApiError): + """ + Error when user tries to unsubscribe from a changeset + that he is not subscribed to + """ pass @@ -88,6 +131,9 @@ class OsmApi: Main class of osmapi, instanciate this class to use osmapi """ + MAX_RETRY_LIMIT = 5 + """Maximum retries if a call to the remote API fails (default: 5)""" + def __init__( self, username=None, @@ -95,7 +141,7 @@ class OsmApi: passwordfile=None, appid="", created_by="osmapi/"+__version__, - api="www.openstreetmap.org", + api="https://www.openstreetmap.org", changesetauto=False, changesetautotags={}, changesetautosize=500, @@ -114,9 +160,12 @@ class OsmApi: If this is omitted "osmapi" is used. It is possible to configure the URL to connect to using the `api` - parameter. By default this is the production API of OpenStreetMap, - for testing purposes, one might prefer the official test instance at - "api06.dev.openstreetmap.org". + parameter. By default this is the SSL version of the production API + of OpenStreetMap, for testing purposes, one might prefer the official + test instance at "api06.dev.openstreetmap.org" or any other valid + OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' + in front of the hostname of the `api` parameter (e.g. + https://api.openstreetmap.com). There are several options to control the changeset behaviour. By default, a programmer has to take care to open and close a changeset @@ -304,6 +353,15 @@ class OsmApi: 'uid': id of user of last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If the supplied information contain an existing node, + `OsmApi.OsmTypeAlreadyExistsError` is raised. """ return self._do("create", "node", NodeData) @@ -334,6 +392,15 @@ class OsmApi: 'uid': id of user of last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("modify", "node", NodeData) @@ -364,6 +431,15 @@ class OsmApi: 'uid': id of user of last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("delete", "node", NodeData) @@ -548,6 +624,18 @@ class OsmApi: 'uid': id of user of last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If the supplied information contain an existing node, + `OsmApi.OsmTypeAlreadyExistsError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("create", "way", WayData) @@ -576,6 +664,15 @@ class OsmApi: 'uid': id of user of last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("modify", "way", WayData) @@ -604,6 +701,15 @@ class OsmApi: 'uid': id of user of last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("delete", "way", WayData) @@ -791,6 +897,18 @@ class OsmApi: 'uid': id of user that made the last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If the supplied information contain an existing node, + `OsmApi.OsmTypeAlreadyExistsError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("create", "relation", RelationData) @@ -828,6 +946,15 @@ class OsmApi: 'uid': id of user that made the last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("modify", "relation", RelationData) @@ -865,6 +992,15 @@ class OsmApi: 'uid': id of user that made the last change, 'visible': True|False } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._do("delete", "relation", RelationData) @@ -952,7 +1088,7 @@ class OsmApi: This function is useful for relations containing other relations. - If you don't need all levels, use `osmapi.OsmApi.RelationFull` + If you don't need all levels, use `OsmApi.RelationFull` instead, which return only 2 levels. """ data = [] @@ -987,7 +1123,7 @@ class OsmApi: The `RelationId` is a unique identifier for a way. - If you need all levels, use `osmapi.OsmApi.RelationFullRecur`. + If you need all levels, use `OsmApi.RelationFullRecur`. """ uri = "/api/0.6/relation/"+str(RelationId)+"/full" data = self._get(uri) @@ -1061,9 +1197,15 @@ class OsmApi: def ChangesetUpdate(self, ChangesetTags={}): """ Updates current changeset with `ChangesetTags`. + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. """ if not self._CurrentChangesetId: - raise Exception("No changeset currently opened") + raise NoChangesetOpenError("No changeset currently opened") if "created_by" not in ChangesetTags: ChangesetTags["created_by"] = self._created_by self._put( @@ -1079,9 +1221,15 @@ class OsmApi: If `ChangesetTags` are given, this tags are applied (key/value). Returns `ChangesetId` + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ if self._CurrentChangesetId: - raise Exception("Changeset already opened") + raise ChangesetAlreadyOpenError("Changeset already opened") if "created_by" not in ChangesetTags: ChangesetTags["created_by"] = self._created_by result = self._put( @@ -1096,9 +1244,15 @@ class OsmApi: Closes current changeset. Returns `ChangesetId`. + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. """ if not self._CurrentChangesetId: - raise Exception("No changeset currently opened") + raise NoChangesetOpenError("No changeset currently opened") self._put( "/api/0.6/changeset/"+str(self._CurrentChangesetId)+"/close", "" @@ -1119,6 +1273,9 @@ class OsmApi: } Returns list with updated ids. + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. """ data = "" data += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" @@ -1257,6 +1414,9 @@ class OsmApi: 'uid': id of user that created this changeset, } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. """ params = urllib.urlencode({'text': comment}) data = self._post( @@ -1291,6 +1451,9 @@ class OsmApi: 'user': username of user that created this changeset, 'uid': id of user that created this changeset, } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. """ try: data = self._post( @@ -1330,6 +1493,9 @@ class OsmApi: 'user': username of user that created this changeset, 'uid': id of user that created this changeset, } + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. """ try: data = self._post( @@ -1422,6 +1588,7 @@ class OsmApi: def NoteCreate(self, NoteData): """ Creates a note. + Returns updated NoteData (without timestamp). """ uri = "/api/0.6/notes" @@ -1431,6 +1598,7 @@ class OsmApi: def NoteComment(self, NoteId, comment): """ Adds a new comment to a note. + Returns the updated note. """ path = "/api/0.6/notes/%s/comment" % NoteId @@ -1439,7 +1607,11 @@ class OsmApi: def NoteClose(self, NoteId, comment): """ Closes a note. + Returns the updated note. + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. """ path = "/api/0.6/notes/%s/close" % NoteId return self._NoteAction(path, comment, optionalAuth=False) @@ -1447,7 +1619,11 @@ class OsmApi: def NoteReopen(self, NoteId, comment): """ Reopens a note. + Returns the updated note. + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. """ path = "/api/0.6/notes/%s/reopen" % NoteId return self._NoteAction(path, comment, optionalAuth=False) @@ -1476,6 +1652,7 @@ class OsmApi: def _NoteAction(self, path, comment=None, optionalAuth=True): """ Performs an action on a Note with a comment + Return the updated note """ uri = path @@ -1642,7 +1819,7 @@ class OsmApi: def _do_manu(self, action, OsmType, OsmData): if not self._CurrentChangesetId: - raise Exception( + raise NoChangesetOpenError( "You need to open a changeset before uploading data" ) if "timestamp" in OsmData: @@ -1650,7 +1827,9 @@ class OsmApi: OsmData["changeset"] = self._CurrentChangesetId if action == "create": if OsmData.get("id", -1) > 0: - raise Exception("This "+OsmType+" already exists") + raise OsmTypeAlreadyExistsError( + "This "+OsmType+" already exists" + ) result = self._put( "/api/0.6/" + OsmType + "/create", self._XmlBuild(OsmType, OsmData) @@ -1677,6 +1856,15 @@ class OsmApi: def flush(self): """ Force the changes to be uploaded to OSM and the changeset to be closed + + If no authentication information are provided, + `OsmApi.UsernamePasswordMissingError` is raised. + + If there is no open changeset, + `OsmApi.NoChangesetOpenError` is raised. + + If there is already an open changeset, + `OsmApi.ChangesetAlreadyOpenError` is raised. """ return self._changesetautoflush(True) @@ -1701,12 +1889,9 @@ class OsmApi: def _http_request(self, cmd, path, auth, send): # noqa if self._debug: - path2 = path - if len(path2) > 50: - path2 = path2[:50]+"[...]" error_msg = ( "%s %s %s" - % (time.strftime("%Y-%m-%d %H:%M:%S"), cmd, path2) + % (time.strftime("%Y-%m-%d %H:%M:%S"), cmd, path) ) print(error_msg, file=sys.stderr) self._conn.putrequest(cmd, path) @@ -1745,7 +1930,7 @@ class OsmApi: if self._debug: error_msg = ( "%s %s %s" - % (time.strftime("%Y-%m-%d %H:%M:%S"), cmd, path2) + % (time.strftime("%Y-%m-%d %H:%M:%S"), cmd, path) ) print(error_msg, file=sys.stderr) return response.read() @@ -1758,22 +1943,34 @@ class OsmApi: return self._http_request(cmd, path, auth, send) except ApiError as e: if e.status >= 500: - if i == 5: + if i == self.MAX_RETRY_LIMIT: raise if i != 1: self._sleep() self._conn = self._get_http_connection() else: raise - except Exception: - if i == 5: - raise + except Exception as e: + print(e) + if i == self.MAX_RETRY_LIMIT: + if isinstance(e, OsmApiError): + raise + raise MaximumRetryLimitReachedError( + "Give up after %s retries" % i + ) if i != 1: self._sleep() self._conn = self._get_http_connection() def _get_http_connection(self): - return httplib.HTTPConnection(self._api, 80) + https_str = 'https://' + http_str = 'http://' + if self._api.lower().startswith(https_str): + return httplib.HTTPSConnection(self._api[len(https_str):], 443) + elif self._api.lower().startswith(http_str): + return httplib.HTTPConnection(self._api[len(http_str):], 80) + else: + return httplib.HTTPConnection(self._api, 80) def _sleep(self): time.sleep(5) diff --git a/osmapi/__init__.py b/osmapi/__init__.py index ddb4e18..ff2f8cf 100644 --- a/osmapi/__init__.py +++ b/osmapi/__init__.py @@ -1,5 +1,5 @@ from __future__ import (absolute_import, print_function, unicode_literals) -__version__ = '0.5.0' +__version__ = '0.6.0' from .OsmApi import * # noqa diff --git a/requirements.txt b/requirements.txt index 1842bac..4565b99 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ pypandoc==0.7.0 Unidecode==0.04.14 pdoc==0.3.1 +Pygments==1.6 diff --git a/setup.py b/setup.py index dd70272..19dbba4 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import codecs +from distutils.core import setup version = __import__('osmapi').__version__ @@ -13,7 +14,6 @@ try: except (IOError, ImportError): description = 'Python wrapper for the OSM API' -from distutils.core import setup setup( name='osmapi', packages=['osmapi'], diff --git a/tests/changeset_tests.py b/tests/changeset_tests.py index 7024a23..f191e68 100644 --- a/tests/changeset_tests.py +++ b/tests/changeset_tests.py @@ -1,7 +1,7 @@ from __future__ import (unicode_literals, absolute_import) from nose.tools import * # noqa from . import osmapi_tests -from osmapi import AlreadySubscribedApiError, NotSubscribedApiError +import osmapi import mock import xmltodict import datetime @@ -100,10 +100,10 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osm version="0.6" generator="osmapi/0.5.0">\n' + b'<osm version="0.6" generator="osmapi/0.6.0">\n' b' <changeset visible="true">\n' b' <tag k="test" v="foobar"/>\n' - b' <tag k="created_by" v="osmapi/0.5.0"/>\n' + b' <tag k="created_by" v="osmapi/0.6.0"/>\n' b' </changeset>\n' b'</osm>\n' ) @@ -134,7 +134,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osm version="0.6" generator="osmapi/0.5.0">\n' + b'<osm version="0.6" generator="osmapi/0.6.0">\n' b' <changeset visible="true">\n' b' <tag k="test" v="foobar"/>\n' b' <tag k="created_by" v="MyTestOSMApp"/>\n' @@ -148,7 +148,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): self._conn_mock() with self.assertRaisesRegexp( - Exception, + osmapi.NoChangesetOpenError, 'No changeset currently opened'): self.api.ChangesetUpdate( { @@ -173,10 +173,10 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osm version="0.6" generator="osmapi/0.5.0">\n' + b'<osm version="0.6" generator="osmapi/0.6.0">\n' b' <changeset visible="true">\n' b' <tag k="foobar" v="A new test changeset"/>\n' - b' <tag k="created_by" v="osmapi/0.5.0"/>\n' + b' <tag k="created_by" v="osmapi/0.6.0"/>\n' b' </changeset>\n' b'</osm>\n' ) @@ -201,7 +201,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osm version="0.6" generator="osmapi/0.5.0">\n' + b'<osm version="0.6" generator="osmapi/0.6.0">\n' b' <changeset visible="true">\n' b' <tag k="foobar" v="A new test changeset"/>\n' b' <tag k="created_by" v="CoolTestApp"/>\n' @@ -221,7 +221,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): ) with self.assertRaisesRegexp( - Exception, + osmapi.ChangesetAlreadyOpenError, 'Changeset already opened'): self.api.ChangesetCreate( { @@ -248,7 +248,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): self._conn_mock() with self.assertRaisesRegexp( - Exception, + osmapi.NoChangesetOpenError, 'No changeset currently opened'): self.api.ChangesetClose() @@ -286,7 +286,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osmChange version="0.6" generator="osmapi/0.5.0">\n' + b'<osmChange version="0.6" generator="osmapi/0.6.0">\n' b'<create>\n' b' <node lat="47.123" lon="8.555" visible="true" ' b'changeset="4444">\n' @@ -360,7 +360,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osmChange version="0.6" generator="osmapi/0.5.0">\n' + b'<osmChange version="0.6" generator="osmapi/0.6.0">\n' b'<modify>\n' b' <way id="4294967296" version="2" visible="true" ' b'changeset="4444">\n' @@ -444,7 +444,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): xmltosorteddict(sendargs[0]), xmltosorteddict( b'<?xml version="1.0" encoding="UTF-8"?>\n' - b'<osmChange version="0.6" generator="osmapi/0.5.0">\n' + b'<osmChange version="0.6" generator="osmapi/0.6.0">\n' b'<delete>\n' b' <relation id="676" version="2" visible="true" ' b'changeset="4444">\n' @@ -652,7 +652,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): def test_ChangesetSubscribeWhenAlreadySubscribed(self): self._conn_mock(auth=True, status=409) - with self.assertRaises(AlreadySubscribedApiError) as cm: + with self.assertRaises(osmapi.AlreadySubscribedApiError) as cm: self.api.ChangesetSubscribe(52924) self.assertEquals(cm.exception.status, 409) @@ -690,7 +690,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi): def test_ChangesetUnsubscribeWhenNotSubscribed(self): self._conn_mock(auth=True, status=404) - with self.assertRaises(NotSubscribedApiError) as cm: + with self.assertRaises(osmapi.NotSubscribedApiError) as cm: self.api.ChangesetUnsubscribe(52924) self.assertEquals(cm.exception.status, 404) diff --git a/tests/fixtures/passwordfile.txt b/tests/fixtures/passwordfile.txt new file mode 100644 index 0000000..708dc2e --- /dev/null +++ b/tests/fixtures/passwordfile.txt @@ -0,0 +1 @@ +testosm:testpass diff --git a/tests/helper_tests.py b/tests/helper_tests.py index cfc528b..cb9286a 100644 --- a/tests/helper_tests.py +++ b/tests/helper_tests.py @@ -3,6 +3,14 @@ from nose.tools import * # noqa from . import osmapi_tests import osmapi import mock +import os + +__location__ = os.path.realpath( + os.path.join( + os.getcwd(), + os.path.dirname(__file__) + ) +) class TestOsmApiHelper(osmapi_tests.TestOsmApi): @@ -23,6 +31,16 @@ class TestOsmApiHelper(osmapi_tests.TestOsmApi): self.api._username = 'testuser' self.api._password = 'testpassword' + def test_passwordfile(self): + path = os.path.join( + __location__, + 'fixtures', + 'passwordfile.txt' + ) + my_api = osmapi.OsmApi(passwordfile=path) + self.assertEquals('testosm', my_api._username) + self.assertEquals('testpass', my_api._password) + def test_http_request_get(self): response = self.api._http_request( 'GET', diff --git a/tests/node_tests.py b/tests/node_tests.py index b435c70..8fa1c12 100644 --- a/tests/node_tests.py +++ b/tests/node_tests.py @@ -1,7 +1,7 @@ from __future__ import (unicode_literals, absolute_import) from nose.tools import * # noqa from . import osmapi_tests -from osmapi import OsmApi, UsernamePasswordMissingError +import osmapi import mock import datetime @@ -59,7 +59,7 @@ class TestOsmApiNode(osmapi_tests.TestOsmApi): def test_NodeCreate_changesetauto(self): # setup mock - self.api = OsmApi( + self.api = osmapi.OsmApi( api="api06.dev.openstreetmap.org", changesetauto=True ) @@ -124,10 +124,32 @@ class TestOsmApiNode(osmapi_tests.TestOsmApi): } with self.assertRaisesRegexp( - Exception, + osmapi.NoChangesetOpenError, 'need to open a changeset'): self.api.NodeCreate(test_node) + def test_NodeCreate_existing_node(self): + # setup mock + self.api.ChangesetCreate = mock.Mock( + return_value=1111 + ) + self.api._CurrentChangesetId = 1111 + + test_node = { + 'id': 123, + 'lat': 47.287, + 'lon': 8.765, + 'tag': { + 'amenity': 'place_of_worship', + 'religion': 'pastafarian' + } + } + + with self.assertRaisesRegexp( + osmapi.OsmTypeAlreadyExistsError, + 'This node already exists'): + self.api.NodeCreate(test_node) + def test_NodeCreate_wo_auth(self): self._conn_mock() @@ -146,10 +168,33 @@ class TestOsmApiNode(osmapi_tests.TestOsmApi): } with self.assertRaisesRegexp( - UsernamePasswordMissingError, + osmapi.UsernamePasswordMissingError, 'Username/Password missing'): self.api.NodeCreate(test_node) + def test_NodeCreate_with_exception(self): + self._conn_mock(auth=True) + self.api._http_request = mock.Mock(side_effect=Exception) + + # setup mock + self.api.ChangesetCreate = mock.Mock( + return_value=1111 + ) + self.api._CurrentChangesetId = 1111 + test_node = { + 'lat': 47.287, + 'lon': 8.765, + 'tag': { + 'amenity': 'place_of_worship', + 'religion': 'pastafarian' + } + } + + with self.assertRaisesRegexp( + osmapi.MaximumRetryLimitReachedError, + 'Give up after 5 retries'): + self.api.NodeCreate(test_node) + def test_NodeUpdate(self): self._conn_mock(auth=True) diff --git a/tests/relation_tests.py b/tests/relation_tests.py index 82b85ad..7d5c839 100644 --- a/tests/relation_tests.py +++ b/tests/relation_tests.py @@ -1,6 +1,7 @@ from __future__ import (unicode_literals, absolute_import) from nose.tools import * # noqa from . import osmapi_tests +import osmapi import mock import datetime @@ -140,6 +141,37 @@ class TestOsmApiRelation(osmapi_tests.TestOsmApi): self.assertEquals(result['member'], test_relation['member']) self.assertEquals(result['tag'], test_relation['tag']) + def test_RelationCreate_existing_node(self): + # setup mock + self.api.ChangesetCreate = mock.Mock( + return_value=1111 + ) + self.api._CurrentChangesetId = 1111 + + test_relation = { + 'id': 456, + 'tag': { + 'type': 'test', + }, + 'member': [ + { + 'ref': 6908, + 'role': 'outer', + 'type': 'way' + }, + { + 'ref': 6352, + 'role': 'point', + 'type': 'node' + }, + ] + } + + with self.assertRaisesRegexp( + osmapi.OsmTypeAlreadyExistsError, + 'This relation already exists'): + self.api.RelationCreate(test_relation) + def test_RelationUpdate(self): self._conn_mock(auth=True) diff --git a/tests/way_tests.py b/tests/way_tests.py index 233d249..d5f50bc 100644 --- a/tests/way_tests.py +++ b/tests/way_tests.py @@ -1,6 +1,7 @@ from __future__ import (unicode_literals, absolute_import) from nose.tools import * # noqa from . import osmapi_tests +import osmapi import mock import datetime @@ -104,6 +105,27 @@ class TestOsmApiWay(osmapi_tests.TestOsmApi): self.assertEquals(result['nd'], test_way['nd']) self.assertEquals(result['tag'], test_way['tag']) + def test_WayCreate_existing_node(self): + # setup mock + self.api.ChangesetCreate = mock.Mock( + return_value=1111 + ) + self.api._CurrentChangesetId = 1111 + + test_way = { + 'id': 456, + 'nd': [11949, 11950], + 'tag': { + 'highway': 'unclassified', + 'name': 'Osmapi Street' + } + } + + with self.assertRaisesRegexp( + osmapi.OsmTypeAlreadyExistsError, + 'This way already exists'): + self.api.WayCreate(test_way) + def test_WayUpdate(self): self._conn_mock(auth=True) diff --git a/tox.ini b/tox.ini index 310e84e..ccb6940 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,py32,py33 +envlist = py26,py27,py32,py33,py34 [testenv] commands=nosetests --verbose deps = -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-osmapi.git _______________________________________________ Pkg-grass-devel mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

