[Git][debian-gis-team/python-osmapi][upstream] New upstream version 4.3.0+ds

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Tue Jan 21 14:29:18 GMT 2025



Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-osmapi


Commits:
5bb1c0b6 by Bas Couwenberg at 2025-01-21T15:23:18+01:00
New upstream version 4.3.0+ds
- - - - -


13 changed files:

- .github/workflows/build.yml
- CHANGELOG.md
- README.md
- + examples/error_handling.py
- + examples/log_output.py
- examples/write_to_osm.py
- osmapi/OsmApi.py
- osmapi/__init__.py
- osmapi/errors.py
- osmapi/http.py
- osmapi/parser.py
- tests/changeset_test.py
- tests/fixtures/test_NoteCreate_wo_auth.xml


Changes:

=====================================
.github/workflows/build.yml
=====================================
@@ -2,7 +2,7 @@ name: Test osmapi package
 on:
   pull_request:
   push:
-    branches: [master, develop]
+    branches: [main, develop]
 jobs:
   test:
     runs-on: ubuntu-latest


=====================================
CHANGELOG.md
=====================================
@@ -4,6 +4,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
 
 ## [Unreleased]
 
+## [4.3.0] - 2025-01-21
+### Added
+- New `ConnectionApiError` when a connection or network error occurs (see issue #176, thanks [Mateusz Konieczny](https://github.com/matkoniecz))
+- 
+### Changed
+- Use the pattern `raise XYError from e` to explicitly add the original exceptions as the cause for a new (wrapped) exception.
+
+### Removed
+- Remove u string prefix (see PR #180, thanks [Boris Verkhovskiy](https://github.com/verhovsky))
+
 ## [4.2.0] - 2024-08-08
 ### Added
 - Add a new `timeout` parameter to `OsmApi` which allows to set a timeout in seconds (default is 30s) for the API requests (see issue #170, thanks [Mateusz Konieczny](https://github.com/matkoniecz))
@@ -357,7 +367,8 @@ Miroslav Šedivý
 - `Fixed` for any bug fixes.
 - `Security` to invite users to upgrade in case of vulnerabilities.
 
-[Unreleased]: https://github.com/metaodi/osmapi/compare/v4.2.0...HEAD
+[Unreleased]: https://github.com/metaodi/osmapi/compare/v4.3.0...HEAD
+[4.3.0]: https://github.com/metaodi/osmapi/compare/v4.2.0...v4.3.0
 [4.2.0]: https://github.com/metaodi/osmapi/compare/v4.1.0...v4.2.0
 [4.1.0]: https://github.com/metaodi/osmapi/compare/v4.0.0...v4.1.0
 [4.0.0]: https://github.com/metaodi/osmapi/compare/v3.1.0...v4.0.0


=====================================
README.md
=====================================
@@ -3,7 +3,7 @@ osmapi
 
 [![Build osmapi](https://github.com/metaodi/osmapi/actions/workflows/build.yml/badge.svg)](https://github.com/metaodi/osmapi/actions/workflows/build.yml)
 [![Version](https://img.shields.io/pypi/v/osmapi.svg)](https://pypi.python.org/pypi/osmapi/)
-[![License](https://img.shields.io/pypi/l/osmapi.svg)](https://github.com/metaodi/osmapi/blob/master/LICENSE.txt)
+[![License](https://img.shields.io/pypi/l/osmapi.svg)](https://github.com/metaodi/osmapi/blob/develop/LICENSE.txt)
 [![Coverage](https://img.shields.io/coveralls/metaodi/osmapi/develop.svg)](https://coveralls.io/r/metaodi/osmapi?branch=develop)
 [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
@@ -26,7 +26,7 @@ The build the documentation locally, you can use
     make docs
 
 This project uses GitHub Pages to publish its documentation.
-To update the online documentation, you need to re-generate the documentation with the above command and update the `master` branch of this repository.
+To update the online documentation, you need to re-generate the documentation with the above command and update the `main` branch of this repository.
 
 ## Examples
 
@@ -40,32 +40,21 @@ Check the [examples directory](https://github.com/metaodi/osmapi/tree/develop/ex
 >>> import osmapi
 >>> api = osmapi.OsmApi()
 >>> print(api.NodeGet(123))
-{u'changeset': 532907, u'uid': 14298,
-u'timestamp': u'2007-09-29T09:19:17Z',
-u'lon': 10.790009299999999, u'visible': True,
-u'version': 1, u'user': u'Mede',
-u'lat': 59.9503044, u'tag': {}, u'id': 123}
+{'changeset': 532907, 'uid': 14298,
+'timestamp': '2007-09-29T09:19:17Z',
+'lon': 10.790009299999999, 'visible': True,
+'version': 1, 'user': 'Mede',
+'lat': 59.9503044, 'tag': {}, 'id': 123}
 ```
 
-### Constructor
-
-```python
-import osmapi
-api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", username = "you", password = "***")
-api = osmapi.OsmApi(username = "you", passwordfile = "/etc/mypasswords")
-api = osmapi.OsmApi(passwordfile = "/etc/mypasswords") # if only the passwordfile is specified, the credentials on the first line of the file will be used
-```
-
-Note: Each line in the password file should have the format _user:password_
-
 ### Write to OpenStreetMap
 
 ```python
 >>> import osmapi
->>> api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", username = u"metaodi", password = u"*******")
->>> api.ChangesetCreate({u"comment": u"My first test"})
->>> print(api.NodeCreate({u"lon":1, u"lat":1, u"tag": {}}))
-{u'changeset': 532907, u'lon': 1, u'version': 1, u'lat': 1, u'tag': {}, u'id': 164684}
+>>> api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", username = "metaodi", password = "*******")
+>>> api.ChangesetCreate({"comment": "My first test"})
+>>> print(api.NodeCreate({"lon":1, "lat":1, "tag": {}}))
+{'changeset': 532907, 'lon': 1, 'version': 1, 'lat': 1, 'tag': {}, 'id': 164684}
 >>> api.ChangesetClose()
 ```
 
@@ -107,6 +96,24 @@ with api.Changeset({"comment": "My first test"}) as changeset_id:
 An alternative way using the `requests-oauthlib` library can be found
 [in the examples](https://github.com/metaodi/osmapi/blob/develop/examples/oauth2.py).
 
+
+### User agent / credit for application
+
+To credit the application that supplies changes to OSM, an `appid` can be provided.
+This is a string identifying the application.
+If this is omitted "osmapi" is used.
+
+```python
+api = osmapi.OsmApi(
+    api="https://api06.dev.openstreetmap.org",
+    appid="MyOSM Script"
+)
+```
+
+ If then changesets are made using this osmapi instance, they get a tag `created_by` with the following content: `MyOSM Script (osmapi/<version>)` 
+ 
+ [Example changeset of `Kort` using osmapi](https://www.openstreetmap.org/changeset/55197785)
+
 ## Note about imports / automated edits
 
 Scripted imports and automated edits should only be carried out by those with experience and understanding of the way the OpenStreetMap community creates maps, and only with careful **planning** and **consultation** with the local community.
@@ -140,8 +147,8 @@ To create a new release, follow these steps (please respect [Semantic Versioning
 1. Adapt the version number in `osmapi/__init__.py`
 1. Update the CHANGELOG with the version
 1. Re-build the documentation (`make docs`)
-1. Create a pull request to merge develop into master (make sure the tests pass!)
-1. Create a [new release/tag on GitHub](https://github.com/metaodi/osmapi/releases) (on the master branch)
+1. Create a [pull request to merge develop into main](https://github.com/metaodi/osmapi/compare/main...develop) (make sure the tests pass!)
+1. Create a [new release/tag on GitHub](https://github.com/metaodi/osmapi/releases) (on the main branch)
 1. The [publication on PyPI](https://pypi.python.org/pypi/osmapi) happens via [GitHub Actions](https://github.com/metaodi/osmapi/actions/workflows/publish_python.yml) on every tagged commit
 
 ## Attribution


=====================================
examples/error_handling.py
=====================================
@@ -0,0 +1,144 @@
+from dotenv import load_dotenv, find_dotenv
+from oauthcli import OpenStreetMapDevAuth
+from oauthlib.oauth2.rfc6749.errors import OAuth2Error
+import logging
+import os
+import osmapi
+import requests
+import subprocess
+import sys
+import urllib3
+
+
+load_dotenv(find_dotenv())
+
+# logging setup
+log = logging.getLogger(__name__)
+loglevel = logging.DEBUG
+logging.basicConfig(
+    format="%(asctime)s %(name)s %(levelname)-8s %(message)s",
+    level=loglevel,
+    datefmt="%Y-%m-%d %H:%M:%S",
+)
+logging.captureWarnings(True)
+
+# shut up DEBUG messages of specific loggers
+logging.getLogger(osmapi.dom.__name__).setLevel(logging.INFO)
+logging.getLogger(urllib3.__name__).setLevel(logging.INFO)
+
+
+def clear_screen():
+    # check and make call for specific operating system
+    _ = subprocess.call("clear" if os.name == "posix" else "cls")
+
+
+# The error handling with osmapi is very easy, simply catch the
+# exception for the specific case you want to handle.
+# - All osmapi excepctions are child classes of osmapi.OsmApiError
+# - Errors that result from the communication with the OSM server osmapi.ApiError
+# - There are a number of subclasses to differantiate the different errors
+# - catch more specific errors first, then use more general error classes
+
+# Upload data to OSM without a changeset
+log.debug("Try to write data to OSM without a changeset")
+api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
+try:
+    node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
+except osmapi.NoChangesetOpenError as e:
+    log.exception(e)
+    log.debug("There is no open changeset")
+input("Press Enter to continue...")
+clear_screen()
+
+
+# wrong server: ConnectionError
+log.debug("Connect to wrong server...")
+api = osmapi.OsmApi(api="https://invalid.server.name")
+try:
+    api.ChangesetGet(123)
+except osmapi.ConnectionApiError as e:
+    log.exception(e)
+    log.debug("Error connecting to server")
+input("Press Enter to continue...")
+clear_screen()
+
+
+# changeset not found: ElementNotFoundApiError
+log.debug("Request non-existent changeset id...")
+api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
+try:
+    api.ChangesetGet(111111111111)
+except osmapi.ElementNotFoundApiError as e:
+    log.exception(e)
+    log.debug("Changeset not found")
+input("Press Enter to continue...")
+clear_screen()
+
+
+# unauthorized request
+log.debug("Try to add data with wrong authorization")
+try:
+    s = requests.Session()
+    s.auth = ("user", "pass")
+    api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=s)
+    with api.Changeset({"comment": "My first test"}) as changeset_id:
+        node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
+except osmapi.UnauthorizedApiError as e:
+    log.exception(e)
+    log.debug("Unauthorized to make this request")
+input("Press Enter to continue...")
+clear_screen()
+
+# request without auhorization
+log.debug("Try to add data without authorization")
+try:
+    api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
+    with api.Changeset({"comment": "My first test"}) as changeset_id:
+        node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
+except osmapi.UsernamePasswordMissingError as e:
+    log.exception(e)
+    log.debug("Username/Password or authorization missing")
+input("Press Enter to continue...")
+clear_screen()
+
+
+# a more or less complete "real-life" example
+client_id = os.getenv("OSM_OAUTH_CLIENT_ID")
+client_secret = os.getenv("OSM_OAUTH_CLIENT_SECRET")
+
+try:
+    auth = OpenStreetMapDevAuth(
+        client_id, client_secret, ["write_api", "write_notes"]
+    ).auth_code()
+except OAuth2Error as e:
+    log.exception(e)
+    log.debug("An OAuth2 error occured")
+    sys.exit(1)
+
+try:
+    api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=auth.session)
+    with api.Changeset({"comment": "My first test"}) as changeset_id:
+        log.debug(f"Part of Changeset {changeset_id}")
+        node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})
+        log.debug(node1)
+
+    # get all the info from the closed changeset
+    changeset = api.ChangesetGet(changeset_id)
+    log.debug(changeset)
+    exit_code = 0
+except osmapi.ConnectionApiError as e:
+    log.debug(f"Connection error: {str(e)}")
+    exit_code = 1
+    # display error for user, try again?
+except osmapi.ElementNotFoundApiError as e:
+    log.debug(f"Changeset not found: {str(e)}")
+    exit_code = 1
+except osmapi.ApiError as e:
+    log.debug(f"Error on the API side: {str(e)}")
+    exit_code = 1
+except osmapi.OsmApiError as e:
+    log.debug(f"Some other error: {str(e)}")
+    exit_code = 1
+finally:
+    log.debug(f"Exit code: {exit_code}")
+    sys.exit(exit_code)


=====================================
examples/log_output.py
=====================================
@@ -0,0 +1,23 @@
+import osmapi
+import logging
+from pprint import pformat
+import urllib3
+
+log = logging.getLogger(__name__)
+
+loglevel = logging.DEBUG
+logging.basicConfig(
+    format="%(asctime)s %(name)s %(levelname)-8s %(message)s",
+    level=loglevel,
+    datefmt="%Y-%m-%d %H:%M:%S",
+)
+logging.captureWarnings(True)
+
+# shut up DEBUG messages of specific loggers
+logging.getLogger(osmapi.dom.__name__).setLevel(logging.INFO)
+logging.getLogger(urllib3.__name__).setLevel(logging.INFO)
+
+
+api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org")
+node1 = api.NodeGet("1111")
+log.debug(pformat(node1))


=====================================
examples/write_to_osm.py
=====================================
@@ -1,14 +1,21 @@
+# install cli-oauth2 for requests:  pip install cli-oauth2
+from oauthcli import OpenStreetMapDevAuth
 import osmapi
 from dotenv import load_dotenv, find_dotenv
 import os
 
 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
-)
+# load secrets for OAuth
+client_id = os.getenv("OSM_OAUTH_CLIENT_ID")
+client_secret = os.getenv("OSM_OAUTH_CLIENT_SECRET")
+
+auth = OpenStreetMapDevAuth(
+    client_id, client_secret, ["write_api", "write_notes"]
+).auth_code()
+
+
+api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", session=auth.session)
 with api.Changeset({"comment": "My first test"}) as changeset_id:
     print(f"Part of Changeset {changeset_id}")
     node1 = api.NodeCreate({"lon": 1, "lat": 1, "tag": {}})


=====================================
osmapi/OsmApi.py
=====================================
@@ -1183,7 +1183,7 @@ class OsmApi:
 
             with osmapi.Changeset({"comment": "Import script XYZ"}) as changeset_id:
                 print(f"Part of changeset {changeset_id}")
-                api.NodeCreate({u"lon":1, u"lat":1, u"tag": {}})
+                api.NodeCreate({"lon":1, "lat":1, "tag": {}})
 
         If `ChangesetTags` are given, this tags are applied (key/value).
 
@@ -1265,7 +1265,9 @@ class OsmApi:
             )
         except errors.ApiError as e:
             if e.status == 409:
-                raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                raise errors.ChangesetClosedApiError(
+                    e.status, e.reason, e.payload
+                ) from e
             else:
                 raise
         return self._CurrentChangesetId
@@ -1332,7 +1334,9 @@ class OsmApi:
             self._CurrentChangesetId = 0
         except errors.ApiError as e:
             if e.status == 409:
-                raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                raise errors.ChangesetClosedApiError(
+                    e.status, e.reason, e.payload
+                ) from e
             else:
                 raise
         return CurrentChangesetId
@@ -1376,7 +1380,9 @@ class OsmApi:
             if e.status == 409 and re.search(
                 r"The changeset .* was closed at .*", e.payload
             ):
-                raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                raise errors.ChangesetClosedApiError(
+                    e.status, e.reason, e.payload
+                ) from e
             else:
                 raise
         try:
@@ -1386,7 +1392,7 @@ class OsmApi:
         except (xml.parsers.expat.ExpatError, IndexError) as e:
             raise errors.XmlResponseInvalidError(
                 f"The XML response from the OSM API is invalid: {e!r}"
-            )
+            ) from e
 
         for change in ChangesData:
             if change["action"] == "delete":
@@ -1509,7 +1515,9 @@ class OsmApi:
             )
         except errors.ApiError as e:
             if e.status == 409:
-                raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                raise errors.ChangesetClosedApiError(
+                    e.status, e.reason, e.payload
+                ) from e
             else:
                 raise
         changeset = dom.OsmResponseToDom(data, tag="changeset", single=True)
@@ -1548,7 +1556,9 @@ class OsmApi:
             )
         except errors.ApiError as e:
             if e.status == 409:
-                raise errors.AlreadySubscribedApiError(e.status, e.reason, e.payload)
+                raise errors.AlreadySubscribedApiError(
+                    e.status, e.reason, e.payload
+                ) from e
             else:
                 raise
         changeset = dom.OsmResponseToDom(data, tag="changeset", single=True)
@@ -1586,7 +1596,7 @@ class OsmApi:
                 f"/api/0.6/changeset/{ChangesetId}/unsubscribe", None, forceAuth=True
             )
         except errors.ElementNotFoundApiError as e:
-            raise errors.NotSubscribedApiError(e.status, e.reason, e.payload)
+            raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e
 
         changeset = dom.OsmResponseToDom(data, tag="changeset", single=True)
         return dom.DomParseChangeset(changeset)
@@ -1767,7 +1777,9 @@ class OsmApi:
             result = self._session._post(uri, None, optionalAuth=optionalAuth)
         except errors.ApiError as e:
             if e.status == 409:
-                raise errors.NoteAlreadyClosedApiError(e.status, e.reason, e.payload)
+                raise errors.NoteAlreadyClosedApiError(
+                    e.status, e.reason, e.payload
+                ) from e
             else:
                 raise
 
@@ -1844,13 +1856,17 @@ class OsmApi:
                 if e.status == 409 and re.search(
                     r"The changeset .* was closed at .*", e.payload
                 ):
-                    raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                    raise errors.ChangesetClosedApiError(
+                        e.status, e.reason, e.payload
+                    ) from e
                 elif e.status == 409:
-                    raise errors.VersionMismatchApiError(e.status, e.reason, e.payload)
+                    raise errors.VersionMismatchApiError(
+                        e.status, e.reason, e.payload
+                    ) from e
                 elif e.status == 412:
                     raise errors.PreconditionFailedApiError(
                         e.status, e.reason, e.payload
-                    )
+                    ) from e
                 else:
                     raise
             OsmData["id"] = int(result.strip())
@@ -1867,13 +1883,17 @@ class OsmApi:
                 if e.status == 409 and re.search(
                     r"The changeset .* was closed at .*", e.payload
                 ):
-                    raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                    raise errors.ChangesetClosedApiError(
+                        e.status, e.reason, e.payload
+                    ) from e
                 elif e.status == 409:
-                    raise errors.VersionMismatchApiError(e.status, e.reason, e.payload)
+                    raise errors.VersionMismatchApiError(
+                        e.status, e.reason, e.payload
+                    ) from e
                 elif e.status == 412:
                     raise errors.PreconditionFailedApiError(
                         e.status, e.reason, e.payload
-                    )
+                    ) from e
                 else:
                     raise
             OsmData["version"] = int(result.strip())
@@ -1888,13 +1908,17 @@ class OsmApi:
                 if e.status == 409 and re.search(
                     r"The changeset .* was closed at .*", e.payload
                 ):
-                    raise errors.ChangesetClosedApiError(e.status, e.reason, e.payload)
+                    raise errors.ChangesetClosedApiError(
+                        e.status, e.reason, e.payload
+                    ) from e
                 elif e.status == 409:
-                    raise errors.VersionMismatchApiError(e.status, e.reason, e.payload)
+                    raise errors.VersionMismatchApiError(
+                        e.status, e.reason, e.payload
+                    ) from e
                 elif e.status == 412:
                     raise errors.PreconditionFailedApiError(
                         e.status, e.reason, e.payload
-                    )
+                    ) from e
                 else:
                     raise
             OsmData["version"] = int(result.strip())


=====================================
osmapi/__init__.py
=====================================
@@ -1,4 +1,4 @@
-__version__ = "4.2.0"
+__version__ = "4.3.0"
 
 from .OsmApi import *  # noqa
 from .errors import *  # noqa


=====================================
osmapi/errors.py
=====================================
@@ -150,3 +150,10 @@ class TimeoutApiError(ApiError):
     """
     Error if the http request ran into a timeout
     """
+
+
+class ConnectionApiError(ApiError):
+    """
+    Error if there was a network error (e.g. DNS failure, refused connection)
+    while connecting to the remote server.
+    """


=====================================
osmapi/http.py
=====================================
@@ -73,12 +73,14 @@ class OsmApiSession:
             response = self._session.request(
                 method, path, data=send, timeout=self._timeout
             )
-        except requests.exceptions.Timeout:
+        except requests.exceptions.Timeout as e:
             raise errors.TimeoutApiError(
                 0, f"Request timed out (timeout={self._timeout})", ""
-            )
+            ) from e
+        except requests.exceptions.ConnectionError as e:
+            raise errors.ConnectionApiError(0, f"Connection error: {str(e)}", "") from e
         except requests.exceptions.RequestException as e:
-            raise errors.ApiError(0, str(e), "")
+            raise errors.ApiError(0, str(e), "") from e
 
         if response.status_code != 200:
             payload = response.content.strip()
@@ -119,6 +121,8 @@ class OsmApiSession:
                 else:
                     logger.exception("ApiError Exception occured")
                     raise
+            except errors.UsernamePasswordMissingError:
+                raise
             except Exception as e:
                 logger.exception("General exception occured")
                 if i == self.MAX_RETRY_LIMIT:
@@ -126,7 +130,7 @@ class OsmApiSession:
                         raise
                     raise errors.MaximumRetryLimitReachedError(
                         f"Give up after {i} retries"
-                    )
+                    ) from e
                 if i != 1:
                     self._sleep()
                 self._session = self._get_http_session()


=====================================
osmapi/parser.py
=====================================
@@ -23,7 +23,7 @@ def ParseOsm(data):
     except (xml.parsers.expat.ExpatError, IndexError) as e:
         raise errors.XmlResponseInvalidError(
             f"The XML response from the OSM API is invalid: {e!r}"
-        )
+        ) from e
 
     result = []
     for elem in data.childNodes:
@@ -55,7 +55,7 @@ def ParseOsc(data):
     except (xml.parsers.expat.ExpatError, IndexError) as e:
         raise errors.XmlResponseInvalidError(
             f"The XML response from the OSM API is invalid: {e!r}"
-        )
+        ) from e
 
     result = []
     for action in data.childNodes:


=====================================
tests/changeset_test.py
=====================================
@@ -63,6 +63,23 @@ def test_ChangesetGet(api, add_response):
     assert result == test_changeset
 
 
+def test_ChangesetGet_with_connection_error(api, add_response):
+    # Setup mock
+    add_response(
+        GET,
+        "/changeset/123",
+        body=requests.exceptions.ConnectionError("Connection aborted."),
+    ),
+
+    # Call
+    with pytest.raises(osmapi.ConnectionApiError) as execinfo:
+        api.ChangesetGet(123)
+    assert (
+        str(execinfo.value)
+        == "Request failed: 0 - Connection error: Connection aborted. - "
+    )
+
+
 def test_ChangesetGet_with_timeout(api, add_response):
     # Setup mock
     add_response(GET, "/changeset/123", body=requests.exceptions.Timeout())
@@ -87,10 +104,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/4.2.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.3.0">\n'
         b'  <changeset visible="true">\n'
         b'    <tag k="test" v="foobar"/>\n'
-        b'    <tag k="created_by" v="osmapi/4.2.0"/>\n'
+        b'    <tag k="created_by" v="osmapi/4.3.0"/>\n'
         b"  </changeset>\n"
         b"</osm>\n"
     )
@@ -110,7 +127,7 @@ def test_ChangesetUpdate_with_created_by(auth_api, add_response):
     result = auth_api.ChangesetUpdate({"test": "foobar", "created_by": "MyTestOSMApp"})
     changeset_xml = xmltosorteddict(
         b'<?xml version="1.0" encoding="UTF-8"?>\n'
-        b'<osm version="0.6" generator="osmapi/4.2.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.3.0">\n'
         b'  <changeset visible="true">\n'
         b'    <tag k="test" v="foobar"/>\n'
         b'    <tag k="created_by" v="MyTestOSMApp"/>\n'
@@ -134,10 +151,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/4.2.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.3.0">\n'
         b'  <changeset visible="true">\n'
         b'    <tag k="foobar" v="A new test changeset"/>\n'
-        b'    <tag k="created_by" v="osmapi/4.2.0"/>\n'
+        b'    <tag k="created_by" v="osmapi/4.3.0"/>\n'
         b"  </changeset>\n"
         b"</osm>\n"
     )
@@ -157,7 +174,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/4.2.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.3.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'
@@ -237,7 +254,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/4.2.0">\n'
+        b'<osmChange version="0.6" generator="osmapi/4.3.0">\n'
         b"<create>\n"
         b'  <node lat="47.123" lon="8.555" visible="true" '
         b'changeset="4444">\n'
@@ -309,7 +326,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/4.2.0">\n'
+        b'<osmChange version="0.6" generator="osmapi/4.3.0">\n'
         b"<modify>\n"
         b'  <way id="4294967296" version="2" visible="true" '
         b'changeset="4444">\n'
@@ -382,7 +399,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/4.2.0">\n'
+        b'<osmChange version="0.6" generator="osmapi/4.3.0">\n'
         b"<delete>\n"
         b'  <relation id="676" version="2" visible="true" '
         b'changeset="4444">\n'


=====================================
tests/fixtures/test_NoteCreate_wo_auth.xml
=====================================
@@ -18,15 +18,15 @@
 </note>
 </osm>
 
-{u'comments': [{u'action': u'opened',
-                u'date': u'2014-10-03 16:09:12 UTC',
-                u'html': u'<p>This is an unauthenticated test</p>',
-                u'text': u'This is an unauthenticated test',
-                u'uid': None,
-                u'user': None}],
- u'date_closed': None,
- u'date_created': u'2014-10-03 16:09:11 UTC',
- u'id': u'824',
- u'lat': 47.123,
- u'lon': 8.432,
- u'status': u'open'}
+{'comments': [{'action': 'opened',
+                'date': '2014-10-03 16:09:12 UTC',
+                'html': '<p>This is an unauthenticated test</p>',
+                'text': 'This is an unauthenticated test',
+                'uid': None,
+                'user': None}],
+ 'date_closed': None,
+ 'date_created': '2014-10-03 16:09:11 UTC',
+ 'id': '824',
+ 'lat': 47.123,
+ 'lon': 8.432,
+ 'status': 'open'}



View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/5bb1c0b6f23967d1c5fe6ca47e53b1021c8ade64

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/5bb1c0b6f23967d1c5fe6ca47e53b1021c8ade64
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/20250121/7f72df59/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list