[Git][debian-gis-team/python-osmapi][upstream] New upstream version 2.0.0
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Tue Nov 23 05:15:35 GMT 2021
Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-osmapi
Commits:
79856c6f by Bas Couwenberg at 2021-11-23T06:04:07+01:00
New upstream version 2.0.0
- - - - -
27 changed files:
- + .github/workflows/build.yml
- + .github/workflows/publish_python.yml
- − .travis.yml
- CHANGELOG.md
- README.md
- build.sh
- osmapi/OsmApi.py
- osmapi/__init__.py
- + osmapi/errors.py
- requirements.txt
- setup.cfg
- setup.py
- + setup.sh
- test-requirements.txt
- tests/changeset_tests.py
- + tests/fixtures/test_NodeRelationsUnusedElement.xml
- + tests/fixtures/test_NodeUpdateConflict.xml
- + tests/fixtures/test_NodeUpdateWhenChangesetIsClosed.xml
- + tests/fixtures/test_NodeWaysNotExists.xml
- + tests/fixtures/test_RelationRelationsUnusedElement.xml
- + tests/fixtures/test_WayRelationsUnusedElement.xml
- + tests/fixtures/test_WayUpdatePreconditionFailed.xml
- tests/node_tests.py
- tests/osmapi_tests.py
- tests/relation_tests.py
- tests/way_tests.py
- tox.ini
Changes:
=====================================
.github/workflows/build.yml
=====================================
@@ -0,0 +1,38 @@
+name: Build osmapi
+on:
+ pull_request:
+ push:
+ branches: [master]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: [3.7, 3.8, 3.9]
+
+ steps:
+ - uses: actions/checkout at v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python at v1
+ 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: Build the package
+ run: ./build.sh
+
+ - name: Test coverage
+ run: coveralls --service=github
+ if: ${{ matrix.python-version == '3.8' }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
=====================================
.github/workflows/publish_python.yml
=====================================
@@ -0,0 +1,29 @@
+# workflow inspired by chezou/tabula-py
+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
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout at v1
+ - name: Set up Python
+ uses: actions/setup-python at v1
+ 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 }}
+ run: |
+ python setup.py sdist bdist_wheel
+ twine upload dist/*
=====================================
.travis.yml deleted
=====================================
@@ -1,36 +0,0 @@
-language: python
-
-python:
-- '2.7'
-- '3.4'
-- '3.5'
-- '3.6'
-
-matrix:
- include:
- - python: '3.7'
- dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
- sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
-
-before_install:
-- sudo apt-get update -qq
-- sudo apt-get install -qq pandoc
-
-install:
-- pip install -r requirements.txt -r test-requirements.txt
-- pip install .
-
-script: ./build.sh
-
-after_success: coveralls
-
-deploy:
- - provider: pypi
- user: odi
- password:
- secure: "EfW0Fje7pn/iLv2+eiFGc/PwD2jcdfF5Df9uUKWTCRP8wqMrFriErTczlWqfEIARZDnxQXU5c+UWEQvZ/5D5/4ZctJyvMcmgYi7I1ECXVwYQh0c5prsFl+9uynNJQWfW404v5Kb3MJE1iGaSEOeonObeqThQGdtDTPYZd9H4j20="
- on:
- tags: true
- all_branches: true
- python: 3.7
- repo: metaodi/osmapi
=====================================
CHANGELOG.md
=====================================
@@ -2,9 +2,24 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project follows [Semantic Versioning](http://semver.org/).
-## [Unreleased][unreleased]
+## [Unreleased]
-## 1.3.0 - 2020-10-05
+## [2.0.0] - 2021-11-22
+### Added
+- Move from Travis CI to Github Actions
+- Add more API-specific errors to catch specific errors (see issue #115, thanks [Mateusz Konieczny](https://github.com/matkoniecz)):
+ - `ChangesetClosedApiError`
+ - `NoteClosedApiError`
+ - `VersionMismatchApiError`
+ - `PreconditionFailedApiError`
+
+### Changed
+- **BC-Break**: osmapi does **not** support Python 2.7, 3.3, 3.4, 3.5 and 3.6 anymore
+
+### Fixed
+- Return an empty list in `NodeRelations`, `WayRelations`, `RelationRelations` and `NodeWays` if the returned XML is empty (thanks [FisherTsai](https://github.com/FisherTsai), see issue #117)
+
+## [1.3.0] - 2020-10-05
### Added
- Add close() method to close the underlying http session (see issue #107)
- Add context manager to automatically open and close the http session (see issue #107)
@@ -12,15 +27,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Fixed
- Correctly parse password file (thanks [Julien Palard](https://github.com/JulienPalard), see pull request #106)
-## 1.2.2 - 2018-11-05
+## [1.2.2] - 2018-11-05
### Fixed
- Update PyPI password for deployment
-## 1.2.1 - 2018-11-05
+## [1.2.1] - 2018-11-05
### Fixed
- Deployment to PyPI with Travis
-## 1.2.0 - 2018-11-05
+## [1.2.0] - 2018-11-05
### Added
- Support Python 3.7 (thanks a lot [cclauss](https://github.com/cclauss))
@@ -31,25 +46,25 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Updated dependencies for Python 3.7
- Adapt README to use Python 3 syntax (thanks [cclauss](https://github.com/cclauss))
-## 1.1.0 - 2017-10-11
+## [1.1.0] - 2017-10-11
### Added
- Raise new `XmlResponseInvalidError` if XML response from the OpenStreetMap API is invalid
### Changed
- Improved README (thanks [Mateusz Konieczny](https://github.com/matkoniecz))
-## 1.0.2 - 2017-09-07
+## [1.0.2] - 2017-09-07
### Added
- Rais ResponseEmptyApiError if we expect a response from the OpenStreetMap API, but didn't get one
### Removed
- Removed httpretty as HTTP mock library
-## 1.0.1 - 2017-09-07
+## [1.0.1] - 2017-09-07
### Fixed
- Make sure tests run offline
-## 1.0.0 - 2017-09-05
+## [1.0.0] - 2017-09-05
### Added
- Officially support Python 3.5 and 3.6
@@ -59,41 +74,41 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Changed
- **BC-Break:** raise an exception if the requested element is deleted (previoulsy `None` has been returned)
-## 0.8.1 - 2016-12-21
+## [0.8.1] - 2016-12-21
### Fixed
- Use setuptools instead of distutils in setup.py
-## 0.8.0 - 2016-12-21
+## [0.8.0] - 2016-12-21
### Removed
- This release no longer supports Python 3.2, if you need it, go back to release <= 0.6.2
## Changed
- Read version from __init__.py instead of importing it in setup.py
-## 0.7.2 - 2016-12-21
+## [0.7.2] - 2016-12-21
### Fixed
- Added 'requests' as a dependency to setup.py to fix installation problems
-## 0.7.1 - 2016-12-12
+## [0.7.1] - 2016-12-12
### Changed
- Catch OSError in setup.py to avoid installation errors
-## 0.7.0 - 2016-12-07
+## [0.7.0] - 2016-12-07
### Changed
- Replace the old httplib with requests library (thanks a lot [Austin Hartzheim](http://austinhartzheim.me/)!)
- Use format strings instead of ugly string concatenation
- Fix unicode in changesets (thanks a lot to [MichaelVL](https://github.com/MichaelVL)!)
-## 0.6.2 - 2016-01-04
+## [0.6.2] - 2016-01-04
### Changed
- Re-arranged README
- Make sure PyPI releases are only created when a release has been tagged on GitHub
-## 0.6.1 - 2016-01-04
+## [0.6.1] - 2016-01-04
### Changed
- The documentation is now available at a new domain: http://osmapi.metaodi.ch, the previous provider does no longer provide this service
-## 0.6.0 - 2015-05-26
+## [0.6.0] - 2015-05-26
### Added
- SSL support for the API calls (thanks [Austin Hartzheim](http://austinhartzheim.me/)!)
- Run tests on Python 3.4 as well
@@ -104,7 +119,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Changed
- Changed generic `Exception` with more specific ones, so a client can catch those and react accordingly (no BC-break!)
-## 0.5.0 - 2015-01-03
+## [0.5.0] - 2015-01-03
### Changed
- BC-break: all dates are now parsed as datetime objects
@@ -113,7 +128,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- When (un)subscribing to a changeset, there are two special errors `AlreadySubscribedApiError` and `NotSubscribedApiError` to check for
- The ChangesetGet method got a new parameter `include_discussion` to determine wheter or not changeset discussion should be in the response
-## 0.4.2 - 2015-01-01
+## [0.4.2] - 2015-01-01
### Fixed
- Result of `NodeWay` is now actually parsed as a `way`
@@ -123,54 +138,54 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Changed
- Update to pdoc 0.3.1 which changed the appearance of the online docs
-## 0.4.1 - 2014-10-08
+## [0.4.1] - 2014-10-08
### Changed
- Parse dates in notes as `datetime` objects
-## 0.4.0 - 2014-10-07
+## [0.4.0] - 2014-10-07
### Added
- Release for OSM Notes API
- Generation of online documentation (http://osmapi.divshot.io)
-## 0.3.1 - 2014-06-21
+## [0.3.1] - 2014-06-21
### Fixed
- Hotfix release of Python 3.x (base64)
-## 0.3.0 - 2014-05-20
+## [0.3.0] - 2014-05-20
### Added
- Support for Python 3.x
- Use `tox` to run tests against multiple versions of Python
-## 0.2.26 - 2014-05-02
+## [0.2.26] - 2014-05-02
### Fixed
- Fixed notes again
-## 0.2.25 - 2014-05-02
+## [0.2.25] - 2014-05-02
### Fixed
- Unit tests for basic functionality
- Fixed based on the unit tests (previously undetected bugs)
-## 0.2.24 - 2014-01-07
+## [0.2.24] - 2014-01-07
### Fixed
- Fixed notes
-## 0.2.23 - 2014-01-03
+## [0.2.23] - 2014-01-03
### Changed
- Hotfix release
-## 0.2.22 - 2014-01-03
+## [0.2.22] - 2014-01-03
### Fixed
- Fixed README.md not found error during installation
-## 0.2.21 - 2014-01-03
+## [0.2.21] - 2014-01-03
### Changed
- Updated description
-## 0.2.20 - 2014-01-01
+## [0.2.20] - 2014-01-01
### Added
- First release of PyPI package "osmapi"
-## 0.2.19 - 2014-01-01
+## [0.2.19] - 2014-01-01
### Changed
- Inital version from SVN (http://svn.openstreetmap.org/applications/utils/python_lib/OsmApi/OsmApi.py)
- Move to GitHub
@@ -265,3 +280,37 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- `Removed` for deprecated features removed in this release.
- `Fixed` for any bug fixes.
- `Security` to invite users to upgrade in case of vulnerabilities.
+
+[Unreleased]: https://github.com/metaodi/osmapi/compare/v2.0.0...HEAD
+[2.0.0]: https://github.com/metaodi/osmapi/compare/v1.3.0...v2.0.0
+[1.3.0]: https://github.com/metaodi/osmapi/compare/v1.2.2...v1.3.0
+[1.2.2]: https://github.com/metaodi/osmapi/compare/v1.2.1...v1.2.2
+[1.2.1]: https://github.com/metaodi/osmapi/compare/v1.2.0...v1.2.1
+[1.2.0]: https://github.com/metaodi/osmapi/compare/v1.1.0...v1.2.0
+[1.1.0]: https://github.com/metaodi/osmapi/compare/v1.0.2...v1.1.0
+[1.0.2]: https://github.com/metaodi/osmapi/compare/v1.0.1...v1.0.2
+[1.0.1]: https://github.com/metaodi/osmapi/compare/v1.0.0...v1.0.1
+[1.0.0]: https://github.com/metaodi/osmapi/compare/v0.8.1...v1.0.0
+[0.8.1]: https://github.com/metaodi/osmapi/compare/v0.8.0...v0.8.1
+[0.8.0]: https://github.com/metaodi/osmapi/compare/v0.7.2...v0.8.0
+[0.7.2]: https://github.com/metaodi/osmapi/compare/v0.7.1...v0.7.2
+[0.7.1]: https://github.com/metaodi/osmapi/compare/v0.7.0...v0.7.1
+[0.7.0]: https://github.com/metaodi/osmapi/compare/v0.6.2...v0.7.0
+[0.6.2]: https://github.com/metaodi/osmapi/compare/v0.6.1...v0.6.2
+[0.6.1]: https://github.com/metaodi/osmapi/compare/v0.6.0...v0.6.1
+[0.6.0]: https://github.com/metaodi/osmapi/compare/v0.5.0...v0.6.0
+[0.5.0]: https://github.com/metaodi/osmapi/compare/v0.4.2...v0.5.0
+[0.4.2]: https://github.com/metaodi/osmapi/compare/v0.4.1...v0.4.2
+[0.4.1]: https://github.com/metaodi/osmapi/compare/v0.4.0...v0.4.1
+[0.4.0]: https://github.com/metaodi/osmapi/compare/v0.3.1...v0.4.0
+[0.3.1]: https://github.com/metaodi/osmapi/compare/v0.3.0...v0.3.1
+[0.3.0]: https://github.com/metaodi/osmapi/compare/v0.2.26...v0.3.0
+[0.2.26]: https://github.com/metaodi/osmapi/compare/v0.2.25...v0.2.26
+[0.2.25]: https://github.com/metaodi/osmapi/compare/v0.2.24...v0.2.25
+[0.2.24]: https://github.com/metaodi/osmapi/compare/v0.2.23...v0.2.24
+[0.2.23]: https://github.com/metaodi/osmapi/compare/v0.2.22...v0.2.23
+[0.2.22]: https://github.com/metaodi/osmapi/compare/v0.2.21...v0.2.22
+[0.2.21]: https://github.com/metaodi/osmapi/compare/v0.2.20...v0.2.21
+[0.2.20]: https://github.com/metaodi/osmapi/compare/v0.2.19...v0.2.20
+[0.2.19]: https://github.com/metaodi/osmapi/releases/tag/v0.2.19
+
=====================================
README.md
=====================================
@@ -1,12 +1,12 @@
osmapi
======
-[![Build](https://img.shields.io/travis/metaodi/osmapi/develop.svg)](https://travis-ci.org/metaodi/osmapi)
+[![Build osmapi](https://github.com/metaodi/osmapi/actions/workflows/build.yml/badge.svg)](https://github.com/metaodi/osmapi/actions/workflows/build.yml)
[![Coverage](https://img.shields.io/coveralls/metaodi/osmapi/develop.svg)](https://coveralls.io/r/metaodi/osmapi?branch=develop)
[![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)
-Python wrapper for the OSM API
+Python wrapper for the OSM API (requires Python >= 3.7)
## Installation
@@ -20,7 +20,7 @@ The documentation is generated using `pdoc` and can be [viewed online](http://os
The build the documentation locally, you can use
- pdoc --html osmapi.OsmApi # create HTML file
+ pdoc -o . osmapi # create HTML files
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 `gh-pages` branch of this repository.
@@ -87,7 +87,7 @@ To run the tests use the following command:
nosetests --verbose
-By using tox you can even run the tests against different versions of python (2.7, 3.4, 3.5, 3.6 and 3.7):
+By using tox you can even run the tests against different versions of python (3.7, 3.8, 3.9):
tox
@@ -99,8 +99,8 @@ To create a new release, follow these steps (please respect [Semantic Versioning
1. Update the CHANGELOG with the version
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. The [publication on PyPI](https://pypi.python.org/pypi/osmapi) happens via [Travis CI](https://travis-ci.org/metaodi/osmapi) on every tagged commit
-1. Re-build the documentation (see above) and copy the generated file to `index.html` on the `gh-pages` 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
+1. Re-build the documentation (see above) and copy the generated files to the `gh-pages` branch
## Attribution
=====================================
build.sh
=====================================
@@ -15,7 +15,7 @@ flake8 --statistics --show-source .
nosetests --verbose --with-coverage
# generate the docs
-pdoc --html --overwrite osmapi/OsmApi.py
+pdoc -o . osmapi
# setup a new virtualenv and try to install the lib
virtualenv pyenv
=====================================
osmapi/OsmApi.py
=====================================
@@ -27,120 +27,32 @@ Find all information about changes of the different versions of this module
"""
-from __future__ import (absolute_import, print_function, unicode_literals)
import xml.dom.minidom
import xml.parsers.expat
import time
import sys
-import urllib
+import urllib.parse
+import re
import requests
from datetime import datetime
from osmapi import __version__
-
-# Python 3.x
-if getattr(urllib, 'urlencode', None) is None:
- urllib.urlencode = urllib.parse.urlencode
-
-
-class OsmApiError(Exception):
- """
- General OsmApi error class to provide a superclass for all other errors
- """
-
-
-class MaximumRetryLimitReachedError(OsmApiError):
- """
- Error when the maximum amount of retries is reached and we have to give up
- """
-
-
-class UsernamePasswordMissingError(OsmApiError):
- """
- Error when username or password is missing for an authenticated request
- """
- pass
-
-
-class NoChangesetOpenError(OsmApiError):
- """
- Error when an operation requires an open changeset, but currently
- no changeset _is_ open
- """
- pass
-
-
-class ChangesetAlreadyOpenError(OsmApiError):
- """
- Error when a user tries to open a changeset when there is already
- an open changeset
- """
- pass
-
-
-class OsmTypeAlreadyExistsError(OsmApiError):
- """
- Error when a user tries to create an object that already exsits
- """
- pass
-
-
-class XmlResponseInvalidError(OsmApiError):
- """
- Error if the XML response from the OpenStreetMap API is invalid
- """
-
-
-class ApiError(OsmApiError):
- """
- Error class, is thrown when an API request fails
- """
-
- def __init__(self, status, reason, payload):
- self.status = status
- """HTTP error code"""
-
- self.reason = reason
- """Error message"""
-
- self.payload = payload
- """Payload of API when this error occured"""
-
- def __str__(self):
- return (
- "Request failed: %s - %s - %s"
- % (str(self.status), self.reason, self.payload)
- )
-
-
-class AlreadySubscribedApiError(ApiError):
- """
- Error when a user tries to subscribe to a changeset
- that she is already subscribed to
- """
- pass
-
-
-class NotSubscribedApiError(ApiError):
- """
- Error when user tries to unsubscribe from a changeset
- that he is not subscribed to
- """
- pass
-
-
-class ElementDeletedApiError(ApiError):
- """
- Error when the requested element is deleted
- """
- pass
-
-
-class ResponseEmptyApiError(ApiError):
- """
- Error when the response to the request is empty
- """
- pass
+from .errors import AlreadySubscribedApiError
+from .errors import ApiError
+from .errors import ChangesetAlreadyOpenError
+from .errors import ChangesetClosedApiError
+from .errors import ElementDeletedApiError
+from .errors import MaximumRetryLimitReachedError
+from .errors import NoChangesetOpenError
+from .errors import NotSubscribedApiError
+from .errors import NoteClosedApiError
+from .errors import OsmApiError
+from .errors import OsmTypeAlreadyExistsError
+from .errors import PreconditionFailedApiError
+from .errors import ResponseEmptyApiError
+from .errors import UsernamePasswordMissingError
+from .errors import VersionMismatchApiError
+from .errors import XmlResponseInvalidError
class OsmApi:
@@ -396,6 +308,9 @@ class OsmApi:
If the supplied information contain an existing node,
`OsmApi.OsmTypeAlreadyExistsError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
return self._do("create", "node", NodeData)
@@ -435,6 +350,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
return self._do("modify", "node", NodeData)
@@ -475,6 +393,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
+
If the requested element has already been deleted,
`OsmApi.ElementDeletedApiError` is raised.
"""
@@ -527,7 +448,7 @@ class OsmApi:
"""
uri = "/api/0.6/node/%d/ways" % NodeId
data = self._get(uri)
- ways = self._OsmResponseToDom(data, tag="way")
+ ways = self._OsmResponseToDom(data, tag="way", allow_empty=True)
result = []
for way in ways:
data = self._DomParseWay(way)
@@ -568,7 +489,7 @@ class OsmApi:
"""
uri = "/api/0.6/node/%d/relations" % NodeId
data = self._get(uri)
- relations = self._OsmResponseToDom(data, tag="relation")
+ relations = self._OsmResponseToDom(data, tag="relation", allow_empty=True)
result = []
for relation in relations:
data = self._DomParseRelation(relation)
@@ -669,6 +590,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
return self._do("create", "way", WayData)
@@ -706,6 +630,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
return self._do("modify", "way", WayData)
@@ -744,6 +671,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
+
If the requested element has already been deleted,
`OsmApi.ElementDeletedApiError` is raised.
"""
@@ -805,7 +735,7 @@ class OsmApi:
"""
uri = "/api/0.6/way/%d/relations" % WayId
data = self._get(uri)
- relations = self._OsmResponseToDom(data, tag="relation")
+ relations = self._OsmResponseToDom(data, tag="relation", allow_empty=True)
result = []
for relation in relations:
data = self._DomParseRelation(relation)
@@ -945,6 +875,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
return self._do("create", "relation", RelationData)
@@ -991,6 +924,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
return self._do("modify", "relation", RelationData)
@@ -1038,6 +974,9 @@ class OsmApi:
If there is already an open changeset,
`OsmApi.ChangesetAlreadyOpenError` is raised.
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
+
If the requested element has already been deleted,
`OsmApi.ElementDeletedApiError` is raised.
"""
@@ -1100,7 +1039,7 @@ class OsmApi:
"""
uri = "/api/0.6/relation/%d/relations" % RelationId
data = self._get(uri)
- relations = self._OsmResponseToDom(data, tag="relation")
+ relations = self._OsmResponseToDom(data, tag="relation", allow_empty=True)
result = []
for relation in relations:
data = self._DomParseRelation(relation)
@@ -1243,16 +1182,25 @@ class OsmApi:
If there is no open changeset,
`OsmApi.NoChangesetOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
if not self._CurrentChangesetId:
raise NoChangesetOpenError("No changeset currently opened")
if "created_by" not in ChangesetTags:
ChangesetTags["created_by"] = self._created_by
- self._put(
- "/api/0.6/changeset/%s" % (self._CurrentChangesetId),
- self._XmlBuild("changeset", {"tag": ChangesetTags}),
- return_value=False
- )
+ try:
+ self._put(
+ "/api/0.6/changeset/%s" % (self._CurrentChangesetId),
+ self._XmlBuild("changeset", {"tag": ChangesetTags}),
+ return_value=False
+ )
+ except ApiError as e:
+ if e.status == 409:
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
return self._CurrentChangesetId
def ChangesetCreate(self, ChangesetTags={}):
@@ -1291,16 +1239,25 @@ class OsmApi:
If there is no open changeset,
`OsmApi.NoChangesetOpenError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
if not self._CurrentChangesetId:
raise NoChangesetOpenError("No changeset currently opened")
- self._put(
- "/api/0.6/changeset/%s/close" % (self._CurrentChangesetId),
- "",
- return_value=False
- )
- CurrentChangesetId = self._CurrentChangesetId
- self._CurrentChangesetId = 0
+ try:
+ self._put(
+ "/api/0.6/changeset/%s/close" % (self._CurrentChangesetId),
+ "",
+ return_value=False
+ )
+ CurrentChangesetId = self._CurrentChangesetId
+ self._CurrentChangesetId = 0
+ except ApiError as e:
+ if e.status == 409:
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
return CurrentChangesetId
def ChangesetUpload(self, ChangesData):
@@ -1318,6 +1275,9 @@ class OsmApi:
If no authentication information are provided,
`OsmApi.UsernamePasswordMissingError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
data = ""
data += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
@@ -1333,10 +1293,16 @@ class OsmApi:
).decode("utf-8")
data += "</" + change["action"] + ">\n"
data += "</osmChange>"
- data = self._post(
- "/api/0.6/changeset/%s/upload" % (self._CurrentChangesetId),
- data.encode("utf-8")
- )
+ try:
+ data = self._post(
+ "/api/0.6/changeset/%s/upload" % (self._CurrentChangesetId),
+ data.encode("utf-8")
+ )
+ except ApiError as e:
+ if e.status == 409 and re.search(r"The changeset .* was closed at .*", e.payload):
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
try:
data = xml.dom.minidom.parseString(data)
data = data.getElementsByTagName("diffResult")[0]
@@ -1426,7 +1392,7 @@ class OsmApi:
params["closed"] = 1
if params:
- uri += "?" + urllib.urlencode(params)
+ uri += "?" + urllib.parse.urlencode(params)
data = self._get(uri)
changesets = self._OsmResponseToDom(data, tag="changeset")
@@ -1463,12 +1429,21 @@ class OsmApi:
If no authentication information are provided,
`OsmApi.UsernamePasswordMissingError` is raised.
+
+ If the changeset is already closed,
+ `OsmApi.ChangesetClosedApiError` is raised.
"""
- params = urllib.urlencode({'text': comment})
- data = self._post(
- "/api/0.6/changeset/%s/comment" % (ChangesetId),
- params
- )
+ params = urllib.parse.urlencode({'text': comment})
+ try:
+ data = self._post(
+ "/api/0.6/changeset/%s/comment" % (ChangesetId),
+ params
+ )
+ except ApiError as e:
+ if e.status == 409:
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
changeset = self._OsmResponseToDom(data, tag="changeset", single=True)
return self._DomParseChangeset(changeset)
@@ -1628,7 +1603,7 @@ class OsmApi:
Returns updated NoteData (without timestamp).
"""
uri = "/api/0.6/notes"
- uri += "?" + urllib.urlencode(NoteData)
+ uri += "?" + urllib.parse.urlencode(NoteData)
return self._NoteAction(uri)
def NoteComment(self, NoteId, comment):
@@ -1683,7 +1658,7 @@ class OsmApi:
params['q'] = query
params['limit'] = limit
params['closed'] = closed
- uri += "?" + urllib.urlencode(params)
+ uri += "?" + urllib.parse.urlencode(params)
data = self._get(uri)
return self.ParseNotes(data)
@@ -1698,8 +1673,14 @@ class OsmApi:
if comment is not None:
params = {}
params['text'] = comment
- uri += "?" + urllib.urlencode(params)
- result = self._post(uri, None, optionalAuth=optionalAuth)
+ uri += "?" + urllib.parse.urlencode(params)
+ try:
+ result = self._post(uri, None, optionalAuth=optionalAuth)
+ except ApiError as e:
+ if e.status == 404:
+ raise NoteClosedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
# parse the result
noteElement = self._OsmResponseToDom(result, tag="note", single=True)
@@ -1861,7 +1842,7 @@ class OsmApi:
else:
return self._do_manu(action, OsmType, OsmData)
- def _do_manu(self, action, OsmType, OsmData):
+ def _do_manu(self, action, OsmType, OsmData): # noqa
if not self._CurrentChangesetId:
raise NoChangesetOpenError(
"You need to open a changeset before uploading data"
@@ -1874,25 +1855,56 @@ class OsmApi:
raise OsmTypeAlreadyExistsError(
"This %s already exists" % OsmType
)
- result = self._put(
- "/api/0.6/%s/create" % OsmType,
- self._XmlBuild(OsmType, OsmData)
- )
+ try:
+ result = self._put(
+ "/api/0.6/%s/create" % OsmType,
+ self._XmlBuild(OsmType, OsmData)
+ )
+ except ApiError as e:
+ if e.status == 409 and re.search(r"The changeset .* was closed at .*", e.payload):
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ elif e.status == 409:
+ raise VersionMismatchApiError(e.status, e.reason, e.payload)
+ elif e.status == 412:
+ raise PreconditionFailedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
OsmData["id"] = int(result.strip())
OsmData["version"] = 1
return OsmData
elif action == "modify":
- result = self._put(
- "/api/0.6/%s/%s" % (OsmType, OsmData["id"]),
- self._XmlBuild(OsmType, OsmData)
- )
+ try:
+ result = self._put(
+ "/api/0.6/%s/%s" % (OsmType, OsmData["id"]),
+ self._XmlBuild(OsmType, OsmData)
+ )
+ except ApiError as e:
+ print(e.reason)
+ if e.status == 409 and re.search(r"The changeset .* was closed at .*", e.payload):
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ elif e.status == 409:
+ raise VersionMismatchApiError(e.status, e.reason, e.payload)
+ elif e.status == 412:
+ raise PreconditionFailedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
OsmData["version"] = int(result.strip())
return OsmData
elif action == "delete":
- result = self._delete(
- "/api/0.6/%s/%s" % (OsmType, OsmData["id"]),
- self._XmlBuild(OsmType, OsmData)
- )
+ try:
+ result = self._delete(
+ "/api/0.6/%s/%s" % (OsmType, OsmData["id"]),
+ self._XmlBuild(OsmType, OsmData)
+ )
+ except ApiError as e:
+ if e.status == 409 and re.search(r"The changeset .* was closed at .*", e.payload):
+ raise ChangesetClosedApiError(e.status, e.reason, e.payload)
+ elif e.status == 409:
+ raise VersionMismatchApiError(e.status, e.reason, e.payload)
+ elif e.status == 412:
+ raise PreconditionFailedApiError(e.status, e.reason, e.payload)
+ else:
+ raise
OsmData["version"] = int(result.strip())
OsmData["visible"] = False
return OsmData
@@ -2065,7 +2077,7 @@ class OsmApi:
# Internal dom function #
##################################################
- def _OsmResponseToDom(self, response, tag, single=False):
+ def _OsmResponseToDom(self, response, tag, single=False, allow_empty=False):
"""
Returns the (sub-) DOM parsed from an OSM response
"""
@@ -2074,7 +2086,13 @@ class OsmApi:
osm_dom = dom.getElementsByTagName("osm")[0]
all_data = osm_dom.getElementsByTagName(tag)
first_element = all_data[0]
- except (xml.parsers.expat.ExpatError, IndexError) as e:
+ except (IndexError) as e:
+ if allow_empty:
+ return []
+ raise XmlResponseInvalidError(
+ "The XML response from the OSM API is invalid: %r" % e
+ )
+ except (xml.parsers.expat.ExpatError) as e:
raise XmlResponseInvalidError(
"The XML response from the OSM API is invalid: %r" % e
)
=====================================
osmapi/__init__.py
=====================================
@@ -1,5 +1,4 @@
-from __future__ import (absolute_import, print_function, unicode_literals)
-
-__version__ = '1.3.0'
+__version__ = '2.0.0'
from .OsmApi import * # noqa
+from .errors import * # noqa
=====================================
osmapi/errors.py
=====================================
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+class OsmApiError(Exception):
+ """
+ General OsmApi error class to provide a superclass for all other errors
+ """
+
+
+class MaximumRetryLimitReachedError(OsmApiError):
+ """
+ Error when the maximum amount of retries is reached and we have to give up
+ """
+
+
+class UsernamePasswordMissingError(OsmApiError):
+ """
+ Error when username or password is missing for an authenticated request
+ """
+ pass
+
+
+class NoChangesetOpenError(OsmApiError):
+ """
+ Error when an operation requires an open changeset, but currently
+ no changeset _is_ open
+ """
+ pass
+
+
+class ChangesetAlreadyOpenError(OsmApiError):
+ """
+ Error when a user tries to open a changeset when there is already
+ an open changeset
+ """
+ pass
+
+
+class OsmTypeAlreadyExistsError(OsmApiError):
+ """
+ Error when a user tries to create an object that already exsits
+ """
+ pass
+
+
+class XmlResponseInvalidError(OsmApiError):
+ """
+ Error if the XML response from the OpenStreetMap API is invalid
+ """
+
+
+class ApiError(OsmApiError):
+ """
+ Error class, is thrown when an API request fails
+ """
+
+ def __init__(self, status, reason, payload):
+ self.status = status
+ """HTTP error code"""
+
+ self.reason = reason
+ """Error message"""
+
+ self.payload = payload
+ """Payload of API when this error occured"""
+
+ def __str__(self):
+ return (
+ "Request failed: %s - %s - %s"
+ % (str(self.status), self.reason, self.payload)
+ )
+
+
+class AlreadySubscribedApiError(ApiError):
+ """
+ Error when a user tries to subscribe to a changeset
+ that she is already subscribed to
+ """
+ pass
+
+
+class NotSubscribedApiError(ApiError):
+ """
+ Error when user tries to unsubscribe from a changeset
+ that he is not subscribed to
+ """
+ pass
+
+
+class ElementDeletedApiError(ApiError):
+ """
+ Error when the requested element is deleted
+ """
+ pass
+
+
+class ResponseEmptyApiError(ApiError):
+ """
+ Error when the response to the request is empty
+ """
+ pass
+
+
+class ChangesetClosedApiError(ApiError):
+ """
+ Error if the the changeset in question has already been closed
+ """
+
+
+class NoteClosedApiError(ApiError):
+ """
+ Error if the the note in question has already been closed
+ """
+
+
+class VersionMismatchApiError(ApiError):
+ """
+ Error if the provided version does not match the database version
+ of the element
+ """
+
+
+class PreconditionFailedApiError(ApiError):
+ """
+ Error if the precondition of the operation was not met:
+ - When a way has nodes that do not exist or are not visible
+ - 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
+ """
=====================================
requirements.txt
=====================================
@@ -1,7 +1,7 @@
# This file lists the dependencies of this extension.
# Install with a command like: pip install -r pip-requirements.txt
-pdoc==0.3.2
-Pygments==2.2.0
-pypandoc==1.4
-requests==2.20.0
-Unidecode==1.0.22
+pdoc==8.0.1
+Pygments==2.10.0
+pypandoc==1.6.4
+requests==2.26.0
+Unidecode==1.3.2
=====================================
setup.cfg
=====================================
@@ -3,3 +3,8 @@ description-file = README.md
[flake8]
max-complexity = 10
+exclude = .git,.tox,__pycache__,pyenv,build,dist
+# the new Torvalds default for line length
+max-line-length = 100
+# set to true to check all ignored errors
+disable_noqa = False
=====================================
setup.py
=====================================
@@ -41,13 +41,9 @@ setup(
'Topic :: Scientific/Engineering :: GIS',
'Topic :: Software Development :: Libraries',
'Development Status :: 4 - Beta',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
],
)
=====================================
setup.sh
=====================================
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+[ ! -d pyenv ] && python -m venv pyenv
+source pyenv/bin/activate
+
+pip install --upgrade pip
+pip install -r requirements.txt
+pip install -r test-requirements.txt
+pip install -e .
=====================================
test-requirements.txt
=====================================
@@ -1,10 +1,8 @@
-# This file lists the dependencies of this extension.
-# Install with a command like: pip install -r pip-requirements.txt
-coverage==4.5.1
-coveralls==1.5.1
-flake8==3.6.0
-mock==2.0.0
-nose==1.3.7
-tox==3.5.3
-virtualenv==16.1.0
-xmltodict==0.11.0
+coverage
+coveralls
+flake8
+mock
+nose
+tox
+virtualenv
+xmltodict
=====================================
tests/changeset_tests.py
=====================================
@@ -92,10 +92,10 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osm version="0.6" generator="osmapi/2.0.0">\n'
b' <changeset visible="true">\n'
b' <tag k="test" v="foobar"/>\n'
- b' <tag k="created_by" v="osmapi/1.3.0"/>\n'
+ b' <tag k="created_by" v="osmapi/2.0.0"/>\n'
b' </changeset>\n'
b'</osm>\n'
)
@@ -125,7 +125,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osm version="0.6" generator="osmapi/2.0.0">\n'
b' <changeset visible="true">\n'
b' <tag k="test" v="foobar"/>\n'
b' <tag k="created_by" v="MyTestOSMApp"/>\n'
@@ -163,10 +163,10 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osm version="0.6" generator="osmapi/2.0.0">\n'
b' <changeset visible="true">\n'
b' <tag k="foobar" v="A new test changeset"/>\n'
- b' <tag k="created_by" v="osmapi/1.3.0"/>\n'
+ b' <tag k="created_by" v="osmapi/2.0.0"/>\n'
b' </changeset>\n'
b'</osm>\n'
)
@@ -190,7 +190,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osm version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osm version="0.6" generator="osmapi/2.0.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'
@@ -276,7 +276,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osmChange version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osmChange version="0.6" generator="osmapi/2.0.0">\n'
b'<create>\n'
b' <node lat="47.123" lon="8.555" visible="true" '
b'changeset="4444">\n'
@@ -350,7 +350,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osmChange version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osmChange version="0.6" generator="osmapi/2.0.0">\n'
b'<modify>\n'
b' <way id="4294967296" version="2" visible="true" '
b'changeset="4444">\n'
@@ -434,7 +434,7 @@ class TestOsmApiChangeset(osmapi_tests.TestOsmApi):
xmltosorteddict(kwargs['data']),
xmltosorteddict(
b'<?xml version="1.0" encoding="UTF-8"?>\n'
- b'<osmChange version="0.6" generator="osmapi/1.3.0">\n'
+ b'<osmChange version="0.6" generator="osmapi/2.0.0">\n'
b'<delete>\n'
b' <relation id="676" version="2" visible="true" '
b'changeset="4444">\n'
=====================================
tests/fixtures/test_NodeRelationsUnusedElement.xml
=====================================
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+</osm>
=====================================
tests/fixtures/test_NodeUpdateConflict.xml
=====================================
@@ -0,0 +1 @@
+Version does not match the current database version of the element
=====================================
tests/fixtures/test_NodeUpdateWhenChangesetIsClosed.xml
=====================================
@@ -0,0 +1 @@
+The changeset 2222 was closed at 2021-11-20 09:42:47 UTC.
=====================================
tests/fixtures/test_NodeWaysNotExists.xml
=====================================
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+</osm>
=====================================
tests/fixtures/test_RelationRelationsUnusedElement.xml
=====================================
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+</osm>
=====================================
tests/fixtures/test_WayRelationsUnusedElement.xml
=====================================
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.6" generator="OpenStreetMap server" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
+</osm>
=====================================
tests/fixtures/test_WayUpdatePreconditionFailed.xml
=====================================
@@ -0,0 +1 @@
+Way 876 requires the nodes with id in (11950), which either do not exist, or are not visible.
=====================================
tests/node_tests.py
=====================================
@@ -234,6 +234,68 @@ class TestOsmApiNode(osmapi_tests.TestOsmApi):
self.assertEquals(result['lon'], test_node['lon'])
self.assertEquals(result['tag'], test_node['tag'])
+ def test_NodeUpdateWhenChangesetIsClosed(self):
+ self._session_mock(auth=True, status=409)
+
+ self.api.ChangesetCreate = mock.Mock(
+ return_value=1111
+ )
+ self.api._CurrentChangesetId = 1111
+
+ test_node = {
+ 'id': 7676,
+ 'lat': 47.287,
+ 'lon': 8.765,
+ 'tag': {
+ 'amenity': 'place_of_worship',
+ 'name': 'christian'
+ }
+ }
+
+ self.api.ChangesetCreate({
+ 'comment': 'This is a test dataset'
+ })
+
+ with self.assertRaises(osmapi.ChangesetClosedApiError) as cm:
+ self.api.NodeUpdate(test_node)
+
+ self.assertEquals(cm.exception.status, 409)
+ self.assertEquals(
+ cm.exception.payload,
+ "The changeset 2222 was closed at 2021-11-20 09:42:47 UTC."
+ )
+
+ def test_NodeUpdateConflict(self):
+ self._session_mock(auth=True, status=409)
+
+ self.api.ChangesetCreate = mock.Mock(
+ return_value=1111
+ )
+ self.api._CurrentChangesetId = 1111
+
+ test_node = {
+ 'id': 7676,
+ 'lat': 47.287,
+ 'lon': 8.765,
+ 'tag': {
+ 'amenity': 'place_of_worship',
+ 'name': 'christian'
+ }
+ }
+
+ self.api.ChangesetCreate({
+ 'comment': 'This is a test dataset'
+ })
+
+ with self.assertRaises(osmapi.VersionMismatchApiError) as cm:
+ self.api.NodeUpdate(test_node)
+
+ self.assertEquals(cm.exception.status, 409)
+ self.assertEquals(
+ cm.exception.payload,
+ "Version does not match the current database version of the element"
+ )
+
def test_NodeDelete(self):
self._session_mock(auth=True)
@@ -301,6 +363,18 @@ class TestOsmApiNode(osmapi_tests.TestOsmApi):
}
)
+ def test_NodeWaysNotExists(self):
+ self._session_mock()
+
+ result = self.api.NodeWays(404)
+
+ args, kwargs = self.api._session.request.call_args
+ self.assertEquals(args[0], 'GET')
+ self.assertEquals(args[1], f'{self.api_base}/api/0.6/node/404/ways')
+
+ self.assertEquals(len(result), 0)
+ self.assertIsInstance(result, list)
+
def test_NodeRelations(self):
self._session_mock()
@@ -329,6 +403,21 @@ class TestOsmApiNode(osmapi_tests.TestOsmApi):
}
)
+ def test_NodeRelationsUnusedElement(self):
+ self._session_mock()
+
+ result = self.api.NodeRelations(4295668179)
+
+ args, kwargs = self.api._session.request.call_args
+ self.assertEquals(args[0], 'GET')
+ self.assertEquals(
+ args[1],
+ self.api_base + '/api/0.6/node/4295668179/relations'
+ )
+
+ self.assertEquals(len(result), 0)
+ self.assertIsInstance(result, list)
+
def test_NodesGet(self):
self._session_mock()
=====================================
tests/osmapi_tests.py
=====================================
@@ -27,8 +27,7 @@ class TestOsmApi(unittest.TestCase):
print(self._testMethodName)
print(self.api)
- def _session_mock(self, auth=False, filenames=None, status=200,
- reason=None):
+ def _session_mock(self, auth=False, filenames=None, status=200):
if auth:
self.api._username = 'testuser'
self.api._password = 'testpassword'
=====================================
tests/relation_tests.py
=====================================
@@ -273,6 +273,21 @@ class TestOsmApiRelation(osmapi_tests.TestOsmApi):
'Aargauischer Radroutennetz'
)
+ def test_RelationRelationsUnusedElement(self):
+ self._session_mock()
+
+ result = self.api.RelationRelations(1532552)
+
+ args, kwargs = self.api._session.request.call_args
+ self.assertEquals(args[0], 'GET')
+ self.assertEquals(
+ args[1],
+ f'{self.api_base}/api/0.6/relation/1532552/relations'
+ )
+
+ self.assertEquals(len(result), 0)
+ self.assertIsInstance(result, list)
+
def test_RelationFull(self):
self._session_mock()
=====================================
tests/way_tests.py
=====================================
@@ -155,6 +155,39 @@ class TestOsmApiWay(osmapi_tests.TestOsmApi):
self.assertEquals(result['nd'], test_way['nd'])
self.assertEquals(result['tag'], test_way['tag'])
+ def test_WayUpdatePreconditionFailed(self):
+ self._session_mock(auth=True, status=412)
+
+ self.api.ChangesetCreate = mock.Mock(
+ return_value=1111
+ )
+ self.api._CurrentChangesetId = 1111
+
+ test_way = {
+ 'id': 876,
+ 'nd': [11949, 11950],
+ 'tag': {
+ 'highway': 'unclassified',
+ 'name': 'Osmapi Street Update'
+ }
+ }
+
+ self.api.ChangesetCreate({
+ 'comment': 'This is a test dataset'
+ })
+
+ with self.assertRaises(osmapi.PreconditionFailedApiError) as cm:
+ self.api.WayUpdate(test_way)
+
+ self.assertEquals(cm.exception.status, 412)
+ self.assertEquals(
+ cm.exception.payload,
+ (
+ "Way 876 requires the nodes with id in (11950), "
+ "which either do not exist, or are not visible."
+ )
+ )
+
def test_WayDelete(self):
self._session_mock(auth=True)
@@ -229,6 +262,21 @@ class TestOsmApiWay(osmapi_tests.TestOsmApi):
}
)
+ def test_WayRelationsUnusedElement(self):
+ self._session_mock()
+
+ result = self.api.WayRelations(4295032193)
+
+ args, kwargs = self.api._session.request.call_args
+ self.assertEquals(args[0], 'GET')
+ self.assertEquals(
+ args[1],
+ self.api_base + '/api/0.6/way/4295032193/relations'
+ )
+
+ self.assertEquals(len(result), 0)
+ self.assertIsInstance(result, list)
+
def test_WayFull(self):
self._session_mock()
=====================================
tox.ini
=====================================
@@ -1,5 +1,5 @@
[tox]
-envlist = py27,py34,py35,py36,py37
+envlist = py37,py38,py39
[testenv]
commands=nosetests --verbose
deps =
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/79856c6f3a32f4bdba94b2d6ebd8849e46423670
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-osmapi/-/commit/79856c6f3a32f4bdba94b2d6ebd8849e46423670
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/20211123/f08222da/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list