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

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Thu Aug 8 15:56:02 BST 2024



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


Commits:
64f9267b by Bas Couwenberg at 2024-08-08T16:48:28+02:00
New upstream version 4.2.0+ds
- - - - -


17 changed files:

- .github/workflows/build.yml
- .github/workflows/publish_python.yml
- CHANGELOG.md
- README.md
- examples/oauth2_backend.py
- + examples/timeout.py
- osmapi/OsmApi.py
- osmapi/__init__.py
- osmapi/dom.py
- osmapi/errors.py
- osmapi/http.py
- requirements.txt
- build.sh → test.sh
- tests/changeset_test.py
- + tests/fixtures/test_ChangesetGetWithoutDiscussion.xml
- tests/fixtures/test_ChangesetUpload_create_node.xml
- tests/helper_test.py


Changes:

=====================================
.github/workflows/build.yml
=====================================
@@ -1,10 +1,10 @@
-name: Build osmapi
+name: Test osmapi package
 on:
   pull_request:
   push:
     branches: [master, develop]
 jobs:
-  build:
+  test:
     runs-on: ubuntu-latest
     timeout-minutes: 10
     strategy:
@@ -13,10 +13,10 @@ jobs:
         python-version: ["3.8", "3.9", "3.10", "3.11"]
 
     steps:
-    - uses: actions/checkout at v3
+    - uses: actions/checkout at v4
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python at v4
+      uses: actions/setup-python at v5
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -28,5 +28,5 @@ jobs:
         pip install -r test-requirements.txt
         pip install -e .
 
-    - name: Build the package 
-      run: ./build.sh
+    - name: Test the package 
+      run: ./test.sh


=====================================
.github/workflows/publish_python.yml
=====================================
@@ -2,28 +2,57 @@
 name: Upload Python Package
 
 on:
-  push:
-    # Sequence of patterns matched against refs/tags
-    tags:
-      - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
+  release:
+    types: [created]
+
+permissions:
+  contents: read
 
 jobs:
+  test:
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    strategy:
+      matrix:
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
+
+    steps:
+    - uses: actions/checkout at v4
+
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python at v5
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: Install dependencies
+      run: |
+        sudo apt-get install pandoc
+        python -m pip install --upgrade pip
+        pip install -r requirements.txt
+        pip install -r test-requirements.txt
+        pip install -e .
+
+    - name: Test the package 
+      run: ./test.sh
+        
   deploy:
     runs-on: ubuntu-latest
+    needs: [test]
+    environment: release
+    permissions:
+      id-token: write
     steps:
-    - uses: actions/checkout at v3
+    - uses: actions/checkout at v4
     - name: Set up Python
-      uses: actions/setup-python at v4
+      uses: actions/setup-python at v5
       with:
         python-version: '3.8'
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        pip install setuptools wheel twine
-    - name: Build and publish
-      env:
-        TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
-        TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+        pip install setuptools wheel build
+    - name: Build
       run: |
-        python setup.py sdist bdist_wheel
-        twine upload dist/*
+        python -m build
+    - name: Publish
+      uses: pypa/gh-action-pypi-publish at release/v1


=====================================
CHANGELOG.md
=====================================
@@ -4,6 +4,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
 
 ## [Unreleased]
 
+## [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))
+
+### Changed
+- Only include `discussion` key in result of `ChangesetGet` if `include_discussion=True` (see issue #163, thanks [Mateusz Konieczny](https://github.com/matkoniecz))
+- Update OAuth example in README using [cli-oauth2](https://github.com/Zverik/cli-oauth2) (see PR #169, thanks [Ilya Zverev](https://github.com/Zverik))
+
 ## [4.1.0] - 2024-03-19
 ### Added
 - OAuth 2.0 example in README and in the `examples` directory
@@ -349,7 +357,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.1.0...HEAD
+[Unreleased]: https://github.com/metaodi/osmapi/compare/v4.2.0...HEAD
+[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
 [3.1.0]: https://github.com/metaodi/osmapi/compare/v3.0.0...v3.1.0


=====================================
README.md
=====================================
@@ -71,66 +71,42 @@ Note: Each line in the password file should have the format _user:password_
 
 ### OAuth authentication
 
-Username/Password authentication will be deprecated in 2024 (see [official OWG announcemnt](https://www.openstreetmap.org/user/pnorman/diary/401157) for details).
+Username/Password authentication will be deprecated in July 2024
+(see [official OWG announcemnt](https://blog.openstreetmap.org/2024/04/17/oauth-1-0a-and-http-basic-auth-shutdown-on-openstreetmap-org/) for details).
 In order to use this library in the future, you'll need to use OAuth 2.0.
 
-To use OAuth 2.0, you must register an application with an OpenStreetMap account, either on the [development server](https://master.apis.dev.openstreetmap.org/oauth2/applications) or on the [production server](https://www.openstreetmap.org/oauth2/applications).
+To use OAuth 2.0, you must register an application with an OpenStreetMap account, either on the
+[development server](https://master.apis.dev.openstreetmap.org/oauth2/applications)
+or on the [production server](https://www.openstreetmap.org/oauth2/applications).
 Once this registration is done, you'll get a `client_id` and a `client_secret` that you can use to authenticate users.
 
-Example code using [`requests-oauth2client`](https://pypi.org/project/requests-oauth2client/):
+Example code using [`cli-oauth2`](https://github.com/Zverik/cli-oauth2) on the development server, replace `OpenStreetMapDevAuth` with `OpenStreetMapAuth` to use the production server:
 
 ```python
-from requests_oauth2client import OAuth2Client, OAuth2AuthorizationCodeAuth
-import requests
-import webbrowser
 import osmapi
-import os
+from oauthcli import OpenStreetMapDevAuth
 
 client_id = "<client_id>"
 client_secret = "<client_secret>"
 
-# special value for redirect_uri for non-web applications
-redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
+auth = OpenStreetMapDevAuth(
+    client_id, client_secret, ['read_prefs', 'write_map']
+).auth_code()
 
-authorization_base_url = "https://master.apis.dev.openstreetmap.org/oauth2/authorize"
-token_url = "https://master.apis.dev.openstreetmap.org/oauth2/token"
-
-oauth2client = OAuth2Client(
-    token_endpoint=token_url,
-    authorization_endpoint=authorization_base_url,
-    redirect_uri=redirect_uri,
-    client_id=client_id,
-    client_secret=client_secret,
-    code_challenge_method=None,
-)
-
-# open OSM website to authrorize user using the write_api and write_notes scope
-scope = ["write_api", "write_notes"]
-az_request = oauth2client.authorization_request(scope=scope)
-print(f"Authorize user using this URL: {az_request.uri}")
-webbrowser.open(az_request.uri)
-
-# create a new requests session using the OAuth authorization
-auth_code = input("Paste the authorization code here: ")
-auth = OAuth2AuthorizationCodeAuth(
-    oauth2client,
-    auth_code,
-    redirect_uri=redirect_uri,
-)
-oauth_session = requests.Session()
-oauth_session.auth = auth
-
-# use the custom session
 api = osmapi.OsmApi(
     api="https://api06.dev.openstreetmap.org",
-    session=oauth_session
+    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": {}})
     print(node1)
 ```
 
+An alternative way using the `requests-oauthlib` library can be found
+[in the examples](https://github.com/metaodi/osmapi/blob/develop/examples/oauth2.py).
+
 ## 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.


=====================================
examples/oauth2_backend.py
=====================================
@@ -19,6 +19,11 @@ load_dotenv(find_dotenv())
 client_id = os.getenv("OSM_OAUTH_CLIENT_ID")
 client_secret = os.getenv("OSM_OAUTH_CLIENT_SECRET")
 
+if client_id is None:
+    raise
+if client_secret is None:
+    raise
+
 # special value for redirect_uri for non-web applications
 redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
 


=====================================
examples/timeout.py
=====================================
@@ -0,0 +1,33 @@
+# 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())
+
+# 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()
+
+
+# Use a normal timeout (30s is the default value)
+normal_timeout_api = osmapi.OsmApi(
+    api="https://api06.dev.openstreetmap.org", session=auth.session, timeout=30
+)
+changeset_id = normal_timeout_api.ChangesetCreate({"comment": "My first test"})
+print(f"Create new changeset {changeset_id}")
+
+# Deliberately using a very small timeout to show what happens when a timeout occurs
+low_timeout_api = osmapi.OsmApi(
+    api="https://api06.dev.openstreetmap.org", session=auth.session, timeout=0.00001
+)
+try:
+    changeset_id = low_timeout_api.ChangesetCreate({"comment": "My first test"})
+    print(f"Create new changeset {changeset_id}")
+except osmapi.errors.TimeoutApiError as e:
+    print(f"Timeout error occured: {str(e)}")


=====================================
osmapi/OsmApi.py
=====================================
@@ -61,6 +61,7 @@ class OsmApi:
         changesetautosize=500,
         changesetautomulti=1,
         session=None,
+        timeout=30,
     ):
         """
         Initialized the OsmApi object.
@@ -93,6 +94,14 @@ class OsmApi:
         upload (default: 500) and `changesetautomulti` defines how many
         uploads should be made before closing a changeset and opening a new
         one (default: 1).
+
+        The `session` parameter can be used to provide a custom requests
+        http session object (requests.Session). This might be useful for
+        OAuth authentication, custom adapters, hooks etc.
+
+        Finally the `timeout` parameter is used by the http session to
+        throw an expcetion if the the timeout (in seconds) has passed without
+        an answer from the server.
         """
 
         # Get username
@@ -144,16 +153,24 @@ class OsmApi:
 
         # Http connection
         self.http_session = session
+        self._timeout = timeout
         auth = None
         if self._username and self._password:
             auth = (self._username, self._password)
         self._session = http.OsmApiSession(
-            self._api, self._created_by, auth=auth, session=self.http_session
+            self._api,
+            self._created_by,
+            auth=auth,
+            session=self.http_session,
+            timeout=self._timeout,
         )
 
     def __enter__(self):
         self._session = http.OsmApiSession(
-            self._api, self._created_by, session=self.http_session
+            self._api,
+            self._created_by,
+            session=self.http_session,
+            timeout=self._timeout,
         )
         return self
 
@@ -1218,10 +1235,10 @@ class OsmApi:
         """
         path = f"/api/0.6/changeset/{ChangesetId}"
         if include_discussion:
-            path += "?include_discussion=true"
+            path = f"{path}?include_discussion=true"
         data = self._session._get(path)
         changeset = dom.OsmResponseToDom(data, tag="changeset", single=True)
-        return dom.DomParseChangeset(changeset)
+        return dom.DomParseChangeset(changeset, include_discussion=include_discussion)
 
     def ChangesetUpdate(self, ChangesetTags={}):
         """
@@ -1345,10 +1362,8 @@ class OsmApi:
         data += self._created_by + '">\n'
         for change in ChangesData:
             data += "<" + change["action"] + ">\n"
-            change["data"]["changeset"] = self._CurrentChangesetId
-            data += xmlbuilder._XmlBuild(
-                change["type"], change["data"], False, data=self
-            ).decode("utf-8")
+            changeData = change["data"]
+            data += self._add_changeset_data(changeData, change["type"])
             data += "</" + change["action"] + ">\n"
         data += "</osmChange>"
         try:
@@ -1373,12 +1388,13 @@ class OsmApi:
                 f"The XML response from the OSM API is invalid: {e!r}"
             )
 
-        for item, change in zip(data, ChangesData):
+        for change in ChangesData:
             if change["action"] == "delete":
-                change["data"].pop("version")
+                for changeElement in change["data"]:
+                    changeElement.pop("version")
             else:
-                change["data"]["id"] = int(item.getAttribute("new_id"))
-                change["data"]["version"] = int(item.getAttribute("new_version"))
+                self._assign_id_and_version(data, change["data"])
+
         return ChangesData
 
     def ChangesetDownload(self, ChangesetId):
@@ -1902,3 +1918,17 @@ class OsmApi:
             self.ChangesetClose()
             self._changesetautocpt = 0
         return None
+
+    def _add_changeset_data(self, changeData, type):
+        data = ""
+        for changedElement in changeData:
+            changedElement["changeset"] = self._CurrentChangesetId
+            data += xmlbuilder._XmlBuild(type, changedElement, False, data=self).decode(
+                "utf-8"
+            )
+        return data
+
+    def _assign_id_and_version(self, ResponseData, RequestData):
+        for response, element in zip(ResponseData, RequestData):
+            element["id"] = int(response.getAttribute("new_id"))
+            element["version"] = int(response.getAttribute("new_version"))


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


=====================================
osmapi/dom.py
=====================================
@@ -64,13 +64,14 @@ def DomParseRelation(DomElement):
     return result
 
 
-def DomParseChangeset(DomElement):
+def DomParseChangeset(DomElement, include_discussion=False):
     """
     Returns ChangesetData for the changeset.
     """
     result = _DomGetAttributes(DomElement)
     result["tag"] = _DomGetTag(DomElement)
-    result["discussion"] = _DomGetDiscussion(DomElement)
+    if include_discussion:
+        result["discussion"] = _DomGetDiscussion(DomElement)
 
     return result
 


=====================================
osmapi/errors.py
=====================================
@@ -144,3 +144,9 @@ class PreconditionFailedApiError(ApiError):
     - When a relation has elements that do not exist or are not visible
     - When a node/way/relation is still used in a way/relation
     """
+
+
+class TimeoutApiError(ApiError):
+    """
+    Error if the http request ran into a timeout
+    """


=====================================
osmapi/http.py
=====================================
@@ -14,9 +14,10 @@ class OsmApiSession:
     MAX_RETRY_LIMIT = 5
     """Maximum retries if a call to the remote API fails (default: 5)"""
 
-    def __init__(self, base_url, created_by, auth=None, session=None):
+    def __init__(self, base_url, created_by, auth=None, session=None, timeout=30):
         self._api = base_url
         self._created_by = created_by
+        self._timeout = timeout
 
         try:
             self._auth = auth
@@ -68,7 +69,17 @@ class OsmApiSession:
         if auth and not self._auth:
             raise errors.UsernamePasswordMissingError("Username/Password missing")
 
-        response = self._session.request(method, path, data=send)
+        try:
+            response = self._session.request(
+                method, path, data=send, timeout=self._timeout
+            )
+        except requests.exceptions.Timeout:
+            raise errors.TimeoutApiError(
+                0, f"Request timed out (timeout={self._timeout})", ""
+            )
+        except requests.exceptions.RequestException as e:
+            raise errors.ApiError(0, str(e), "")
+
         if response.status_code != 200:
             payload = response.content.strip()
             if response.status_code == 401:
@@ -106,9 +117,10 @@ class OsmApiSession:
                         self._sleep()
                     self._session = self._get_http_session()
                 else:
+                    logger.exception("ApiError Exception occured")
                     raise
             except Exception as e:
-                logger.error(e)
+                logger.exception("General exception occured")
                 if i == self.MAX_RETRY_LIMIT:
                     if isinstance(e, errors.OsmApiError):
                         raise


=====================================
requirements.txt
=====================================
@@ -1,4 +1,4 @@
-pdoc==8.0.1
+pdoc==14.5.1
 Pygments==2.15.0
-requests==2.31.0
+requests==2.32.0
 python-dotenv


=====================================
build.sh → test.sh
=====================================


=====================================
tests/changeset_test.py
=====================================
@@ -3,6 +3,7 @@ import xmltodict
 import datetime
 import pytest
 from responses import GET, PUT, POST
+import requests
 
 
 def xmltosorteddict(xml):
@@ -47,7 +48,6 @@ def test_ChangesetGet(api, add_response):
         "id": 123,
         "closed_at": datetime.datetime(2009, 9, 7, 22, 57, 37),
         "created_at": datetime.datetime(2009, 9, 7, 21, 57, 36),
-        "discussion": [],
         "max_lat": "52.4710193",
         "max_lon": "-1.4831815",
         "min_lat": "45.9667901",
@@ -63,6 +63,18 @@ def test_ChangesetGet(api, add_response):
     assert result == test_changeset
 
 
+def test_ChangesetGet_with_timeout(api, add_response):
+    # Setup mock
+    add_response(GET, "/changeset/123", body=requests.exceptions.Timeout())
+
+    # Call
+    with pytest.raises(osmapi.TimeoutApiError) as execinfo:
+        api.ChangesetGet(123)
+    assert (
+        str(execinfo.value) == "Request failed: 0 - Request timed out (timeout=30) - "
+    )
+
+
 def test_ChangesetUpdate(auth_api, add_response):
     # Setup mock
     resp = add_response(PUT, "/changeset/create", filename="test_ChangesetCreate.xml")
@@ -75,10 +87,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.1.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.2.0">\n'
         b'  <changeset visible="true">\n'
         b'    <tag k="test" v="foobar"/>\n'
-        b'    <tag k="created_by" v="osmapi/4.1.0"/>\n'
+        b'    <tag k="created_by" v="osmapi/4.2.0"/>\n'
         b"  </changeset>\n"
         b"</osm>\n"
     )
@@ -98,7 +110,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.1.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.2.0">\n'
         b'  <changeset visible="true">\n'
         b'    <tag k="test" v="foobar"/>\n'
         b'    <tag k="created_by" v="MyTestOSMApp"/>\n'
@@ -122,10 +134,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.1.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.2.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.1.0"/>\n'
+        b'    <tag k="created_by" v="osmapi/4.2.0"/>\n'
         b"  </changeset>\n"
         b"</osm>\n"
     )
@@ -145,7 +157,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.1.0">\n'
+        b'<osm version="0.6" generator="osmapi/4.2.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'
@@ -208,23 +220,35 @@ def test_ChangesetUpload_create_node(auth_api, add_response):
         {
             "type": "node",
             "action": "create",
-            "data": {
-                "lat": 47.123,
-                "lon": 8.555,
-                "tag": {"amenity": "place_of_worship", "religion": "pastafarian"},
-            },
+            "data": [
+                {
+                    "lat": 47.123,
+                    "lon": 8.555,
+                    "tag": {"amenity": "place_of_worship", "religion": "pastafarian"},
+                },
+                {
+                    "lat": 47.125,
+                    "lon": 8.557,
+                    "tag": {"amenity": "place_of_worship", "religion": "pastafarian"},
+                },
+            ],
         }
     ]
 
     upload_xml = xmltosorteddict(
         b'<?xml version="1.0" encoding="UTF-8"?>\n'
-        b'<osmChange version="0.6" generator="osmapi/4.1.0">\n'
+        b'<osmChange version="0.6" generator="osmapi/4.2.0">\n'
         b"<create>\n"
         b'  <node lat="47.123" lon="8.555" visible="true" '
         b'changeset="4444">\n'
         b'    <tag k="amenity" v="place_of_worship"/>\n'
         b'    <tag k="religion" v="pastafarian"/>\n'
         b"  </node>\n"
+        b'  <node lat="47.125" lon="8.557" visible="true" '
+        b'changeset="4444">\n'
+        b'    <tag k="amenity" v="place_of_worship"/>\n'
+        b'    <tag k="religion" v="pastafarian"/>\n'
+        b"  </node>\n"
         b"</create>\n"
         b"</osmChange>"
     )
@@ -239,11 +263,11 @@ def test_ChangesetUpload_create_node(auth_api, add_response):
     assert result[0]["action"] == changesdata[0]["action"]
 
     data = result[0]["data"]
-    assert data["lat"] == changesdata[0]["data"]["lat"]
-    assert data["lon"] == changesdata[0]["data"]["lon"]
-    assert data["tag"] == changesdata[0]["data"]["tag"]
-    assert data["id"] == 4295832900
-    assert result[0]["data"]["version"] == 1
+    assert data[0]["lat"] == changesdata[0]["data"][0]["lat"]
+    assert data[0]["lon"] == changesdata[0]["data"][0]["lon"]
+    assert data[0]["tag"] == changesdata[0]["data"][0]["tag"]
+    assert data[0]["id"] == 4295832900
+    assert result[0]["data"][0]["version"] == 1
 
 
 def test_ChangesetUpload_modify_way(auth_api, add_response):
@@ -255,35 +279,37 @@ def test_ChangesetUpload_modify_way(auth_api, add_response):
         {
             "type": "way",
             "action": "modify",
-            "data": {
-                "id": 4294967296,
-                "version": 2,
-                "nd": [
-                    4295832773,
-                    4295832773,
-                    4294967304,
-                    4294967303,
-                    4294967300,
-                    4608751,
-                    4294967305,
-                    4294967302,
-                    8548430,
-                    4294967296,
-                    4294967301,
-                    4294967298,
-                    4294967306,
-                    7855737,
-                    4294967297,
-                    4294967299,
-                ],
-                "tag": {"highway": "secondary", "name": "Stansted Road"},
-            },
+            "data": [
+                {
+                    "id": 4294967296,
+                    "version": 2,
+                    "nd": [
+                        4295832773,
+                        4295832773,
+                        4294967304,
+                        4294967303,
+                        4294967300,
+                        4608751,
+                        4294967305,
+                        4294967302,
+                        8548430,
+                        4294967296,
+                        4294967301,
+                        4294967298,
+                        4294967306,
+                        7855737,
+                        4294967297,
+                        4294967299,
+                    ],
+                    "tag": {"highway": "secondary", "name": "Stansted Road"},
+                }
+            ],
         }
     ]
 
     upload_xml = xmltosorteddict(
         b'<?xml version="1.0" encoding="UTF-8"?>\n'
-        b'<osmChange version="0.6" generator="osmapi/4.1.0">\n'
+        b'<osmChange version="0.6" generator="osmapi/4.2.0">\n'
         b"<modify>\n"
         b'  <way id="4294967296" version="2" visible="true" '
         b'changeset="4444">\n'
@@ -313,16 +339,16 @@ def test_ChangesetUpload_modify_way(auth_api, add_response):
     # Call
     auth_api.ChangesetCreate()
     result = auth_api.ChangesetUpload(changesdata)
-
     # Assert
     assert xmltosorteddict(resp.calls[1].request.body) == upload_xml
 
     assert result[0]["type"] == changesdata[0]["type"]
     assert result[0]["action"] == changesdata[0]["action"]
 
-    data = result[0]["data"]
-    assert data["nd"] == changesdata[0]["data"]["nd"]
-    assert data["tag"] == changesdata[0]["data"]["tag"]
+    data = result[0]["data"][0]
+    print(data)
+    assert data["nd"] == changesdata[0]["data"][0]["nd"]
+    assert data["tag"] == changesdata[0]["data"][0]["tag"]
     assert data["id"] == 4294967296
     assert data["version"] == 3
 
@@ -336,25 +362,27 @@ def test_ChangesetUpload_delete_relation(auth_api, add_response):
         {
             "type": "relation",
             "action": "delete",
-            "data": {
-                "id": 676,
-                "version": 2,
-                "member": [
-                    {"ref": 4799, "role": "outer", "type": "way"},
-                    {"ref": 9391, "role": "outer", "type": "way"},
-                ],
-                "tag": {
-                    "admin_level": "9",
-                    "boundary": "administrative",
-                    "type": "multipolygon",
-                },
-            },
+            "data": [
+                {
+                    "id": 676,
+                    "version": 2,
+                    "member": [
+                        {"ref": 4799, "role": "outer", "type": "way"},
+                        {"ref": 9391, "role": "outer", "type": "way"},
+                    ],
+                    "tag": {
+                        "admin_level": "9",
+                        "boundary": "administrative",
+                        "type": "multipolygon",
+                    },
+                }
+            ],
         }
     ]
 
     upload_xml = xmltosorteddict(
         b'<?xml version="1.0" encoding="UTF-8"?>\n'
-        b'<osmChange version="0.6" generator="osmapi/4.1.0">\n'
+        b'<osmChange version="0.6" generator="osmapi/4.2.0">\n'
         b"<delete>\n"
         b'  <relation id="676" version="2" visible="true" '
         b'changeset="4444">\n'
@@ -377,9 +405,9 @@ def test_ChangesetUpload_delete_relation(auth_api, add_response):
     assert result[0]["type"] == changesdata[0]["type"]
     assert result[0]["action"] == changesdata[0]["action"]
 
-    data = result[0]["data"]
-    assert data["member"], changesdata[0]["data"]["member"]
-    assert data["tag"] == changesdata[0]["data"]["tag"]
+    data = result[0]["data"][0]
+    assert data["member"], changesdata[0]["data"][0]["member"]
+    assert data["tag"] == changesdata[0]["data"][0]["tag"]
     assert data["id"] == 676
     assert "version" not in data
 
@@ -393,19 +421,21 @@ def test_ChangesetUpload_invalid_response(auth_api, add_response):
         {
             "type": "relation",
             "action": "delete",
-            "data": {
-                "id": 676,
-                "version": 2,
-                "member": [
-                    {"ref": 4799, "role": "outer", "type": "way"},
-                    {"ref": 9391, "role": "outer", "type": "way"},
-                ],
-                "tag": {
-                    "admin_level": "9",
-                    "boundary": "administrative",
-                    "type": "multipolygon",
-                },
-            },
+            "data": [
+                {
+                    "id": 676,
+                    "version": 2,
+                    "member": [
+                        {"ref": 4799, "role": "outer", "type": "way"},
+                        {"ref": 9391, "role": "outer", "type": "way"},
+                    ],
+                    "tag": {
+                        "admin_level": "9",
+                        "boundary": "administrative",
+                        "type": "multipolygon",
+                    },
+                }
+            ],
         }
     ]
 
@@ -421,11 +451,13 @@ def test_ChangesetUpload_no_auth(api):
         {
             "type": "node",
             "action": "create",
-            "data": {
-                "lat": 47.123,
-                "lon": 8.555,
-                "tag": {"amenity": "place_of_worship", "religion": "pastafarian"},
-            },
+            "data": [
+                {
+                    "lat": 47.123,
+                    "lon": 8.555,
+                    "tag": {"amenity": "place_of_worship", "religion": "pastafarian"},
+                }
+            ],
         }
     ]
 
@@ -516,7 +548,6 @@ def test_ChangesetsGet(api, add_response):
             "closed_at": datetime.datetime(2014, 4, 29, 20, 25, 1),
             "created_at": datetime.datetime(2014, 4, 29, 20, 25, 1),
             "id": 41417,
-            "discussion": [],
             "max_lat": "58.8997467",
             "max_lon": "22.7364427",
             "min_lat": "58.8501594",
@@ -578,6 +609,30 @@ def test_ChangesetGetWithComment(api, add_response):
     }
 
 
+def test_ChangesetGetWithoutDiscussion(api, add_response):
+    resp = add_response(GET, "/changeset/52924")
+
+    result = api.ChangesetGet(52924, include_discussion=False)
+
+    assert resp.calls[0].request.params == {}
+    assert result == {
+        "id": 52924,
+        "closed_at": datetime.datetime(2015, 1, 1, 14, 54, 2),
+        "created_at": datetime.datetime(2015, 1, 1, 14, 54, 1),
+        "max_lat": "58.3369242",
+        "max_lon": "25.8829107",
+        "min_lat": "58.336813",
+        "min_lon": "25.8823273",
+        "open": False,
+        "user": "metaodi",
+        "uid": 1841,
+        "tag": {
+            "comment": "My test",
+            "created_by": "osmapi/0.4.1",
+        },
+    }
+
+
 def test_ChangesetComment(auth_api, add_response):
     resp = add_response(POST, "/changeset/123/comment")
 
@@ -588,7 +643,6 @@ def test_ChangesetComment(auth_api, add_response):
         "id": 123,
         "closed_at": datetime.datetime(2009, 9, 7, 22, 57, 37),
         "created_at": datetime.datetime(2009, 9, 7, 21, 57, 36),
-        "discussion": [],
         "max_lat": "52.4710193",
         "max_lon": "-1.4831815",
         "min_lat": "45.9667901",
@@ -618,7 +672,6 @@ def test_ChangesetSubscribe(auth_api, add_response):
         "id": 123,
         "closed_at": datetime.datetime(2009, 9, 7, 22, 57, 37),
         "created_at": datetime.datetime(2009, 9, 7, 21, 57, 36),
-        "discussion": [],
         "max_lat": "52.4710193",
         "max_lon": "-1.4831815",
         "min_lat": "45.9667901",
@@ -659,7 +712,6 @@ def test_ChangesetUnsubscribe(auth_api, add_response):
         "id": 123,
         "closed_at": datetime.datetime(2009, 9, 7, 22, 57, 37),
         "created_at": datetime.datetime(2009, 9, 7, 21, 57, 36),
-        "discussion": [],
         "max_lat": "52.4710193",
         "max_lon": "-1.4831815",
         "min_lat": "45.9667901",


=====================================
tests/fixtures/test_ChangesetGetWithoutDiscussion.xml
=====================================
@@ -0,0 +1,7 @@
+<?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/">
+  <changeset id="52924" user="metaodi" uid="1841" created_at="2015-01-01T14:54:01Z" closed_at="2015-01-01T14:54:02Z" open="false" min_lat="58.336813" min_lon="25.8823273" max_lat="58.3369242" max_lon="25.8829107">
+    <tag k="created_by" v="osmapi/0.4.1"/>
+    <tag k="comment" v="My test"/>
+  </changeset>
+</osm>


=====================================
tests/fixtures/test_ChangesetUpload_create_node.xml
=====================================
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <diffResult version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
     <node old_id="0" new_id="4295832900" new_version="1"/>
+    <node old_id="0" new_id="4295832901" new_version="1"/>
 </diffResult>
 


=====================================
tests/helper_test.py
=====================================
@@ -59,7 +59,7 @@ class TestOsmApiHelper(osmapi_test.TestOsmApi):
     def test_http_request_get(self):
         response = self.api._session._http_request("GET", "/api/0.6/test", False, None)
         self.mock_session.request.assert_called_with(
-            "GET", self.api_base + "/api/0.6/test", data=None
+            "GET", self.api_base + "/api/0.6/test", data=None, timeout=30
         )
         self.assertEqual(response, "test response")
         self.assertEqual(self.mock_session.request.call_count, 1)
@@ -70,7 +70,7 @@ class TestOsmApiHelper(osmapi_test.TestOsmApi):
             "PUT", "/api/0.6/testput", False, data
         )
         self.mock_session.request.assert_called_with(
-            "PUT", self.api_base + "/api/0.6/testput", data="data"
+            "PUT", self.api_base + "/api/0.6/testput", data="data", timeout=30
         )
         self.assertEqual(response, "test response")
 
@@ -80,7 +80,7 @@ class TestOsmApiHelper(osmapi_test.TestOsmApi):
             "PUT", "/api/0.6/testdelete", False, data
         )
         self.mock_session.request.assert_called_with(
-            "PUT", self.api_base + "/api/0.6/testdelete", data="delete data"
+            "PUT", self.api_base + "/api/0.6/testdelete", data="delete data", timeout=30
         )
         self.assertEqual(response, "test response")
 
@@ -89,7 +89,7 @@ class TestOsmApiHelper(osmapi_test.TestOsmApi):
             "PUT", "/api/0.6/testauth", True, None
         )
         self.mock_session.request.assert_called_with(
-            "PUT", self.api_base + "/api/0.6/testauth", data=None
+            "PUT", self.api_base + "/api/0.6/testauth", data=None, timeout=30
         )
         self.assertEqual(self.mock_session.auth, ("testuser", "testpassword"))
         self.assertEqual(response, "test response")



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/64f9267be0f811d0840e8811aa128b69be4de16c
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/20240808/b852328d/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list