[python-osmapi] 01/05: Imported Upstream version 0.6.0

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sat May 30 15:02:50 UTC 2015


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 <sebastic at xs4all.nl>
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 git at github.com: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



More information about the Pkg-grass-devel mailing list