[Git][debian-gis-team/python-osmapi][master] 6 commits: New upstream version 3.1.0+ds

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Wed Jan 18 16:16:48 GMT 2023



Bas Couwenberg pushed to branch master 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
- - - - -
e71cafd7 by Bas Couwenberg at 2023-01-18T16:39:04+01:00
Update upstream source from tag 'upstream/3.1.0+ds'

Update to upstream version '3.1.0+ds'
with Debian dir 47df6a2e95e94cf0e3556ee6e06e9fb4316d360e
- - - - -
d906f744 by Bas Couwenberg at 2023-01-18T16:39:22+01:00
New upstream release.

- - - - -
4a2667c5 by Bas Couwenberg at 2023-01-18T16:43:42+01:00
Update copyright file.

- - - - -
1e4c1c2c by Bas Couwenberg at 2023-01-18T16:46:10+01:00
Drop obsolete tox build dependency.

- - - - -
70b79a11 by Bas Couwenberg at 2023-01-18T17:16:35+01:00
Bump Standards-Version to 4.6.2, no changes.

- - - - -


17 changed files:

- CHANGELOG.md
- debian/changelog
- debian/control
- debian/copyright
- + 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


=====================================
debian/changelog
=====================================
@@ -1,9 +1,12 @@
-python-osmapi (3.0.0+ds-2) UNRELEASED; urgency=medium
+python-osmapi (3.1.0+ds-1) UNRELEASED; urgency=medium
 
-  * Bump Standards-Version to 4.6.1, no changes.
+  * New upstream release.
+  * Bump Standards-Version to 4.6.2, no changes.
   * Add Rules-Requires-Root to control file.
+  * Update copyright file.
+  * Drop obsolete tox build dependency.
 
- -- Bas Couwenberg <sebastic at debian.org>  Tue, 21 Jun 2022 07:20:36 +0200
+ -- Bas Couwenberg <sebastic at debian.org>  Wed, 18 Jan 2023 16:39:10 +0100
 
 python-osmapi (3.0.0+ds-1) unstable; urgency=medium
 


=====================================
debian/control
=====================================
@@ -15,9 +15,8 @@ Build-Depends: debhelper-compat (= 12),
                python3-requests,
                python3-responses,
                python3-setuptools,
-               python3-xmltodict,
-               tox
-Standards-Version: 4.6.1
+               python3-xmltodict
+Standards-Version: 4.6.2
 Vcs-Browser: https://salsa.debian.org/debian-gis-team/python-osmapi
 Vcs-Git: https://salsa.debian.org/debian-gis-team/python-osmapi.git
 Homepage: https://wiki.openstreetmap.org/wiki/Osmapi


=====================================
debian/copyright
=====================================
@@ -26,6 +26,7 @@ Files: tests/fixtures/test_Capabilities.xml
  tests/fixtures/test_NodeGet.xml
  tests/fixtures/test_NodeHistory.xml
  tests/fixtures/test_NodeRelations.xml
+ tests/fixtures/test_NotesGet_empty.xml
  tests/fixtures/test_NodesGet.xml
  tests/fixtures/test_NodeWays.xml
  tests/fixtures/test_RelationFull.xml


=====================================
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/-/compare/d6107b91fbbe72b85c5132e09e299f1cb0e5758a...70b79a1111ceceaaa1e0422e4364b838d8c65847

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/compare/d6107b91fbbe72b85c5132e09e299f1cb0e5758a...70b79a1111ceceaaa1e0422e4364b838d8c65847
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/eca9e131/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list