[Git][debian-gis-team/python-osmapi][upstream] New upstream version 3.1.0+ds
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Wed Jan 18 16:41:29 GMT 2023
Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-osmapi
Commits:
8a5d345e by Bas Couwenberg at 2023-01-18T16:39:03+01:00
New upstream version 3.1.0+ds
- - - - -
14 changed files:
- CHANGELOG.md
- + examples/changesets.py
- + examples/notes.py
- osmapi/OsmApi.py
- osmapi/__init__.py
- osmapi/errors.py
- osmapi/http.py
- osmapi/parser.py
- tests/changeset_test.py
- tests/conftest.py
- + tests/fixtures/test_NoteAlreadyClosed.xml
- + tests/fixtures/test_NoteCommentOnClosedNote.xml
- + tests/fixtures/test_NotesGet_empty.xml
- tests/notes_test.py
Changes:
=====================================
CHANGELOG.md
=====================================
@@ -4,6 +4,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
+## [3.1.0] - 2023-01-18
+### Added
+- New `ElementNotFoundApiError` when a 404 response comes from the API
+- Raise an exception if a user tries to create a test changeset on the PROD server (see issue #66, thanks [SomeoneElseOSM](https://github.com/SomeoneElseOSM))
+
+### Changed
+- Add new `NoteAlreadyClosedApiError` exception when you try to close an already closed note (see issue #135, thanks [Mateusz Konieczny](https://github.com/matkoniecz))
+
+### Fixed
+- `NoteGets` now allows empty results i.e. it returns an empty list if no notes were found (see issue #137, thanks [Mateusz Konieczny](https://github.com/matkoniecz))
+
## [3.0.0] - 2022-02-12
### Added
- Add context manager `Changeset()` to open/close changesets
@@ -314,7 +325,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `Fixed` for any bug fixes.
- `Security` to invite users to upgrade in case of vulnerabilities.
-[Unreleased]: https://github.com/metaodi/osmapi/compare/v3.0.0...HEAD
+[Unreleased]: https://github.com/metaodi/osmapi/compare/v3.1.0...HEAD
+[3.1.0]: https://github.com/metaodi/osmapi/compare/v3.0.0...v3.1.0
[3.0.0]: https://github.com/metaodi/osmapi/compare/v2.0.2...v3.0.0
[2.0.2]: https://github.com/metaodi/osmapi/compare/v2.0.1...v2.0.2
[2.0.1]: https://github.com/metaodi/osmapi/compare/v2.0.0...v2.0.1
=====================================
examples/changesets.py
=====================================
@@ -0,0 +1,14 @@
+import osmapi
+from pprint import pprint
+
+api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
+
+try:
+ api.ChangesetGet(111111111111)
+except osmapi.ApiError as e:
+ print(f"Error: {e}")
+ if e.status == 404:
+ print("Changeset not found")
+
+print("")
+pprint(api.ChangesetGet(12345))
=====================================
examples/notes.py
=====================================
@@ -0,0 +1,41 @@
+import osmapi
+from dotenv import load_dotenv, find_dotenv
+import os
+from pprint import pprint
+
+load_dotenv(find_dotenv())
+user = os.getenv('OSM_USER')
+pw = os.getenv('OSM_PASS')
+
+api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", username=user, password=pw)
+empty_notes = api.NotesGet(-93.8472901, 35.9763601, -80, 36.176360100000004, limit=1, closed=0)
+pprint(empty_notes)
+
+
+# create note and then search for it
+note = api.NoteCreate({
+ 'lat': 47.3383501,
+ 'lon': 8.5339522,
+ 'text': 'test note',
+})
+test_notes = api.NotesGet(8.527504, 47.337063, 8.540679, 47.341673, limit=1, closed=0)
+pprint(test_notes)
+
+
+api.NoteComment(note['id'], "Another comment")
+api.NoteClose(note['id'], "Close this test note")
+
+
+# try to close an already closed note
+try:
+ api.NoteClose(note['id'], "Close the note again")
+except osmapi.NoteAlreadyClosedApiError:
+ print('')
+ print(f"The note {note['id']} has already been closed")
+
+# try to comment on closed note
+try:
+ api.NoteComment(note['id'], "Just a comment")
+except osmapi.NoteAlreadyClosedApiError:
+ print('')
+ print(f"The note {note['id']} is closed, comment no longer possible")
=====================================
osmapi/OsmApi.py
=====================================
@@ -257,6 +257,9 @@ class OsmApi:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
uri = "/api/0.6/node/%s" % (NodeId)
if NodeVersion != -1:
@@ -389,6 +392,9 @@ class OsmApi:
If the requested element has already been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
return self._do("delete", "node", NodeData)
@@ -538,6 +544,9 @@ class OsmApi:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
uri = "/api/0.6/way/%s" % (WayId)
if WayVersion != -1:
@@ -667,6 +676,9 @@ class OsmApi:
If the requested element has already been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
return self._do("delete", "way", WayData)
@@ -750,6 +762,9 @@ class OsmApi:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
uri = "/api/0.6/way/%s/full" % (WayId)
data = self._session._get(uri)
@@ -814,6 +829,9 @@ class OsmApi:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
uri = "/api/0.6/relation/%s" % (RelationId)
if RelationVersion != -1:
@@ -970,6 +988,9 @@ class OsmApi:
If the requested element has already been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
return self._do("delete", "relation", RelationData)
@@ -1060,6 +1081,9 @@ class OsmApi:
If any relation (on any level) has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
data = []
todo = [RelationId]
@@ -1097,6 +1121,9 @@ class OsmApi:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
uri = "/api/0.6/relation/%s/full" % (RelationId)
data = self._session._get(uri)
@@ -1249,6 +1276,14 @@ class OsmApi:
raise errors.ChangesetAlreadyOpenError("Changeset already opened")
if "created_by" not in ChangesetTags:
ChangesetTags["created_by"] = self._created_by
+
+ # check if someone tries to create a test changeset to PROD
+ if (
+ self._api == "https://www.openstreetmap.org"
+ and ChangesetTags.get('comment') == "My first test"
+ ):
+ raise errors.OsmApiError("DO NOT CREATE test changesets on the production server")
+
result = self._session._put(
"/api/0.6/changeset/create",
xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self)
@@ -1552,11 +1587,9 @@ class OsmApi:
None,
forceAuth=True
)
- except errors.ApiError as e:
- if e.status == 404:
- raise errors.NotSubscribedApiError(e.status, e.reason, e.payload)
- else:
- raise
+ except errors.ElementNotFoundApiError as e:
+ raise errors.NotSubscribedApiError(e.status, e.reason, e.payload)
+
changeset = dom.OsmResponseToDom(data, tag="changeset", single=True)
return dom.DomParseChangeset(changeset)
@@ -1631,9 +1664,37 @@ class OsmApi:
def NoteCreate(self, NoteData):
"""
- Creates a note.
+ Creates a note based on the supplied `NoteData` dict:
+
+ #!python
+ {
+ 'lat': latitude of note,
+ 'lon': longitude of note,
+ 'text': text of the note,
+ }
+
+ Returns updated `NoteData`:
+
+ #!python
+ {
+ 'id': id of note,
+ 'lat': latitude of note,
+ 'lon': longitude of note,
+ 'date_created': date when the note was created
+ 'date_closed': date when the note was closed (or None if the note is open),
+ 'status': status of the note (open or closed),
+ 'comments': [
+ {
+ 'date': date of the comment,
+ 'action': status of comment (opened, commented, closed),
+ 'text': text of the note,
+ 'html': html version of the text of the note,
+ 'uid': user id of the user creating this note or None
+ 'user': username of the user creating this note or None
+ }
+ ]
+ }
- Returns updated NoteData (without timestamp).
"""
uri = "/api/0.6/notes"
uri += "?" + urllib.parse.urlencode(NoteData)
@@ -1671,6 +1732,9 @@ class OsmApi:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
"""
path = "/api/0.6/notes/%s/reopen" % NoteId
return self._NoteAction(path, comment, optionalAuth=False)
@@ -1710,8 +1774,8 @@ class OsmApi:
try:
result = self._session._post(uri, None, optionalAuth=optionalAuth)
except errors.ApiError as e:
- if e.status == 404:
- raise errors.NoteClosedApiError(e.status, e.reason, e.payload)
+ if e.status == 409:
+ raise errors.NoteAlreadyClosedApiError(e.status, e.reason, e.payload)
else:
raise
=====================================
osmapi/__init__.py
=====================================
@@ -1,4 +1,4 @@
-__version__ = '3.0.0'
+__version__ = '3.1.0'
from .OsmApi import * # noqa
from .errors import * # noqa
=====================================
osmapi/errors.py
=====================================
@@ -93,6 +93,12 @@ class ElementDeletedApiError(ApiError):
pass
+class ElementNotFoundApiError(ApiError):
+ """
+ Error if the the requested element was not found
+ """
+
+
class ResponseEmptyApiError(ApiError):
"""
Error when the response to the request is empty
@@ -106,7 +112,7 @@ class ChangesetClosedApiError(ApiError):
"""
-class NoteClosedApiError(ApiError):
+class NoteAlreadyClosedApiError(ApiError):
"""
Error if the the note in question has already been closed
"""
=====================================
osmapi/http.py
=====================================
@@ -47,6 +47,9 @@ class OsmApiSession:
If the requested element has been deleted,
`OsmApi.ElementDeletedApiError` is raised.
+ If the requested element can not be found,
+ `OsmApi.ElementNotFoundApiError` is raised.
+
If the response status code indicates an error,
`OsmApi.ApiError` is raised.
"""
@@ -69,7 +72,13 @@ class OsmApiSession:
)
if response.status_code != 200:
payload = response.content.strip()
- if response.status_code == 410:
+ if response.status_code == 404:
+ raise errors.ElementNotFoundApiError(
+ response.status_code,
+ response.reason,
+ payload
+ )
+ elif response.status_code == 410:
raise errors.ElementDeletedApiError(
response.status_code,
response.reason,
=====================================
osmapi/parser.py
=====================================
@@ -113,7 +113,7 @@ def ParseNotes(data):
{ ... }
]
"""
- noteElements = dom.OsmResponseToDom(data, tag="note")
+ noteElements = dom.OsmResponseToDom(data, tag="note", allow_empty=True)
result = []
for noteElement in noteElements:
note = dom.DomParseNote(noteElement)
=====================================
tests/changeset_test.py
=====================================
@@ -76,10 +76,10 @@ def test_ChangesetUpdate(auth_api, add_response):
result = auth_api.ChangesetUpdate({'test': 'foobar'})
changeset_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osm version="0.6" generator="osmapi/3.1.0">\n'
b' <changeset visible="true">\n'
b' <tag k="test" v="foobar"/>\n'
- b' <tag k="created_by" v="osmapi/3.0.0"/>\n'
+ b' <tag k="created_by" v="osmapi/3.1.0"/>\n'
b' </changeset>\n'
b'</osm>\n'
)
@@ -104,7 +104,7 @@ def test_ChangesetUpdate_with_created_by(auth_api, add_response):
)
changeset_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osm version="0.6" generator="osmapi/3.1.0">\n'
b' <changeset visible="true">\n'
b' <tag k="test" v="foobar"/>\n'
b' <tag k="created_by" v="MyTestOSMApp"/>\n'
@@ -132,10 +132,10 @@ def test_ChangesetCreate(auth_api, add_response):
changeset_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osm version="0.6" generator="osmapi/3.1.0">\n'
b' <changeset visible="true">\n'
b' <tag k="foobar" v="A new test changeset"/>\n'
- b' <tag k="created_by" v="osmapi/3.0.0"/>\n'
+ b' <tag k="created_by" v="osmapi/3.1.0"/>\n'
b' </changeset>\n'
b'</osm>\n'
)
@@ -155,7 +155,7 @@ def test_ChangesetCreate_with_created_by(auth_api, add_response):
changeset_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osm version="0.6" generator="osmapi/3.1.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'
@@ -179,6 +179,16 @@ def test_ChangesetCreate_with_open_changeset(auth_api, add_response):
assert str(execinfo.value) == 'Changeset already opened'
+def test_ChangesetCreate_with_prod_api_and_test_comment(prod_api):
+ with pytest.raises(osmapi.OsmApiError) as execinfo:
+ prod_api.ChangesetCreate(
+ {
+ 'comment': 'My first test',
+ }
+ )
+ assert str(execinfo.value) == 'DO NOT CREATE test changesets on the production server'
+
+
def test_ChangesetClose(auth_api, add_response):
# setup mock
resp = add_response(PUT, '/changeset/create', filename='test_Changeset_create.xml')
@@ -219,7 +229,7 @@ def test_ChangesetUpload_create_node(auth_api, add_response):
upload_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osmChange version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osmChange version="0.6" generator="osmapi/3.1.0">\n'
b'<create>\n'
b' <node lat="47.123" lon="8.555" visible="true" '
b'changeset="4444">\n'
@@ -287,7 +297,7 @@ def test_ChangesetUpload_modify_way(auth_api, add_response):
upload_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osmChange version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osmChange version="0.6" generator="osmapi/3.1.0">\n'
b'<modify>\n'
b' <way id="4294967296" version="2" visible="true" '
b'changeset="4444">\n'
@@ -366,7 +376,7 @@ def test_ChangesetUpload_delete_relation(auth_api, add_response):
upload_xml = xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osmChange version="0.6" generator="osmapi/3.0.0">\n'
+ b'<osmChange version="0.6" generator="osmapi/3.1.0">\n'
b'<delete>\n'
b' <relation id="676" version="2" visible="true" '
b'changeset="4444">\n'
=====================================
tests/conftest.py
=====================================
@@ -51,6 +51,20 @@ def auth_api():
api.close()
+ at pytest.fixture
+def prod_api():
+ api_base = "https://www.openstreetmap.org"
+ api = osmapi.OsmApi(
+ api=api_base,
+ username='testuser',
+ password='testpassword'
+ )
+ api._session._sleep = mock.Mock()
+
+ yield api
+ api.close()
+
+
@pytest.fixture
def mocked_responses():
with responses.RequestsMock() as rsps:
=====================================
tests/fixtures/test_NoteAlreadyClosed.xml
=====================================
@@ -0,0 +1 @@
+The note 819 was closed at 2022-04-29 20:57:20 UTC
=====================================
tests/fixtures/test_NoteCommentOnClosedNote.xml
=====================================
@@ -0,0 +1 @@
+The note 817 was closed at 2022-04-29 20:57:20 UTC
=====================================
tests/fixtures/test_NotesGet_empty.xml
=====================================
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+</osm>
=====================================
tests/notes_test.py
=====================================
@@ -63,6 +63,29 @@ class TestOsmApiNotes(osmapi_test.TestOsmApi):
]
})
+ def test_NotesGet_empty(self):
+ self._session_mock()
+
+ result = self.api.NotesGet(
+ -93.8472901,
+ 35.9763601,
+ -80,
+ 36.176360100000004,
+ limit=1,
+ closed=0
+ )
+
+ args, kwargs = self.session_mock.request.call_args
+ self.assertEqual(args[0], 'GET')
+ urlParts = urlparse.urlparse(args[1])
+ params = urlparse.parse_qs(urlParts.query)
+
+ self.assertEqual(params['limit'][0], '1')
+ self.assertEqual(params['closed'][0], '0')
+
+ self.assertEqual(len(result), 0)
+ self.assertEqual(result, [])
+
def test_NoteGet(self):
self._session_mock()
@@ -257,16 +280,36 @@ class TestOsmApiNotes(osmapi_test.TestOsmApi):
]
})
+ def test_NoteCommentOnClosedNote(self):
+ self._session_mock(status=409)
+
+ with self.assertRaises(osmapi.NoteAlreadyClosedApiError) as cm:
+ self.api.NoteComment(817, 'Comment on closed note')
+
+ self.assertEqual(cm.exception.status, 409)
+ self.assertEqual(
+ cm.exception.payload,
+ "The note 817 was closed at 2022-04-29 20:57:20 UTC"
+ )
+
+ def test_NoteComment_non_existing_note(self):
+ self._session_mock(status=404)
+
+ with self.assertRaises(osmapi.ElementNotFoundApiError) as cm:
+ self.api.NoteComment(817, 'Comment on closed note')
+
+ self.assertEqual(cm.exception.status, 404)
+
def test_NoteClose(self):
self._session_mock(auth=True)
- result = self.api.NoteClose(814, 'Close this note!')
+ result = self.api.NoteClose(819, 'Close this note!')
args, kwargs = self.session_mock.request.call_args
self.assertEqual(args[0], 'POST')
self.assertEqual(
args[1],
- self.api_base + '/api/0.6/notes/814/close?text=Close+this+note%21'
+ self.api_base + '/api/0.6/notes/819/close?text=Close+this+note%21'
)
self.assertEqual(result, {
@@ -296,6 +339,18 @@ class TestOsmApiNotes(osmapi_test.TestOsmApi):
]
})
+ def test_NoteAlreadyClosed(self):
+ self._session_mock(auth=True, status=409)
+
+ with self.assertRaises(osmapi.NoteAlreadyClosedApiError) as cm:
+ self.api.NoteClose(819, 'Close this note!')
+
+ self.assertEqual(cm.exception.status, 409)
+ self.assertEqual(
+ cm.exception.payload,
+ "The note 819 was closed at 2022-04-29 20:57:20 UTC"
+ )
+
def test_NoteReopen(self):
self._session_mock(auth=True)
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/8a5d345e4866a702d7613ed3bea6baaa45293db5
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/8a5d345e4866a702d7613ed3bea6baaa45293db5
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20230118/b010d8c4/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list