[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