diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a70a7d..2861bcc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/publish_python.yml b/.github/workflows/publish_python.yml index 7f0cb7b..5c3ef6e 100644 --- a/.github/workflows/publish_python.yml +++ b/.github/workflows/publish_python.yml @@ -1,4 +1,3 @@ -# workflow inspired by chezou/tabula-py name: Upload Python Package on: @@ -14,7 +13,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 @@ -46,7 +45,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95e9d24..1b8c04a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,9 +3,15 @@ repos: rev: 23.7.0 hooks: - id: black - language_version: python3.8 + language_version: python3.12 - repo: https://github.com/pycqa/flake8 rev: 6.0.0 hooks: - id: flake8 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy + additional_dependencies: [types-requests] + args: [--config-file=setup.cfg] diff --git a/CHANGELOG.md b/CHANGELOG.md index 048bee7..7ce5a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] +## [5.0.0] - 2026-02-18 +### Changed +- **BC-Break**: Remove support for Python 3.8, new minimum version for osmapi is Python 3.9 +- **BC-Break**: Renamed all methods as `snake_case` instead of `CamelCase`(eg. `osmapi.node_get` instead of `osmapi.NodeGet`). The previous methods are still there, but all issue a `DeprecationWarning` when called. +- While changing the public API of osmapi, the large `OsmApi.py` file was split into several smaller files. + +### Added +- Add type hints and mypy checking to osmapi #186 + +### Removed +- Remove autochangeset feature in favor of Changeset context manager #187 + ## [4.3.0] - 2025-01-21 ### Added - New `ConnectionApiError` when a connection or network error occurs (see issue #176, thanks [Mateusz Konieczny](https://github.com/matkoniecz)) @@ -367,7 +379,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.3.0...HEAD +[Unreleased]: https://github.com/metaodi/osmapi/compare/v5.0.0...HEAD +[5.0.0]: https://github.com/metaodi/osmapi/compare/v4.3.0...v5.0.0 [4.3.0]: https://github.com/metaodi/osmapi/compare/v4.2.0...v4.3.0 [4.2.0]: https://github.com/metaodi/osmapi/compare/v4.1.0...v4.2.0 [4.1.0]: https://github.com/metaodi/osmapi/compare/v4.0.0...v4.1.0 diff --git a/Makefile b/Makefile index 2d2965d..614b680 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,7 @@ format: ## Format source code (black codestyle) lint: ## Linting of source code python -m black --check --diff osmapi examples tests *.py python -m flake8 --statistics --show-source . + python -m mypy osmapi test: ## Run tests (run in UTF-8 mode in Windows) python -Xutf8 -m pytest --cov=osmapi tests/ diff --git a/README.md b/README.md index e772b49..fa06c14 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ osmapi [](https://github.com/pre-commit/pre-commit) -Python wrapper for the OSM API (requires Python >= 3.8) +Python wrapper for the OSM API (requires Python >= 3.9). + +**NOTE**: Since version 5.0 of this library, all method names are in `snake_case`, the `CamelCase` versions are deprecated and will be removed in version 6.0. ## Installation @@ -36,10 +38,11 @@ Check the [examples directory](https://github.com/metaodi/osmapi/tree/develop/ex ### Read from OpenStreetMap + ```python >>> import osmapi >>> api = osmapi.OsmApi() ->>> print(api.NodeGet(123)) +>>> print(api.node_get(123)) {'changeset': 532907, 'uid': 14298, 'timestamp': '2007-09-29T09:19:17Z', 'lon': 10.790009299999999, 'visible': True, @@ -49,13 +52,14 @@ Check the [examples directory](https://github.com/metaodi/osmapi/tree/develop/ex ### Write to OpenStreetMap + ```python >>> import osmapi >>> api = osmapi.OsmApi(api="https://api06.dev.openstreetmap.org", username = "metaodi", password = "*******") ->>> api.ChangesetCreate({"comment": "My first test"}) ->>> print(api.NodeCreate({"lon":1, "lat":1, "tag": {}})) +>>> api.changeset_create({"comment": "My first test"}) +>>> print(api.node_create({"lon":1, "lat":1, "tag": {}})) {'changeset': 532907, 'lon': 1, 'version': 1, 'lat': 1, 'tag': {}, 'id': 164684} ->>> api.ChangesetClose() +>>> api.changeset_close() ``` ### OAuth authentication @@ -69,6 +73,8 @@ To use OAuth 2.0, you must register an application with an OpenStreetMap account 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. +auth = OpenStreetMapDevAuth( + 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 @@ -87,9 +93,9 @@ api = osmapi.OsmApi( session=auth.session ) -with api.Changeset({"comment": "My first test"}) as changeset_id: +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": {}}) + node1 = api.node_create({"lon": 1, "lat": 1, "tag": {}}) print(node1) ``` @@ -103,6 +109,7 @@ To credit the application that supplies changes to OSM, an `appid` can be provid This is a string identifying the application. If this is omitted "osmapi" is used. + ```python api = osmapi.OsmApi( api="https://api06.dev.openstreetmap.org", diff --git a/docs/index.html b/docs/index.html index 717c833..e38b4a6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,232 +1,7 @@ - +
- - -__version__ = "4.3.0" + + + -from .OsmApi import * # noqa -from .errors import * # noqa -
{"role": "", "ref":123, "type": "node"}""" -The OsmApi module is a wrapper for the OpenStreetMap API. -As such it provides an easy access to the functionality of the API. - -You can find this module [on PyPI](https://pypi.python.org/pypi/osmapi) -or [on GitHub](https://github.com/metaodi/osmapi). - -Find all information about changes of the different versions of this module -[in the CHANGELOG](https://github.com/metaodi/osmapi/blob/master/CHANGELOG.md). - - -## Notes: - -* **dictionary keys** are _unicode_ -* **changeset** is _integer_ -* **version** is _integer_ -* **tag** is a _dictionary_ -* **timestamp** is _unicode_ -* **user** is _unicode_ -* **uid** is _integer_ -* node **lat** and **lon** are _floats_ -* way **nd** is list of _integers_ -* relation **member** is a _list of dictionaries_ like -`{"role": "", "ref":123, "type": "node"}` - -""" - -import xml.dom.minidom -import xml.parsers.expat -import urllib.parse -import re -import logging -from contextlib import contextmanager - -from osmapi import __version__ -from . import dom -from . import errors -from . import http -from . import parser -from . import xmlbuilder - - -logger = logging.getLogger(__name__) - - -class OsmApi: - """ - Main class of osmapi, instanciate this class to use osmapi - """ - - def __init__( - self, - username=None, - password=None, - passwordfile=None, - appid="", - created_by=f"osmapi/{__version__}", - api="https://www.openstreetmap.org", - changesetauto=False, - changesetautotags={}, - changesetautosize=500, - changesetautomulti=1, - session=None, - timeout=30, - ): - """ - Initialized the OsmApi object. - - There are two different ways to authenticate a user. - Either `username` and `password` are supplied directly or the path - to a `passwordfile` is given, where on the first line username - and password must be colon-separated (<user>:<pass>). - - To credit the application that supplies changes to OSM, an `appid` - can be provided. This is a string identifying the application. - If this is omitted "osmapi" is used. - - It is possible to configure the URL to connect to using the `api` - parameter. By default this is the SSL version of the production API - of OpenStreetMap, for testing purposes, one might prefer the official - test instance at "api06.dev.openstreetmap.org" or any other valid - OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' - in front of the hostname of the `api` parameter (e.g. - https://api.openstreetmap.com). - - There are several options to control the changeset behaviour. By - default, a programmer has to take care to open and close a changeset - prior to make changes to OSM. - By setting `changesetauto` to `True`, osmapi automatically opens - changesets. - The `changesetautotags` parameter takes a `dict`, where each key/value - pair is applied as tags to the changeset. - The option `changesetautosize` defines the size of each - 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 - self._username = None - if username: - self._username = username - elif passwordfile: - with open(passwordfile) as f: - pass_line = f.readline() - self._username = pass_line.partition(":")[0].strip() - - # Get password - self._password = None - if password: - self._password = password - elif passwordfile: - with open(passwordfile) as f: - for line in f: - key, _, value = line.strip().partition(":") - if key == self._username: - self._password = value - - # Changest informations - # auto create and close changesets - self._changesetauto = changesetauto - # tags for automatic created changesets - self._changesetautotags = changesetautotags - # change count for auto changeset - self._changesetautosize = changesetautosize - # change count for auto changeset - self._changesetautosize = changesetautosize - # close a changeset every # upload - self._changesetautomulti = changesetautomulti - self._changesetautocpt = 0 - # data to upload for auto group - self._changesetautodata = [] - - # Get API - self._api = api.strip("/") - - # Get created_by - if not appid: - self._created_by = created_by - else: - self._created_by = f"{appid} ({created_by})" - - # Initialisation - self._CurrentChangesetId = 0 - - # 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, - timeout=self._timeout, - ) - - def __enter__(self): - self._session = http.OsmApiSession( - self._api, - self._created_by, - session=self.http_session, - timeout=self._timeout, - ) - return self - - def __exit__(self, *args): - self.close() - - def close(self): - try: - if self._changesetauto: - self._changesetautoflush(True) - except errors.ResponseEmptyApiError: - pass - - if self._session: - self._session.close() - - ################################################## - # Capabilities # - ################################################## - - def Capabilities(self): - """ - Returns the API capabilities as a dict: - - #!python - { - 'area': { - 'maximum': area in square degrees that can be queried, - }, - 'changesets': { - 'maximum_elements': number of elements per changeset, - }, - 'status': { - 'api': online|readonly|offline, - 'database': online|readonly|offline, - 'gpx': online|readonly|offline, - }, - 'timeout': { - 'seconds': timeout in seconds for API calls, - }, - 'tracepoints': { - 'per_page': maximum number of points in a GPX track, - }, - 'version': { - 'maximum': maximum version of API this server supports, - 'minimum': minimum version of API this server supports, - }, - 'waynodes': { - 'maximum': maximum number of nodes that a way may contain, - }, - } - - The capabilities can be used by a client to - gain insights of the server in use. - """ - uri = "/api/capabilities" - data = self._session._get(uri) - - data = dom.OsmResponseToDom(data, tag="api", single=True) - result = {} - for elem in data.childNodes: - if elem.nodeType != elem.ELEMENT_NODE: - continue - result[elem.nodeName] = {} - for k, v in elem.attributes.items(): - try: - result[elem.nodeName][k] = float(v) - except Exception: - result[elem.nodeName][k] = v - return result - - ################################################## - # Node # - ################################################## - - def NodeGet(self, NodeId, NodeVersion=-1): - """ - Returns node with `NodeId` as a dict: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `NodeVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/node/{NodeId}" - if NodeVersion != -1: - uri += f"/{NodeVersion}" - data = self._session._get(uri) - data = dom.OsmResponseToDom(data, tag="node", single=True) - return dom.DomParseNode(data) - - def NodeCreate(self, NodeData): - """ - Creates a node based on the supplied `NodeData` dict: - - #!python - { - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def NodeUpdate(self, NodeData): - """ - Updates node with the supplied `NodeData` dict: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - 'version': version number of node, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def NodeDelete(self, NodeData): - """ - Delete node with `NodeData`: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'version': version number of node, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "node", NodeData) - - def NodeHistory(self, NodeId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of NodeData, - '2': dict of NodeData, - ... - } - - `NodeId` is the unique identifier of a node. - """ - uri = f"/api/0.6/node/{NodeId}/history" - data = self._session._get(uri) - nodes = dom.OsmResponseToDom(data, tag="node") - result = {} - for node in nodes: - data = dom.DomParseNode(node) - result[data["version"]] = data - return result - - def NodeWays(self, NodeId): - """ - Returns a list of dicts of `WayData` containing node `NodeId`: - - #!python - [ - { - 'id': id of Way, - 'nd': [] list of NodeIds in this way - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `NodeId` is a unique identifier for a node. - """ - uri = f"/api/0.6/node/{NodeId}/ways" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way", allow_empty=True) - result = [] - for way in ways: - data = dom.DomParseWay(way) - result.append(data) - return result - - def NodeRelations(self, NodeId): - """ - Returns a list of dicts of `RelationData` containing node `NodeId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {}, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `NodeId` is a unique identifier for a node. - """ - uri = f"/api/0.6/node/{NodeId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result - - def NodesGet(self, NodeIdList): - """ - Returns dict with the id of the Node as a key - for each node in `NodeIdList`: - - #!python - { - '1234': dict of NodeData, - '5678': dict of NodeData, - ... - } - - `NodeIdList` is a list containing unique identifiers - for multiple nodes. - """ - node_list = ",".join([str(x) for x in NodeIdList]) - uri = f"/api/0.6/nodes?nodes={node_list}" - data = self._session._get(uri) - nodes = dom.OsmResponseToDom(data, tag="node") - result = {} - for node in nodes: - data = dom.DomParseNode(node) - result[data["id"]] = data - return result - - ################################################## - # Way # - ################################################## - - def WayGet(self, WayId, WayVersion=-1): - """ - Returns way with `WayId` as a dict: - - #!python - { - 'id': id of way, - 'tag': {} tags of this way, - 'nd': [] list of nodes belonging to this way - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `WayVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/way/{WayId}" - if WayVersion != -1: - uri += f"/{WayVersion}" - data = self._session._get(uri) - way = dom.OsmResponseToDom(data, tag="way", single=True) - return dom.DomParseWay(way) - - def WayCreate(self, WayData): - """ - Creates a way based on the supplied `WayData` dict: - - #!python - { - 'nd': [] list of nodes, - 'tag': {} dict of tags, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the supplied information contain an existing node, - `OsmApi.OsmTypeAlreadyExistsError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def WayUpdate(self, WayData): - """ - Updates way with the supplied `WayData` dict: - - #!python - { - 'id': id of way, - 'nd': [] list of nodes, - 'tag': {}, - 'version': version number of way, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def WayDelete(self, WayData): - """ - Delete way with `WayData`: - - #!python - { - 'id': id of way, - 'nd': [] list of nodes, - 'tag': dict of tags, - 'version': version number of way, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "way", WayData) - - def WayHistory(self, WayId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of WayData, - '2': dict of WayData, - ... - } - - `WayId` is the unique identifier of a way. - """ - uri = f"/api/0.6/way/{WayId}/history" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way") - result = {} - for way in ways: - data = dom.DomParseWay(way) - result[data["version"]] = data - return result - - def WayRelations(self, WayId): - """ - Returns a list of dicts of `RelationData` containing way `WayId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `WayId` is a unique identifier for a way. - """ - uri = f"/api/0.6/way/{WayId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result - - def WayFull(self, WayId): - """ - Returns the full data for way `WayId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `WayId` is a unique identifier for a way. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/way/{WayId}/full" - data = self._session._get(uri) - return parser.ParseOsm(data) - - def WaysGet(self, WayIdList): - """ - Returns dict with the id of the way as a key for - each way in `WayIdList`: - - #!python - { - '1234': dict of WayData, - '5678': dict of WayData, - ... - } - - `WayIdList` is a list containing unique identifiers for multiple ways. - """ - way_list = ",".join([str(x) for x in WayIdList]) - uri = f"/api/0.6/ways?ways={way_list}" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way") - result = {} - for way in ways: - data = dom.DomParseWay(way) - result[data["id"]] = data - return result - - ################################################## - # Relation # - ################################################## - - def RelationGet(self, RelationId, RelationVersion=-1): - """ - Returns relation with `RelationId` as a dict: - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `RelationVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/relation/{RelationId}" - if RelationVersion != -1: - uri += f"/{RelationVersion}" - data = self._session._get(uri) - relation = dom.OsmResponseToDom(data, tag="relation", single=True) - return dom.DomParseRelation(relation) - - def RelationCreate(self, RelationData): - """ - Creates a relation based on the supplied `RelationData` dict: - - #!python - { - 'member': [] list of members, - 'tag': {} dict of tags, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the supplied information contain an existing node, - `OsmApi.OsmTypeAlreadyExistsError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def RelationUpdate(self, RelationData): - """ - Updates relation with the supplied `RelationData` dict: - - #!python - { - 'id': id of relation, - 'member': [] list of member dicts, - 'tag': {}, - 'version': version number of relation, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def RelationDelete(self, RelationData): - """ - Delete relation with `RelationData` dict: - - #!python - { - 'id': id of relation, - 'member': [] list of member dicts, - 'tag': {}, - 'version': version number of relation, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "relation", RelationData) - - def RelationHistory(self, RelationId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of RelationData, - '2': dict of RelationData, - ... - } - - `RelationId` is the unique identifier of a relation. - """ - uri = f"/api/0.6/relation/{RelationId}/history" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation") - result = {} - for relation in relations: - data = dom.DomParseRelation(relation) - result[data["version"]] = data - return result - - def RelationRelations(self, RelationId): - """ - Returns a list of dicts of `RelationData` - containing relation `RelationId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `RelationId` is a unique identifier for a relation. - """ - uri = f"/api/0.6/relation/{RelationId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result - - def RelationFullRecur(self, RelationId): - """ - Returns the full data (all levels) for relation - `RelationId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `RelationId` is a unique identifier for a way. - - This function is useful for relations containing other relations. - - If you don't need all levels, use `OsmApi.RelationFull` - instead, which return only 2 levels. - - If any relation (on any level) has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - data = [] - todo = [RelationId] - done = [] - while todo: - rid = todo.pop(0) - done.append(rid) - temp = self.RelationFull(rid) - for item in temp: - if item["type"] != "relation": - continue - if item["data"]["id"] in done: - continue - todo.append(item["data"]["id"]) - data += temp - return data - - def RelationFull(self, RelationId): - """ - Returns the full data (two levels) for relation - `RelationId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `RelationId` is a unique identifier for a way. - - If you need all levels, use `OsmApi.RelationFullRecur`. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/relation/{RelationId}/full" - data = self._session._get(uri) - return parser.ParseOsm(data) - - def RelationsGet(self, RelationIdList): - """ - Returns dict with the id of the relation as a key - for each relation in `RelationIdList`: - - #!python - { - '1234': dict of RelationData, - '5678': dict of RelationData, - ... - } - - `RelationIdList` is a list containing unique identifiers - for multiple relations. - """ - relation_list = ",".join([str(x) for x in RelationIdList]) - uri = f"/api/0.6/relations?relations={relation_list}" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation") - result = {} - for relation in relations: - data = dom.DomParseRelation(relation) - result[data["id"]] = data - return result - - ################################################## - # Changeset # - ################################################## - - @contextmanager - def Changeset(self, ChangesetTags={}): - """ - Context manager for a Changeset. - - It opens a Changeset, uploads the changes and closes the changeset - when used with the `with` statement: - - #!python - import osmapi - - with osmapi.Changeset({"comment": "Import script XYZ"}) as changeset_id: - print(f"Part of changeset {changeset_id}") - api.NodeCreate({"lon":1, "lat":1, "tag": {}}) - - If `ChangesetTags` are given, this tags are applied (key/value). - - Returns `ChangesetId` - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - # Create a new changeset - changeset_id = self.ChangesetCreate(ChangesetTags) - yield changeset_id - - # upload data to changeset - autosize = self._changesetautosize - for i in range(0, len(self._changesetautodata), autosize): - chunk = self._changesetautodata[i : i + autosize] - self.ChangesetUpload(chunk) - self._changesetautodata = [] - self.ChangesetClose() - - def ChangesetGet(self, ChangesetId, include_discussion=False): - """ - Returns changeset with `ChangesetId` as a dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'discussion': [] list of comment dict (-> `include_discussion`) - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - `ChangesetId` is the unique identifier of a changeset. - - If `include_discussion` is set to `True` the changeset discussion - will be available in the result. - """ - path = f"/api/0.6/changeset/{ChangesetId}" - if include_discussion: - path = f"{path}?include_discussion=true" - data = self._session._get(path) - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset, include_discussion=include_discussion) - - def ChangesetUpdate(self, ChangesetTags={}): - """ - Updates current changeset with `ChangesetTags`. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - 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 errors.NoChangesetOpenError("No changeset currently opened") - if "created_by" not in ChangesetTags: - ChangesetTags["created_by"] = self._created_by - try: - self._session._put( - f"/api/0.6/changeset/{self._CurrentChangesetId}", - xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self), - return_value=False, - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - return self._CurrentChangesetId - - def ChangesetCreate(self, ChangesetTags={}): - """ - Opens a changeset. - - If `ChangesetTags` are given, this tags are applied (key/value). - - Returns `ChangesetId` - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - if self._CurrentChangesetId: - raise errors.ChangesetAlreadyOpenError("Changeset already opened") - if "created_by" not in ChangesetTags: - ChangesetTags["created_by"] = self._created_by - - # check if someone tries to create a test changeset to PROD - if ( - self._api == "https://www.openstreetmap.org" - and ChangesetTags.get("comment") == "My first test" - ): - raise errors.OsmApiError( - "DO NOT CREATE test changesets on the production server" - ) - - result = self._session._put( - "/api/0.6/changeset/create", - xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self), - ) - self._CurrentChangesetId = int(result) - return self._CurrentChangesetId - - def ChangesetClose(self): - """ - Closes current changeset. - - Returns `ChangesetId`. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - 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 errors.NoChangesetOpenError("No changeset currently opened") - try: - self._session._put( - f"/api/0.6/changeset/{self._CurrentChangesetId}/close", - "", - return_value=False, - ) - CurrentChangesetId = self._CurrentChangesetId - self._CurrentChangesetId = 0 - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - return CurrentChangesetId - - def ChangesetUpload(self, ChangesData): - """ - Upload data with the `ChangesData` list of dicts: - - #!python - { - type: node|way|relation, - action: create|delete|modify, - data: {} - } - - Returns list with updated ids. - - 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' - data += '<osmChange version="0.6" generator="' - data += self._created_by + '">\n' - for change in ChangesData: - data += "<" + change["action"] + ">\n" - changeData = change["data"] - data += self._add_changeset_data(changeData, change["type"]) - data += "</" + change["action"] + ">\n" - data += "</osmChange>" - try: - data = self._session._post( - f"/api/0.6/changeset/{self._CurrentChangesetId}/upload", - data.encode("utf-8"), - forceAuth=True, - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("diffResult")[0] - data = [x for x in data.childNodes if x.nodeType == x.ELEMENT_NODE] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - for change in ChangesData: - if change["action"] == "delete": - for changeElement in change["data"]: - changeElement.pop("version") - else: - self._assign_id_and_version(data, change["data"]) - - return ChangesData - - def ChangesetDownload(self, ChangesetId): - """ - Download data from changeset `ChangesetId`. - - Returns list of dict: - - #!python - { - 'type': node|way|relation, - 'action': create|delete|modify, - 'data': {} - } - """ - uri = f"/api/0.6/changeset/{ChangesetId}/download" - data = self._session._get(uri) - return parser.ParseOsc(data) - - def ChangesetsGet( # noqa - self, - min_lon=None, - min_lat=None, - max_lon=None, - max_lat=None, - userid=None, - username=None, - closed_after=None, - created_before=None, - only_open=False, - only_closed=False, - ): - """ - Returns a dict with the id of the changeset as key - matching all criteria: - - #!python - { - '1234': dict of ChangesetData, - '5678': dict of ChangesetData, - ... - } - - All parameters are optional. - """ - - uri = "/api/0.6/changesets" - params = {} - if min_lon or min_lat or max_lon or max_lat: - params["bbox"] = f"{min_lon},{min_lat},{max_lon},{max_lat}" - if userid: - params["user"] = userid - if username: - params["display_name"] = username - if closed_after and not created_before: - params["time"] = closed_after - if created_before: - if not closed_after: - closed_after = "1970-01-01T00:00:00Z" - params["time"] = f"{closed_after},{created_before}" - if only_open: - params["open"] = 1 - if only_closed: - params["closed"] = 1 - - if params: - uri += "?" + urllib.parse.urlencode(params) - - data = self._session._get(uri) - changesets = dom.OsmResponseToDom(data, tag="changeset") - result = {} - for curChangeset in changesets: - tmpCS = dom.DomParseChangeset(curChangeset) - result[tmpCS["id"]] = tmpCS - return result - - def ChangesetComment(self, ChangesetId, comment): - """ - Adds a comment to the changeset `ChangesetId` - - `comment` should be a string. - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the changeset is already closed, - `OsmApi.ChangesetClosedApiError` is raised. - """ - params = urllib.parse.urlencode({"text": comment}) - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/comment", params, forceAuth=True - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) - - def ChangesetSubscribe(self, ChangesetId): - """ - Subcribe to the changeset discussion of changeset `ChangesetId`. - - The user will be informed about new comments (i.e. receive an email). - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/subscribe", None, forceAuth=True - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.AlreadySubscribedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) - - def ChangesetUnsubscribe(self, ChangesetId): - """ - Subcribe to the changeset discussion of changeset `ChangesetId`. - - The user will be informed about new comments (i.e. receive an email). - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/unsubscribe", None, forceAuth=True - ) - except errors.ElementNotFoundApiError as e: - raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e - - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) - - ################################################## - # Notes # - ################################################## - - def NotesGet(self, min_lon, min_lat, max_lon, max_lat, limit=100, closed=7): - """ - Returns a list of dicts of notes in the specified bounding box: - - #!python - [ - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - }, - { ... } - ] - - The limit parameter defines how many results should be returned. - - closed specifies the number of days a bug needs to be closed - to no longer be returned. - The value 0 means only open bugs are returned, - -1 means all bugs are returned. - - All parameters are optional. - """ - uri = ( - f"/api/0.6/notes?bbox=" - f"{min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" - f"&limit={limit}&closed={closed}" - ) - data = self._session._get(uri) - return parser.ParseNotes(data) - - def NoteGet(self, id): - """ - Returns a note as dict: - - #!python - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - } - - `id` is the unique identifier of the note. - """ - uri = f"/api/0.6/notes/{id}" - data = self._session._get(uri) - noteElement = dom.OsmResponseToDom(data, tag="note", single=True) - return dom.DomParseNote(noteElement) - - def NoteCreate(self, NoteData): - """ - Creates a note based on the supplied `NoteData` dict: - - #!python - { - 'lat': latitude of note, - 'lon': longitude of note, - 'text': text of the note, - } - - Returns updated `NoteData`: - - #!python - { - 'id': id of note, - 'lat': latitude of note, - 'lon': longitude of note, - 'date_created': date when the note was created - 'date_closed': date when the note was closed or None if it's open, - 'status': status of the note (open or closed), - 'comments': [ - { - 'date': date of the comment, - 'action': status of comment (opened, commented, closed), - 'text': text of the note, - 'html': html version of the text of the note, - 'uid': user id of the user creating this note or None - 'user': username of the user creating this note or None - } - ] - } - - """ - uri = "/api/0.6/notes" - uri += "?" + urllib.parse.urlencode(NoteData) - return self._NoteAction(uri) - - def NoteComment(self, NoteId, comment): - """ - Adds a new comment to a note. - - Returns the updated note. - """ - path = f"/api/0.6/notes/{NoteId}/comment" - return self._NoteAction(path, comment) - - def NoteClose(self, NoteId, comment): - """ - Closes a note. - - Returns the updated note. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - path = f"/api/0.6/notes/{NoteId}/close" - return self._NoteAction(path, comment, optionalAuth=False) - - def NoteReopen(self, NoteId, comment): - """ - Reopens a note. - - Returns the updated note. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - path = f"/api/0.6/notes/{NoteId}/reopen" - return self._NoteAction(path, comment, optionalAuth=False) - - def NotesSearch(self, query, limit=100, closed=7): - """ - Returns a list of dicts of notes that match the given search query. - - The limit parameter defines how many results should be returned. - - closed specifies the number of days a bug needs to be closed - to no longer be returned. - The value 0 means only open bugs are returned, - -1 means all bugs are returned. - """ - uri = "/api/0.6/notes/search" - params = {} - params["q"] = query - params["limit"] = limit - params["closed"] = closed - uri += "?" + urllib.parse.urlencode(params) - data = self._session._get(uri) - - return parser.ParseNotes(data) - - def _NoteAction(self, path, comment=None, optionalAuth=True): - """ - Performs an action on a Note with a comment - - Return the updated note - """ - uri = path - if comment is not None: - params = {} - params["text"] = comment - uri += "?" + urllib.parse.urlencode(params) - try: - result = self._session._post(uri, None, optionalAuth=optionalAuth) - except errors.ApiError as e: - if e.status == 409: - raise errors.NoteAlreadyClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - - # parse the result - noteElement = dom.OsmResponseToDom(result, tag="note", single=True) - return dom.DomParseNote(noteElement) - - ################################################## - # Other # - ################################################## - - def Map(self, min_lon, min_lat, max_lon, max_lat): - """ - Download data in bounding box. - - Returns list of dict: - - #!python - { - type: node|way|relation, - data: {} - } - """ - uri = f"/api/0.6/map?bbox={min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" - data = self._session._get(uri) - return parser.ParseOsm(data) - - def flush(self): - """ - Force the changes to be uploaded to OSM and the changeset to be closed - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - return self._changesetautoflush(True) - - ################################################## - # Internal method # - ################################################## - - def _do(self, action, OsmType, OsmData): - if self._changesetauto: - self._changesetautodata.append( - {"action": action, "type": OsmType, "data": OsmData} - ) - self._changesetautoflush() - return None - else: - return self._do_manu(action, OsmType, OsmData) - - def _do_manu(self, action, OsmType, OsmData): # noqa - if not self._CurrentChangesetId: - raise errors.NoChangesetOpenError( - "You need to open a changeset before uploading data" - ) - if "timestamp" in OsmData: - OsmData.pop("timestamp") - OsmData["changeset"] = self._CurrentChangesetId - if action == "create": - if OsmData.get("id", -1) > 0: - raise errors.OsmTypeAlreadyExistsError(f"This {OsmType} already exists") - try: - result = self._session._put( - f"/api/0.6/{OsmType}/create", - xmlbuilder._XmlBuild(OsmType, OsmData, data=self), - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 409: - raise errors.VersionMismatchApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 412: - raise errors.PreconditionFailedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - OsmData["id"] = int(result.strip()) - OsmData["version"] = 1 - return OsmData - elif action == "modify": - try: - result = self._session._put( - f"/api/0.6/{OsmType}/{OsmData['id']}", - xmlbuilder._XmlBuild(OsmType, OsmData, data=self), - ) - except errors.ApiError as e: - logger.error(e.reason) - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 409: - raise errors.VersionMismatchApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 412: - raise errors.PreconditionFailedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - OsmData["version"] = int(result.strip()) - return OsmData - elif action == "delete": - try: - result = self._session._delete( - f"/api/0.6/{OsmType}/{OsmData['id']}", - xmlbuilder._XmlBuild(OsmType, OsmData, data=self), - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 409: - raise errors.VersionMismatchApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 412: - raise errors.PreconditionFailedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - OsmData["version"] = int(result.strip()) - OsmData["visible"] = False - return OsmData - - def _changesetautoflush(self, force=False): - autosize = self._changesetautosize - while (len(self._changesetautodata) >= autosize) or ( - force and self._changesetautodata - ): - if self._changesetautocpt == 0: - self.ChangesetCreate(self._changesetautotags) - self.ChangesetUpload(self._changesetautodata[:autosize]) - self._changesetautodata = self._changesetautodata[autosize:] - self._changesetautocpt += 1 - if self._changesetautocpt == self._changesetautomulti: - self.ChangesetClose() - self._changesetautocpt = 0 - if self._changesetautocpt and force: - 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")) -
1""" + 2The OsmApi module is a wrapper for the OpenStreetMap API. + 3As such it provides an easy access to the functionality of the API. + 4 + 5You can find this module [on PyPI](https://pypi.python.org/pypi/osmapi) + 6or [on GitHub](https://github.com/metaodi/osmapi). + 7 + 8Find all information about changes of the different versions of this module + 9[in the CHANGELOG](https://github.com/metaodi/osmapi/blob/master/CHANGELOG.md). + 10 + 11 + 12## Notes: + 13 + 14* **dictionary keys** are _unicode_ + 15* **changeset** is _integer_ + 16* **version** is _integer_ + 17* **tag** is a _dictionary_ + 18* **timestamp** is _unicode_ + 19* **user** is _unicode_ + 20* **uid** is _integer_ + 21* node **lat** and **lon** are _floats_ + 22* way **nd** is list of _integers_ + 23* relation **member** is a _list of dictionaries_ like + 24`{"role": "", "ref":123, "type": "node"}` + 25* Since version 5.0 of this library, all method names are in snake_case, + 26the CamelCase versions are deprecated and will be removed in version 6.0. + 27""" + 28 + 29import re + 30import logging + 31import warnings + 32from contextlib import contextmanager + 33from typing import Any, Optional, Generator + 34from xml.dom.minidom import Element + 35import requests + 36 + 37from osmapi import __version__ + 38from . import errors + 39from . import http + 40from . import xmlbuilder + 41from .node import NodeMixin + 42from .way import WayMixin + 43from .relation import RelationMixin + 44from .changeset import ChangesetMixin + 45from .note import NoteMixin + 46from .capabilities import CapabilitiesMixin + 47 + 48logger = logging.getLogger(__name__) + 49 + 50 + 51class OsmApi( + 52 NodeMixin, + 53 WayMixin, + 54 RelationMixin, + 55 ChangesetMixin, + 56 NoteMixin, + 57 CapabilitiesMixin, + 58): + 59 """ + 60 Main class of osmapi, instanciate this class to use osmapi + 61 """ + 62 + 63 def __init__( + 64 self, + 65 username: Optional[str] = None, + 66 password: Optional[str] = None, + 67 passwordfile: Optional[str] = None, + 68 appid: str = "", + 69 created_by: str = f"osmapi/{__version__}", + 70 api: str = "https://www.openstreetmap.org", + 71 session: Optional[requests.Session] = None, + 72 timeout: int = 30, + 73 ) -> None: + 74 """ + 75 Initialized the OsmApi object. + 76 + 77 There are two different ways to authenticate a user. + 78 Either `username` and `password` are supplied directly or the path + 79 to a `passwordfile` is given, where on the first line username + 80 and password must be colon-separated (<user>:<pass>). + 81 + 82 To credit the application that supplies changes to OSM, an `appid` + 83 can be provided. This is a string identifying the application. + 84 If this is omitted "osmapi" is used. + 85 + 86 It is possible to configure the URL to connect to using the `api` + 87 parameter. By default this is the SSL version of the production API + 88 of OpenStreetMap, for testing purposes, one might prefer the official + 89 test instance at "api06.dev.openstreetmap.org" or any other valid + 90 OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' + 91 in front of the hostname of the `api` parameter (e.g. + 92 https://api.openstreetmap.com). + 93 + 94 The `session` parameter can be used to provide a custom requests + 95 http session object (requests.Session). This might be useful for + 96 OAuth authentication, custom adapters, hooks etc. + 97 + 98 Finally the `timeout` parameter is used by the http session to + 99 throw an expcetion if the the timeout (in seconds) has passed without +100 an answer from the server. +101 """ +102 # Get username +103 self._username: Optional[str] = None +104 if username: +105 self._username = username +106 elif passwordfile: +107 with open(passwordfile) as f: +108 pass_line = f.readline() +109 self._username = pass_line.partition(":")[0].strip() +110 +111 # Get password +112 self._password: Optional[str] = None +113 if password: +114 self._password = password +115 elif passwordfile: +116 with open(passwordfile) as f: +117 for line in f: +118 key, _, value = line.strip().partition(":") +119 if key == self._username: +120 self._password = value +121 +122 # Get API +123 self._api: str = api.strip("/") +124 +125 # Get created_by +126 if not appid: +127 self._created_by: str = created_by +128 else: +129 self._created_by = f"{appid} ({created_by})" +130 +131 # Initialisation +132 self._current_changeset_id: int = 0 +133 +134 # Http connection +135 self.http_session: Optional[requests.Session] = session +136 self._timeout: int = timeout +137 auth: Optional[tuple[str, str]] = None +138 if self._username and self._password: +139 auth = (self._username, self._password) +140 self._session: http.OsmApiSession = http.OsmApiSession( +141 self._api, +142 self._created_by, +143 auth=auth, +144 session=self.http_session, +145 timeout=self._timeout, +146 ) +147 +148 def __enter__(self) -> "OsmApi": +149 self._session = http.OsmApiSession( +150 self._api, +151 self._created_by, +152 session=self.http_session, +153 timeout=self._timeout, +154 ) +155 return self +156 +157 def __exit__(self, *args: Any) -> None: +158 self.close() +159 +160 def close(self) -> None: +161 if self._session: +162 self._session.close() +163 +164 ################################################## +165 # Capabilities # +166 ################################################## +167 +168 def Capabilities(self) -> dict[str, dict[str, Any]]: +169 """ +170 Returns the API capabilities as a dict. +171 +172 .. deprecated:: +173 Use :meth:`capabilities` instead. +174 +175 The capabilities can be used by a client to +176 gain insights of the server in use. +177 """ +178 warnings.warn( +179 "Capabilities() is deprecated, use capabilities() instead", +180 DeprecationWarning, +181 stacklevel=2, +182 ) +183 return self.capabilities() +184 +185 ################################################## +186 # Node - Deprecated CamelCase methods # +187 ################################################## +188 +189 def NodeGet(self, NodeId: int, NodeVersion: int = -1) -> dict[str, Any]: +190 """.. deprecated:: Use :meth:`node_get` instead.""" +191 warnings.warn( +192 "NodeGet() is deprecated, use node_get() instead", +193 DeprecationWarning, +194 stacklevel=2, +195 ) +196 return self.node_get(NodeId, NodeVersion) +197 +198 def NodeCreate(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +199 """.. deprecated:: Use :meth:`node_create` instead.""" +200 warnings.warn( +201 "NodeCreate() is deprecated, use node_create() instead", +202 DeprecationWarning, +203 stacklevel=2, +204 ) +205 return self.node_create(NodeData) +206 +207 def NodeUpdate(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +208 """.. deprecated:: Use :meth:`node_update` instead.""" +209 warnings.warn( +210 "NodeUpdate() is deprecated, use node_update() instead", +211 DeprecationWarning, +212 stacklevel=2, +213 ) +214 return self.node_update(NodeData) +215 +216 def NodeDelete(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +217 """.. deprecated:: Use :meth:`node_delete` instead.""" +218 warnings.warn( +219 "NodeDelete() is deprecated, use node_delete() instead", +220 DeprecationWarning, +221 stacklevel=2, +222 ) +223 return self.node_delete(NodeData) +224 +225 def NodeHistory(self, NodeId: int) -> dict[int, dict[str, Any]]: +226 """.. deprecated:: Use :meth:`node_history` instead.""" +227 warnings.warn( +228 "NodeHistory() is deprecated, use node_history() instead", +229 DeprecationWarning, +230 stacklevel=2, +231 ) +232 return self.node_history(NodeId) +233 +234 def NodeWays(self, NodeId: int) -> list[dict[str, Any]]: +235 """.. deprecated:: Use :meth:`node_ways` instead.""" +236 warnings.warn( +237 "NodeWays() is deprecated, use node_ways() instead", +238 DeprecationWarning, +239 stacklevel=2, +240 ) +241 return self.node_ways(NodeId) +242 +243 def NodeRelations(self, NodeId: int) -> list[dict[str, Any]]: +244 """.. deprecated:: Use :meth:`node_relations` instead.""" +245 warnings.warn( +246 "NodeRelations() is deprecated, use node_relations() instead", +247 DeprecationWarning, +248 stacklevel=2, +249 ) +250 return self.node_relations(NodeId) +251 +252 def NodesGet(self, NodeIdList: list[int]) -> dict[int, dict[str, Any]]: +253 """.. deprecated:: Use :meth:`nodes_get` instead.""" +254 warnings.warn( +255 "NodesGet() is deprecated, use nodes_get() instead", +256 DeprecationWarning, +257 stacklevel=2, +258 ) +259 return self.nodes_get(NodeIdList) +260 +261 ################################################## +262 # Way - Deprecated CamelCase methods # +263 ################################################## +264 +265 def WayGet(self, WayId: int, WayVersion: int = -1) -> dict[str, Any]: +266 """.. deprecated:: Use :meth:`way_get` instead.""" +267 warnings.warn( +268 "WayGet() is deprecated, use way_get() instead", +269 DeprecationWarning, +270 stacklevel=2, +271 ) +272 return self.way_get(WayId, WayVersion) +273 +274 def WayCreate(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +275 """.. deprecated:: Use :meth:`way_create` instead.""" +276 warnings.warn( +277 "WayCreate() is deprecated, use way_create() instead", +278 DeprecationWarning, +279 stacklevel=2, +280 ) +281 return self.way_create(WayData) +282 +283 def WayUpdate(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +284 """.. deprecated:: Use :meth:`way_update` instead.""" +285 warnings.warn( +286 "WayUpdate() is deprecated, use way_update() instead", +287 DeprecationWarning, +288 stacklevel=2, +289 ) +290 return self.way_update(WayData) +291 +292 def WayDelete(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +293 """.. deprecated:: Use :meth:`way_delete` instead.""" +294 warnings.warn( +295 "WayDelete() is deprecated, use way_delete() instead", +296 DeprecationWarning, +297 stacklevel=2, +298 ) +299 return self.way_delete(WayData) +300 +301 def WayHistory(self, WayId: int) -> dict[int, dict[str, Any]]: +302 """.. deprecated:: Use :meth:`way_history` instead.""" +303 warnings.warn( +304 "WayHistory() is deprecated, use way_history() instead", +305 DeprecationWarning, +306 stacklevel=2, +307 ) +308 return self.way_history(WayId) +309 +310 def WayRelations(self, WayId: int) -> list[dict[str, Any]]: +311 """.. deprecated:: Use :meth:`way_relations` instead.""" +312 warnings.warn( +313 "WayRelations() is deprecated, use way_relations() instead", +314 DeprecationWarning, +315 stacklevel=2, +316 ) +317 return self.way_relations(WayId) +318 +319 def WayFull(self, WayId: int) -> list[dict[str, Any]]: +320 """.. deprecated:: Use :meth:`way_full` instead.""" +321 warnings.warn( +322 "WayFull() is deprecated, use way_full() instead", +323 DeprecationWarning, +324 stacklevel=2, +325 ) +326 return self.way_full(WayId) +327 +328 def WaysGet(self, WayIdList: list[int]) -> dict[int, dict[str, Any]]: +329 """.. deprecated:: Use :meth:`ways_get` instead.""" +330 warnings.warn( +331 "WaysGet() is deprecated, use ways_get() instead", +332 DeprecationWarning, +333 stacklevel=2, +334 ) +335 return self.ways_get(WayIdList) +336 +337 ################################################## +338 # Relation - Deprecated CamelCase methods # +339 ################################################## +340 +341 def RelationGet(self, RelationId: int, RelationVersion: int = -1) -> dict[str, Any]: +342 """.. deprecated:: Use :meth:`relation_get` instead.""" +343 warnings.warn( +344 "RelationGet() is deprecated, use relation_get() instead", +345 DeprecationWarning, +346 stacklevel=2, +347 ) +348 return self.relation_get(RelationId, RelationVersion) +349 +350 def RelationCreate(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +351 """.. deprecated:: Use :meth:`relation_create` instead.""" +352 warnings.warn( +353 "RelationCreate() is deprecated, use relation_create() instead", +354 DeprecationWarning, +355 stacklevel=2, +356 ) +357 return self.relation_create(RelationData) +358 +359 def RelationUpdate(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +360 """.. deprecated:: Use :meth:`relation_update` instead.""" +361 warnings.warn( +362 "RelationUpdate() is deprecated, use relation_update() instead", +363 DeprecationWarning, +364 stacklevel=2, +365 ) +366 return self.relation_update(RelationData) +367 +368 def RelationDelete(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +369 """.. deprecated:: Use :meth:`relation_delete` instead.""" +370 warnings.warn( +371 "RelationDelete() is deprecated, use relation_delete() instead", +372 DeprecationWarning, +373 stacklevel=2, +374 ) +375 return self.relation_delete(RelationData) +376 +377 def RelationHistory(self, RelationId: int) -> dict[int, dict[str, Any]]: +378 """.. deprecated:: Use :meth:`relation_history` instead.""" +379 warnings.warn( +380 "RelationHistory() is deprecated, use relation_history() instead", +381 DeprecationWarning, +382 stacklevel=2, +383 ) +384 return self.relation_history(RelationId) +385 +386 def RelationRelations(self, RelationId: int) -> list[dict[str, Any]]: +387 """.. deprecated:: Use :meth:`relation_relations` instead.""" +388 warnings.warn( +389 "RelationRelations() is deprecated, use relation_relations() instead", +390 DeprecationWarning, +391 stacklevel=2, +392 ) +393 return self.relation_relations(RelationId) +394 +395 def RelationFullRecur(self, RelationId: int) -> list[dict[str, Any]]: +396 """.. deprecated:: Use :meth:`relation_full_recur` instead.""" +397 warnings.warn( +398 "RelationFullRecur() is deprecated, use relation_full_recur() instead", +399 DeprecationWarning, +400 stacklevel=2, +401 ) +402 return self.relation_full_recur(RelationId) +403 +404 def RelationFull(self, RelationId: int) -> list[dict[str, Any]]: +405 """.. deprecated:: Use :meth:`relation_full` instead.""" +406 warnings.warn( +407 "RelationFull() is deprecated, use relation_full() instead", +408 DeprecationWarning, +409 stacklevel=2, +410 ) +411 return self.relation_full(RelationId) +412 +413 def RelationsGet(self, RelationIdList: list[int]) -> dict[int, dict[str, Any]]: +414 """.. deprecated:: Use :meth:`relations_get` instead.""" +415 warnings.warn( +416 "RelationsGet() is deprecated, use relations_get() instead", +417 DeprecationWarning, +418 stacklevel=2, +419 ) +420 return self.relations_get(RelationIdList) +421 +422 ################################################## +423 # Changeset - Deprecated CamelCase methods # +424 ################################################## +425 +426 @contextmanager +427 def Changeset( +428 self, ChangesetTags: Optional[dict[str, str]] = None +429 ) -> Generator[int, None, None]: +430 """.. deprecated:: Use :meth:`changeset` instead.""" +431 warnings.warn( +432 "Changeset() is deprecated, use changeset() instead", +433 DeprecationWarning, +434 stacklevel=2, +435 ) +436 with self.changeset(ChangesetTags) as changeset_id: +437 yield changeset_id +438 +439 def ChangesetGet( +440 self, ChangesetId: int, include_discussion: bool = False +441 ) -> dict[str, Any]: +442 """.. deprecated:: Use :meth:`changeset_get` instead.""" +443 warnings.warn( +444 "ChangesetGet() is deprecated, use changeset_get() instead", +445 DeprecationWarning, +446 stacklevel=2, +447 ) +448 return self.changeset_get(ChangesetId, include_discussion) +449 +450 def ChangesetUpdate(self, ChangesetTags: Optional[dict[str, str]] = None) -> int: +451 """.. deprecated:: Use :meth:`changeset_update` instead.""" +452 warnings.warn( +453 "ChangesetUpdate() is deprecated, use changeset_update() instead", +454 DeprecationWarning, +455 stacklevel=2, +456 ) +457 return self.changeset_update(ChangesetTags) +458 +459 def ChangesetCreate(self, ChangesetTags: Optional[dict[str, str]] = None) -> int: +460 """.. deprecated:: Use :meth:`changeset_create` instead.""" +461 warnings.warn( +462 "ChangesetCreate() is deprecated, use changeset_create() instead", +463 DeprecationWarning, +464 stacklevel=2, +465 ) +466 return self.changeset_create(ChangesetTags) +467 +468 def ChangesetClose(self) -> int: +469 """.. deprecated:: Use :meth:`changeset_close` instead.""" +470 warnings.warn( +471 "ChangesetClose() is deprecated, use changeset_close() instead", +472 DeprecationWarning, +473 stacklevel=2, +474 ) +475 return self.changeset_close() +476 +477 def ChangesetUpload( +478 self, ChangesData: list[dict[str, Any]] +479 ) -> list[dict[str, Any]]: +480 """.. deprecated:: Use :meth:`changeset_upload` instead.""" +481 warnings.warn( +482 "ChangesetUpload() is deprecated, use changeset_upload() instead", +483 DeprecationWarning, +484 stacklevel=2, +485 ) +486 return self.changeset_upload(ChangesData) +487 +488 def ChangesetDownload(self, ChangesetId: int) -> list[dict[str, Any]]: +489 """.. deprecated:: Use :meth:`changeset_download` instead.""" +490 warnings.warn( +491 "ChangesetDownload() is deprecated, use changeset_download() instead", +492 DeprecationWarning, +493 stacklevel=2, +494 ) +495 return self.changeset_download(ChangesetId) +496 +497 def ChangesetsGet( # noqa +498 self, +499 min_lon: Optional[float] = None, +500 min_lat: Optional[float] = None, +501 max_lon: Optional[float] = None, +502 max_lat: Optional[float] = None, +503 userid: Optional[int] = None, +504 username: Optional[str] = None, +505 closed_after: Optional[str] = None, +506 created_before: Optional[str] = None, +507 only_open: bool = False, +508 only_closed: bool = False, +509 ) -> dict[int, dict[str, Any]]: +510 """.. deprecated:: Use :meth:`changesets_get` instead.""" +511 warnings.warn( +512 "ChangesetsGet() is deprecated, use changesets_get() instead", +513 DeprecationWarning, +514 stacklevel=2, +515 ) +516 return self.changesets_get( +517 min_lon, +518 min_lat, +519 max_lon, +520 max_lat, +521 userid, +522 username, +523 closed_after, +524 created_before, +525 only_open, +526 only_closed, +527 ) +528 +529 def ChangesetComment(self, ChangesetId: int, comment: str) -> dict[str, Any]: +530 """.. deprecated:: Use :meth:`changeset_comment` instead.""" +531 warnings.warn( +532 "ChangesetComment() is deprecated, use changeset_comment() instead", +533 DeprecationWarning, +534 stacklevel=2, +535 ) +536 return self.changeset_comment(ChangesetId, comment) +537 +538 def ChangesetSubscribe(self, ChangesetId: int) -> dict[str, Any]: +539 """.. deprecated:: Use :meth:`changeset_subscribe` instead.""" +540 warnings.warn( +541 "ChangesetSubscribe() is deprecated, use changeset_subscribe() instead", +542 DeprecationWarning, +543 stacklevel=2, +544 ) +545 return self.changeset_subscribe(ChangesetId) +546 +547 def ChangesetUnsubscribe(self, ChangesetId: int) -> dict[str, Any]: +548 """.. deprecated:: Use :meth:`changeset_unsubscribe` instead.""" +549 warnings.warn( +550 "ChangesetUnsubscribe() is deprecated, use changeset_unsubscribe() instead", +551 DeprecationWarning, +552 stacklevel=2, +553 ) +554 return self.changeset_unsubscribe(ChangesetId) +555 +556 ################################################## +557 # Note - Deprecated CamelCase methods # +558 ################################################## +559 +560 def NotesGet( +561 self, +562 min_lon: float, +563 min_lat: float, +564 max_lon: float, +565 max_lat: float, +566 limit: int = 100, +567 closed: int = 7, +568 ) -> list[dict[str, Any]]: +569 """.. deprecated:: Use :meth:`notes_get` instead.""" +570 warnings.warn( +571 "NotesGet() is deprecated, use notes_get() instead", +572 DeprecationWarning, +573 stacklevel=2, +574 ) +575 return self.notes_get(min_lon, min_lat, max_lon, max_lat, limit, closed) +576 +577 def NoteGet(self, id: int) -> dict[str, Any]: +578 """.. deprecated:: Use :meth:`note_get` instead.""" +579 warnings.warn( +580 "NoteGet() is deprecated, use note_get() instead", +581 DeprecationWarning, +582 stacklevel=2, +583 ) +584 return self.note_get(id) +585 +586 def NoteCreate(self, NoteData: dict[str, Any]) -> dict[str, Any]: +587 """.. deprecated:: Use :meth:`note_create` instead.""" +588 warnings.warn( +589 "NoteCreate() is deprecated, use note_create() instead", +590 DeprecationWarning, +591 stacklevel=2, +592 ) +593 return self.note_create(NoteData) +594 +595 def NoteComment(self, note_id: int, comment: str) -> dict[str, Any]: +596 """.. deprecated:: Use :meth:`note_comment` instead.""" +597 warnings.warn( +598 "NoteComment() is deprecated, use note_comment() instead", +599 DeprecationWarning, +600 stacklevel=2, +601 ) +602 return self.note_comment(note_id, comment) +603 +604 def NoteClose(self, note_id: int, comment: Optional[str] = None) -> dict[str, Any]: +605 """.. deprecated:: Use :meth:`note_close` instead.""" +606 warnings.warn( +607 "NoteClose() is deprecated, use note_close() instead", +608 DeprecationWarning, +609 stacklevel=2, +610 ) +611 return self.note_close(note_id, comment) +612 +613 def NoteReopen(self, note_id: int, comment: Optional[str] = None) -> dict[str, Any]: +614 """.. deprecated:: Use :meth:`note_reopen` instead.""" +615 warnings.warn( +616 "NoteReopen() is deprecated, use note_reopen() instead", +617 DeprecationWarning, +618 stacklevel=2, +619 ) +620 return self.note_reopen(note_id, comment) +621 +622 def NotesSearch( +623 self, query: str, limit: int = 100, closed: int = 7 +624 ) -> list[dict[str, Any]]: +625 """.. deprecated:: Use :meth:`notes_search` instead.""" +626 warnings.warn( +627 "NotesSearch() is deprecated, use notes_search() instead", +628 DeprecationWarning, +629 stacklevel=2, +630 ) +631 return self.notes_search(query, limit, closed) +632 +633 def _NoteAction( +634 self, path: str, comment: Optional[str] = None, optionalAuth: bool = True +635 ) -> dict[str, Any]: +636 """Internal method - calls _note_action.""" +637 return self._note_action(path, comment, optionalAuth) +638 +639 ################################################## +640 # Map - Deprecated CamelCase methods # +641 ################################################## +642 +643 def Map( +644 self, min_lon: float, min_lat: float, max_lon: float, max_lat: float +645 ) -> list[dict[str, Any]]: +646 """.. deprecated:: Use :meth:`map` instead.""" +647 warnings.warn( +648 "Map() is deprecated, use map() instead", +649 DeprecationWarning, +650 stacklevel=2, +651 ) +652 return self.map(min_lon, min_lat, max_lon, max_lat) +653 +654 ################################################## +655 # Internal method # +656 ################################################## +657 +658 def _do( # type: ignore[return-value] # noqa: C901 +659 self, action: str, osm_type: str, osm_data: dict[str, Any] +660 ) -> dict[str, Any]: +661 if not self._current_changeset_id: +662 raise errors.NoChangesetOpenError( +663 "You need to open a changeset before uploading data" +664 ) +665 if "timestamp" in osm_data: +666 osm_data.pop("timestamp") +667 osm_data["changeset"] = self._current_changeset_id +668 if action == "create": +669 if osm_data.get("id", -1) > 0: +670 raise errors.OsmTypeAlreadyExistsError( +671 f"This {osm_type} already exists" +672 ) +673 try: +674 result = self._session._put( +675 f"/api/0.6/{osm_type}/create", +676 xmlbuilder._xml_build(osm_type, osm_data, data=self), +677 ) +678 except errors.ApiError as e: +679 if e.status == 409 and re.search( +680 r"The changeset .* was closed at .*", e.payload +681 ): +682 raise errors.ChangesetClosedApiError( +683 e.status, e.reason, e.payload +684 ) from e +685 elif e.status == 409: +686 raise errors.VersionMismatchApiError( +687 e.status, e.reason, e.payload +688 ) from e +689 elif e.status == 412: +690 raise errors.PreconditionFailedApiError( +691 e.status, e.reason, e.payload +692 ) from e +693 else: +694 raise +695 osm_data["id"] = int(result.strip()) +696 osm_data["version"] = 1 +697 return osm_data +698 elif action == "modify": +699 try: +700 result = self._session._put( +701 f"/api/0.6/{osm_type}/{osm_data['id']}", +702 xmlbuilder._xml_build(osm_type, osm_data, data=self), +703 ) +704 except errors.ApiError as e: +705 logger.error(e.reason) +706 if e.status == 409 and re.search( +707 r"The changeset .* was closed at .*", e.payload +708 ): +709 raise errors.ChangesetClosedApiError( +710 e.status, e.reason, e.payload +711 ) from e +712 elif e.status == 409: +713 raise errors.VersionMismatchApiError( +714 e.status, e.reason, e.payload +715 ) from e +716 elif e.status == 412: +717 raise errors.PreconditionFailedApiError( +718 e.status, e.reason, e.payload +719 ) from e +720 else: +721 raise +722 osm_data["version"] = int(result.strip()) +723 return osm_data +724 elif action == "delete": +725 try: +726 result = self._session._delete( +727 f"/api/0.6/{osm_type}/{osm_data['id']}", +728 xmlbuilder._xml_build(osm_type, osm_data, data=self), +729 ) +730 except errors.ApiError as e: +731 if e.status == 409 and re.search( +732 r"The changeset .* was closed at .*", e.payload +733 ): +734 raise errors.ChangesetClosedApiError( +735 e.status, e.reason, e.payload +736 ) from e +737 elif e.status == 409: +738 raise errors.VersionMismatchApiError( +739 e.status, e.reason, e.payload +740 ) from e +741 elif e.status == 412: +742 raise errors.PreconditionFailedApiError( +743 e.status, e.reason, e.payload +744 ) from e +745 else: +746 raise +747 osm_data["version"] = int(result.strip()) +748 osm_data["visible"] = False +749 return osm_data +750 +751 def _add_changeset_data(self, change_data: list[dict[str, Any]], type: str) -> str: +752 data = "" +753 for changed_element in change_data: +754 changed_element["changeset"] = self._current_changeset_id +755 xml_data = xmlbuilder._xml_build(type, changed_element, False, data=self) +756 data += xml_data.decode("utf-8") +757 return data +758 +759 def _assign_id_and_version( +760 self, response_data: list[Element], request_data: list[dict[str, Any]] +761 ) -> None: +762 for response, element in zip(response_data, request_data): +763 element["id"] = int(response.getAttribute("new_id")) +764 element["version"] = int(response.getAttribute("new_version")) +
52class OsmApi( + 53 NodeMixin, + 54 WayMixin, + 55 RelationMixin, + 56 ChangesetMixin, + 57 NoteMixin, + 58 CapabilitiesMixin, + 59): + 60 """ + 61 Main class of osmapi, instanciate this class to use osmapi + 62 """ + 63 + 64 def __init__( + 65 self, + 66 username: Optional[str] = None, + 67 password: Optional[str] = None, + 68 passwordfile: Optional[str] = None, + 69 appid: str = "", + 70 created_by: str = f"osmapi/{__version__}", + 71 api: str = "https://www.openstreetmap.org", + 72 session: Optional[requests.Session] = None, + 73 timeout: int = 30, + 74 ) -> None: + 75 """ + 76 Initialized the OsmApi object. + 77 + 78 There are two different ways to authenticate a user. + 79 Either `username` and `password` are supplied directly or the path + 80 to a `passwordfile` is given, where on the first line username + 81 and password must be colon-separated (<user>:<pass>). + 82 + 83 To credit the application that supplies changes to OSM, an `appid` + 84 can be provided. This is a string identifying the application. + 85 If this is omitted "osmapi" is used. + 86 + 87 It is possible to configure the URL to connect to using the `api` + 88 parameter. By default this is the SSL version of the production API + 89 of OpenStreetMap, for testing purposes, one might prefer the official + 90 test instance at "api06.dev.openstreetmap.org" or any other valid + 91 OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' + 92 in front of the hostname of the `api` parameter (e.g. + 93 https://api.openstreetmap.com). + 94 + 95 The `session` parameter can be used to provide a custom requests + 96 http session object (requests.Session). This might be useful for + 97 OAuth authentication, custom adapters, hooks etc. + 98 + 99 Finally the `timeout` parameter is used by the http session to +100 throw an expcetion if the the timeout (in seconds) has passed without +101 an answer from the server. +102 """ +103 # Get username +104 self._username: Optional[str] = None +105 if username: +106 self._username = username +107 elif passwordfile: +108 with open(passwordfile) as f: +109 pass_line = f.readline() +110 self._username = pass_line.partition(":")[0].strip() +111 +112 # Get password +113 self._password: Optional[str] = None +114 if password: +115 self._password = password +116 elif passwordfile: +117 with open(passwordfile) as f: +118 for line in f: +119 key, _, value = line.strip().partition(":") +120 if key == self._username: +121 self._password = value +122 +123 # Get API +124 self._api: str = api.strip("/") +125 +126 # Get created_by +127 if not appid: +128 self._created_by: str = created_by +129 else: +130 self._created_by = f"{appid} ({created_by})" +131 +132 # Initialisation +133 self._current_changeset_id: int = 0 +134 +135 # Http connection +136 self.http_session: Optional[requests.Session] = session +137 self._timeout: int = timeout +138 auth: Optional[tuple[str, str]] = None +139 if self._username and self._password: +140 auth = (self._username, self._password) +141 self._session: http.OsmApiSession = http.OsmApiSession( +142 self._api, +143 self._created_by, +144 auth=auth, +145 session=self.http_session, +146 timeout=self._timeout, +147 ) +148 +149 def __enter__(self) -> "OsmApi": +150 self._session = http.OsmApiSession( +151 self._api, +152 self._created_by, +153 session=self.http_session, +154 timeout=self._timeout, +155 ) +156 return self +157 +158 def __exit__(self, *args: Any) -> None: +159 self.close() +160 +161 def close(self) -> None: +162 if self._session: +163 self._session.close() +164 +165 ################################################## +166 # Capabilities # +167 ################################################## +168 +169 def Capabilities(self) -> dict[str, dict[str, Any]]: +170 """ +171 Returns the API capabilities as a dict. +172 +173 .. deprecated:: +174 Use :meth:`capabilities` instead. +175 +176 The capabilities can be used by a client to +177 gain insights of the server in use. +178 """ +179 warnings.warn( +180 "Capabilities() is deprecated, use capabilities() instead", +181 DeprecationWarning, +182 stacklevel=2, +183 ) +184 return self.capabilities() +185 +186 ################################################## +187 # Node - Deprecated CamelCase methods # +188 ################################################## +189 +190 def NodeGet(self, NodeId: int, NodeVersion: int = -1) -> dict[str, Any]: +191 """.. deprecated:: Use :meth:`node_get` instead.""" +192 warnings.warn( +193 "NodeGet() is deprecated, use node_get() instead", +194 DeprecationWarning, +195 stacklevel=2, +196 ) +197 return self.node_get(NodeId, NodeVersion) +198 +199 def NodeCreate(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +200 """.. deprecated:: Use :meth:`node_create` instead.""" +201 warnings.warn( +202 "NodeCreate() is deprecated, use node_create() instead", +203 DeprecationWarning, +204 stacklevel=2, +205 ) +206 return self.node_create(NodeData) +207 +208 def NodeUpdate(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +209 """.. deprecated:: Use :meth:`node_update` instead.""" +210 warnings.warn( +211 "NodeUpdate() is deprecated, use node_update() instead", +212 DeprecationWarning, +213 stacklevel=2, +214 ) +215 return self.node_update(NodeData) +216 +217 def NodeDelete(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +218 """.. deprecated:: Use :meth:`node_delete` instead.""" +219 warnings.warn( +220 "NodeDelete() is deprecated, use node_delete() instead", +221 DeprecationWarning, +222 stacklevel=2, +223 ) +224 return self.node_delete(NodeData) +225 +226 def NodeHistory(self, NodeId: int) -> dict[int, dict[str, Any]]: +227 """.. deprecated:: Use :meth:`node_history` instead.""" +228 warnings.warn( +229 "NodeHistory() is deprecated, use node_history() instead", +230 DeprecationWarning, +231 stacklevel=2, +232 ) +233 return self.node_history(NodeId) +234 +235 def NodeWays(self, NodeId: int) -> list[dict[str, Any]]: +236 """.. deprecated:: Use :meth:`node_ways` instead.""" +237 warnings.warn( +238 "NodeWays() is deprecated, use node_ways() instead", +239 DeprecationWarning, +240 stacklevel=2, +241 ) +242 return self.node_ways(NodeId) +243 +244 def NodeRelations(self, NodeId: int) -> list[dict[str, Any]]: +245 """.. deprecated:: Use :meth:`node_relations` instead.""" +246 warnings.warn( +247 "NodeRelations() is deprecated, use node_relations() instead", +248 DeprecationWarning, +249 stacklevel=2, +250 ) +251 return self.node_relations(NodeId) +252 +253 def NodesGet(self, NodeIdList: list[int]) -> dict[int, dict[str, Any]]: +254 """.. deprecated:: Use :meth:`nodes_get` instead.""" +255 warnings.warn( +256 "NodesGet() is deprecated, use nodes_get() instead", +257 DeprecationWarning, +258 stacklevel=2, +259 ) +260 return self.nodes_get(NodeIdList) +261 +262 ################################################## +263 # Way - Deprecated CamelCase methods # +264 ################################################## +265 +266 def WayGet(self, WayId: int, WayVersion: int = -1) -> dict[str, Any]: +267 """.. deprecated:: Use :meth:`way_get` instead.""" +268 warnings.warn( +269 "WayGet() is deprecated, use way_get() instead", +270 DeprecationWarning, +271 stacklevel=2, +272 ) +273 return self.way_get(WayId, WayVersion) +274 +275 def WayCreate(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +276 """.. deprecated:: Use :meth:`way_create` instead.""" +277 warnings.warn( +278 "WayCreate() is deprecated, use way_create() instead", +279 DeprecationWarning, +280 stacklevel=2, +281 ) +282 return self.way_create(WayData) +283 +284 def WayUpdate(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +285 """.. deprecated:: Use :meth:`way_update` instead.""" +286 warnings.warn( +287 "WayUpdate() is deprecated, use way_update() instead", +288 DeprecationWarning, +289 stacklevel=2, +290 ) +291 return self.way_update(WayData) +292 +293 def WayDelete(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +294 """.. deprecated:: Use :meth:`way_delete` instead.""" +295 warnings.warn( +296 "WayDelete() is deprecated, use way_delete() instead", +297 DeprecationWarning, +298 stacklevel=2, +299 ) +300 return self.way_delete(WayData) +301 +302 def WayHistory(self, WayId: int) -> dict[int, dict[str, Any]]: +303 """.. deprecated:: Use :meth:`way_history` instead.""" +304 warnings.warn( +305 "WayHistory() is deprecated, use way_history() instead", +306 DeprecationWarning, +307 stacklevel=2, +308 ) +309 return self.way_history(WayId) +310 +311 def WayRelations(self, WayId: int) -> list[dict[str, Any]]: +312 """.. deprecated:: Use :meth:`way_relations` instead.""" +313 warnings.warn( +314 "WayRelations() is deprecated, use way_relations() instead", +315 DeprecationWarning, +316 stacklevel=2, +317 ) +318 return self.way_relations(WayId) +319 +320 def WayFull(self, WayId: int) -> list[dict[str, Any]]: +321 """.. deprecated:: Use :meth:`way_full` instead.""" +322 warnings.warn( +323 "WayFull() is deprecated, use way_full() instead", +324 DeprecationWarning, +325 stacklevel=2, +326 ) +327 return self.way_full(WayId) +328 +329 def WaysGet(self, WayIdList: list[int]) -> dict[int, dict[str, Any]]: +330 """.. deprecated:: Use :meth:`ways_get` instead.""" +331 warnings.warn( +332 "WaysGet() is deprecated, use ways_get() instead", +333 DeprecationWarning, +334 stacklevel=2, +335 ) +336 return self.ways_get(WayIdList) +337 +338 ################################################## +339 # Relation - Deprecated CamelCase methods # +340 ################################################## +341 +342 def RelationGet(self, RelationId: int, RelationVersion: int = -1) -> dict[str, Any]: +343 """.. deprecated:: Use :meth:`relation_get` instead.""" +344 warnings.warn( +345 "RelationGet() is deprecated, use relation_get() instead", +346 DeprecationWarning, +347 stacklevel=2, +348 ) +349 return self.relation_get(RelationId, RelationVersion) +350 +351 def RelationCreate(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +352 """.. deprecated:: Use :meth:`relation_create` instead.""" +353 warnings.warn( +354 "RelationCreate() is deprecated, use relation_create() instead", +355 DeprecationWarning, +356 stacklevel=2, +357 ) +358 return self.relation_create(RelationData) +359 +360 def RelationUpdate(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +361 """.. deprecated:: Use :meth:`relation_update` instead.""" +362 warnings.warn( +363 "RelationUpdate() is deprecated, use relation_update() instead", +364 DeprecationWarning, +365 stacklevel=2, +366 ) +367 return self.relation_update(RelationData) +368 +369 def RelationDelete(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +370 """.. deprecated:: Use :meth:`relation_delete` instead.""" +371 warnings.warn( +372 "RelationDelete() is deprecated, use relation_delete() instead", +373 DeprecationWarning, +374 stacklevel=2, +375 ) +376 return self.relation_delete(RelationData) +377 +378 def RelationHistory(self, RelationId: int) -> dict[int, dict[str, Any]]: +379 """.. deprecated:: Use :meth:`relation_history` instead.""" +380 warnings.warn( +381 "RelationHistory() is deprecated, use relation_history() instead", +382 DeprecationWarning, +383 stacklevel=2, +384 ) +385 return self.relation_history(RelationId) +386 +387 def RelationRelations(self, RelationId: int) -> list[dict[str, Any]]: +388 """.. deprecated:: Use :meth:`relation_relations` instead.""" +389 warnings.warn( +390 "RelationRelations() is deprecated, use relation_relations() instead", +391 DeprecationWarning, +392 stacklevel=2, +393 ) +394 return self.relation_relations(RelationId) +395 +396 def RelationFullRecur(self, RelationId: int) -> list[dict[str, Any]]: +397 """.. deprecated:: Use :meth:`relation_full_recur` instead.""" +398 warnings.warn( +399 "RelationFullRecur() is deprecated, use relation_full_recur() instead", +400 DeprecationWarning, +401 stacklevel=2, +402 ) +403 return self.relation_full_recur(RelationId) +404 +405 def RelationFull(self, RelationId: int) -> list[dict[str, Any]]: +406 """.. deprecated:: Use :meth:`relation_full` instead.""" +407 warnings.warn( +408 "RelationFull() is deprecated, use relation_full() instead", +409 DeprecationWarning, +410 stacklevel=2, +411 ) +412 return self.relation_full(RelationId) +413 +414 def RelationsGet(self, RelationIdList: list[int]) -> dict[int, dict[str, Any]]: +415 """.. deprecated:: Use :meth:`relations_get` instead.""" +416 warnings.warn( +417 "RelationsGet() is deprecated, use relations_get() instead", +418 DeprecationWarning, +419 stacklevel=2, +420 ) +421 return self.relations_get(RelationIdList) +422 +423 ################################################## +424 # Changeset - Deprecated CamelCase methods # +425 ################################################## +426 +427 @contextmanager +428 def Changeset( +429 self, ChangesetTags: Optional[dict[str, str]] = None +430 ) -> Generator[int, None, None]: +431 """.. deprecated:: Use :meth:`changeset` instead.""" +432 warnings.warn( +433 "Changeset() is deprecated, use changeset() instead", +434 DeprecationWarning, +435 stacklevel=2, +436 ) +437 with self.changeset(ChangesetTags) as changeset_id: +438 yield changeset_id +439 +440 def ChangesetGet( +441 self, ChangesetId: int, include_discussion: bool = False +442 ) -> dict[str, Any]: +443 """.. deprecated:: Use :meth:`changeset_get` instead.""" +444 warnings.warn( +445 "ChangesetGet() is deprecated, use changeset_get() instead", +446 DeprecationWarning, +447 stacklevel=2, +448 ) +449 return self.changeset_get(ChangesetId, include_discussion) +450 +451 def ChangesetUpdate(self, ChangesetTags: Optional[dict[str, str]] = None) -> int: +452 """.. deprecated:: Use :meth:`changeset_update` instead.""" +453 warnings.warn( +454 "ChangesetUpdate() is deprecated, use changeset_update() instead", +455 DeprecationWarning, +456 stacklevel=2, +457 ) +458 return self.changeset_update(ChangesetTags) +459 +460 def ChangesetCreate(self, ChangesetTags: Optional[dict[str, str]] = None) -> int: +461 """.. deprecated:: Use :meth:`changeset_create` instead.""" +462 warnings.warn( +463 "ChangesetCreate() is deprecated, use changeset_create() instead", +464 DeprecationWarning, +465 stacklevel=2, +466 ) +467 return self.changeset_create(ChangesetTags) +468 +469 def ChangesetClose(self) -> int: +470 """.. deprecated:: Use :meth:`changeset_close` instead.""" +471 warnings.warn( +472 "ChangesetClose() is deprecated, use changeset_close() instead", +473 DeprecationWarning, +474 stacklevel=2, +475 ) +476 return self.changeset_close() +477 +478 def ChangesetUpload( +479 self, ChangesData: list[dict[str, Any]] +480 ) -> list[dict[str, Any]]: +481 """.. deprecated:: Use :meth:`changeset_upload` instead.""" +482 warnings.warn( +483 "ChangesetUpload() is deprecated, use changeset_upload() instead", +484 DeprecationWarning, +485 stacklevel=2, +486 ) +487 return self.changeset_upload(ChangesData) +488 +489 def ChangesetDownload(self, ChangesetId: int) -> list[dict[str, Any]]: +490 """.. deprecated:: Use :meth:`changeset_download` instead.""" +491 warnings.warn( +492 "ChangesetDownload() is deprecated, use changeset_download() instead", +493 DeprecationWarning, +494 stacklevel=2, +495 ) +496 return self.changeset_download(ChangesetId) +497 +498 def ChangesetsGet( # noqa +499 self, +500 min_lon: Optional[float] = None, +501 min_lat: Optional[float] = None, +502 max_lon: Optional[float] = None, +503 max_lat: Optional[float] = None, +504 userid: Optional[int] = None, +505 username: Optional[str] = None, +506 closed_after: Optional[str] = None, +507 created_before: Optional[str] = None, +508 only_open: bool = False, +509 only_closed: bool = False, +510 ) -> dict[int, dict[str, Any]]: +511 """.. deprecated:: Use :meth:`changesets_get` instead.""" +512 warnings.warn( +513 "ChangesetsGet() is deprecated, use changesets_get() instead", +514 DeprecationWarning, +515 stacklevel=2, +516 ) +517 return self.changesets_get( +518 min_lon, +519 min_lat, +520 max_lon, +521 max_lat, +522 userid, +523 username, +524 closed_after, +525 created_before, +526 only_open, +527 only_closed, +528 ) +529 +530 def ChangesetComment(self, ChangesetId: int, comment: str) -> dict[str, Any]: +531 """.. deprecated:: Use :meth:`changeset_comment` instead.""" +532 warnings.warn( +533 "ChangesetComment() is deprecated, use changeset_comment() instead", +534 DeprecationWarning, +535 stacklevel=2, +536 ) +537 return self.changeset_comment(ChangesetId, comment) +538 +539 def ChangesetSubscribe(self, ChangesetId: int) -> dict[str, Any]: +540 """.. deprecated:: Use :meth:`changeset_subscribe` instead.""" +541 warnings.warn( +542 "ChangesetSubscribe() is deprecated, use changeset_subscribe() instead", +543 DeprecationWarning, +544 stacklevel=2, +545 ) +546 return self.changeset_subscribe(ChangesetId) +547 +548 def ChangesetUnsubscribe(self, ChangesetId: int) -> dict[str, Any]: +549 """.. deprecated:: Use :meth:`changeset_unsubscribe` instead.""" +550 warnings.warn( +551 "ChangesetUnsubscribe() is deprecated, use changeset_unsubscribe() instead", +552 DeprecationWarning, +553 stacklevel=2, +554 ) +555 return self.changeset_unsubscribe(ChangesetId) +556 +557 ################################################## +558 # Note - Deprecated CamelCase methods # +559 ################################################## +560 +561 def NotesGet( +562 self, +563 min_lon: float, +564 min_lat: float, +565 max_lon: float, +566 max_lat: float, +567 limit: int = 100, +568 closed: int = 7, +569 ) -> list[dict[str, Any]]: +570 """.. deprecated:: Use :meth:`notes_get` instead.""" +571 warnings.warn( +572 "NotesGet() is deprecated, use notes_get() instead", +573 DeprecationWarning, +574 stacklevel=2, +575 ) +576 return self.notes_get(min_lon, min_lat, max_lon, max_lat, limit, closed) +577 +578 def NoteGet(self, id: int) -> dict[str, Any]: +579 """.. deprecated:: Use :meth:`note_get` instead.""" +580 warnings.warn( +581 "NoteGet() is deprecated, use note_get() instead", +582 DeprecationWarning, +583 stacklevel=2, +584 ) +585 return self.note_get(id) +586 +587 def NoteCreate(self, NoteData: dict[str, Any]) -> dict[str, Any]: +588 """.. deprecated:: Use :meth:`note_create` instead.""" +589 warnings.warn( +590 "NoteCreate() is deprecated, use note_create() instead", +591 DeprecationWarning, +592 stacklevel=2, +593 ) +594 return self.note_create(NoteData) +595 +596 def NoteComment(self, note_id: int, comment: str) -> dict[str, Any]: +597 """.. deprecated:: Use :meth:`note_comment` instead.""" +598 warnings.warn( +599 "NoteComment() is deprecated, use note_comment() instead", +600 DeprecationWarning, +601 stacklevel=2, +602 ) +603 return self.note_comment(note_id, comment) +604 +605 def NoteClose(self, note_id: int, comment: Optional[str] = None) -> dict[str, Any]: +606 """.. deprecated:: Use :meth:`note_close` instead.""" +607 warnings.warn( +608 "NoteClose() is deprecated, use note_close() instead", +609 DeprecationWarning, +610 stacklevel=2, +611 ) +612 return self.note_close(note_id, comment) +613 +614 def NoteReopen(self, note_id: int, comment: Optional[str] = None) -> dict[str, Any]: +615 """.. deprecated:: Use :meth:`note_reopen` instead.""" +616 warnings.warn( +617 "NoteReopen() is deprecated, use note_reopen() instead", +618 DeprecationWarning, +619 stacklevel=2, +620 ) +621 return self.note_reopen(note_id, comment) +622 +623 def NotesSearch( +624 self, query: str, limit: int = 100, closed: int = 7 +625 ) -> list[dict[str, Any]]: +626 """.. deprecated:: Use :meth:`notes_search` instead.""" +627 warnings.warn( +628 "NotesSearch() is deprecated, use notes_search() instead", +629 DeprecationWarning, +630 stacklevel=2, +631 ) +632 return self.notes_search(query, limit, closed) +633 +634 def _NoteAction( +635 self, path: str, comment: Optional[str] = None, optionalAuth: bool = True +636 ) -> dict[str, Any]: +637 """Internal method - calls _note_action.""" +638 return self._note_action(path, comment, optionalAuth) +639 +640 ################################################## +641 # Map - Deprecated CamelCase methods # +642 ################################################## +643 +644 def Map( +645 self, min_lon: float, min_lat: float, max_lon: float, max_lat: float +646 ) -> list[dict[str, Any]]: +647 """.. deprecated:: Use :meth:`map` instead.""" +648 warnings.warn( +649 "Map() is deprecated, use map() instead", +650 DeprecationWarning, +651 stacklevel=2, +652 ) +653 return self.map(min_lon, min_lat, max_lon, max_lat) +654 +655 ################################################## +656 # Internal method # +657 ################################################## +658 +659 def _do( # type: ignore[return-value] # noqa: C901 +660 self, action: str, osm_type: str, osm_data: dict[str, Any] +661 ) -> dict[str, Any]: +662 if not self._current_changeset_id: +663 raise errors.NoChangesetOpenError( +664 "You need to open a changeset before uploading data" +665 ) +666 if "timestamp" in osm_data: +667 osm_data.pop("timestamp") +668 osm_data["changeset"] = self._current_changeset_id +669 if action == "create": +670 if osm_data.get("id", -1) > 0: +671 raise errors.OsmTypeAlreadyExistsError( +672 f"This {osm_type} already exists" +673 ) +674 try: +675 result = self._session._put( +676 f"/api/0.6/{osm_type}/create", +677 xmlbuilder._xml_build(osm_type, osm_data, data=self), +678 ) +679 except errors.ApiError as e: +680 if e.status == 409 and re.search( +681 r"The changeset .* was closed at .*", e.payload +682 ): +683 raise errors.ChangesetClosedApiError( +684 e.status, e.reason, e.payload +685 ) from e +686 elif e.status == 409: +687 raise errors.VersionMismatchApiError( +688 e.status, e.reason, e.payload +689 ) from e +690 elif e.status == 412: +691 raise errors.PreconditionFailedApiError( +692 e.status, e.reason, e.payload +693 ) from e +694 else: +695 raise +696 osm_data["id"] = int(result.strip()) +697 osm_data["version"] = 1 +698 return osm_data +699 elif action == "modify": +700 try: +701 result = self._session._put( +702 f"/api/0.6/{osm_type}/{osm_data['id']}", +703 xmlbuilder._xml_build(osm_type, osm_data, data=self), +704 ) +705 except errors.ApiError as e: +706 logger.error(e.reason) +707 if e.status == 409 and re.search( +708 r"The changeset .* was closed at .*", e.payload +709 ): +710 raise errors.ChangesetClosedApiError( +711 e.status, e.reason, e.payload +712 ) from e +713 elif e.status == 409: +714 raise errors.VersionMismatchApiError( +715 e.status, e.reason, e.payload +716 ) from e +717 elif e.status == 412: +718 raise errors.PreconditionFailedApiError( +719 e.status, e.reason, e.payload +720 ) from e +721 else: +722 raise +723 osm_data["version"] = int(result.strip()) +724 return osm_data +725 elif action == "delete": +726 try: +727 result = self._session._delete( +728 f"/api/0.6/{osm_type}/{osm_data['id']}", +729 xmlbuilder._xml_build(osm_type, osm_data, data=self), +730 ) +731 except errors.ApiError as e: +732 if e.status == 409 and re.search( +733 r"The changeset .* was closed at .*", e.payload +734 ): +735 raise errors.ChangesetClosedApiError( +736 e.status, e.reason, e.payload +737 ) from e +738 elif e.status == 409: +739 raise errors.VersionMismatchApiError( +740 e.status, e.reason, e.payload +741 ) from e +742 elif e.status == 412: +743 raise errors.PreconditionFailedApiError( +744 e.status, e.reason, e.payload +745 ) from e +746 else: +747 raise +748 osm_data["version"] = int(result.strip()) +749 osm_data["visible"] = False +750 return osm_data +751 +752 def _add_changeset_data(self, change_data: list[dict[str, Any]], type: str) -> str: +753 data = "" +754 for changed_element in change_data: +755 changed_element["changeset"] = self._current_changeset_id +756 xml_data = xmlbuilder._xml_build(type, changed_element, False, data=self) +757 data += xml_data.decode("utf-8") +758 return data +759 +760 def _assign_id_and_version( +761 self, response_data: list[Element], request_data: list[dict[str, Any]] +762 ) -> None: +763 for response, element in zip(response_data, request_data): +764 element["id"] = int(response.getAttribute("new_id")) +765 element["version"] = int(response.getAttribute("new_version")) +
class OsmApi: - """ - Main class of osmapi, instanciate this class to use osmapi - """ - - def __init__( - self, - username=None, - password=None, - passwordfile=None, - appid="", - created_by=f"osmapi/{__version__}", - api="https://www.openstreetmap.org", - changesetauto=False, - changesetautotags={}, - changesetautosize=500, - changesetautomulti=1, - session=None, - timeout=30, - ): - """ - Initialized the OsmApi object. - - There are two different ways to authenticate a user. - Either `username` and `password` are supplied directly or the path - to a `passwordfile` is given, where on the first line username - and password must be colon-separated (<user>:<pass>). - - To credit the application that supplies changes to OSM, an `appid` - can be provided. This is a string identifying the application. - If this is omitted "osmapi" is used. - - It is possible to configure the URL to connect to using the `api` - parameter. By default this is the SSL version of the production API - of OpenStreetMap, for testing purposes, one might prefer the official - test instance at "api06.dev.openstreetmap.org" or any other valid - OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' - in front of the hostname of the `api` parameter (e.g. - https://api.openstreetmap.com). - - There are several options to control the changeset behaviour. By - default, a programmer has to take care to open and close a changeset - prior to make changes to OSM. - By setting `changesetauto` to `True`, osmapi automatically opens - changesets. - The `changesetautotags` parameter takes a `dict`, where each key/value - pair is applied as tags to the changeset. - The option `changesetautosize` defines the size of each - 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 - self._username = None - if username: - self._username = username - elif passwordfile: - with open(passwordfile) as f: - pass_line = f.readline() - self._username = pass_line.partition(":")[0].strip() - - # Get password - self._password = None - if password: - self._password = password - elif passwordfile: - with open(passwordfile) as f: - for line in f: - key, _, value = line.strip().partition(":") - if key == self._username: - self._password = value - - # Changest informations - # auto create and close changesets - self._changesetauto = changesetauto - # tags for automatic created changesets - self._changesetautotags = changesetautotags - # change count for auto changeset - self._changesetautosize = changesetautosize - # change count for auto changeset - self._changesetautosize = changesetautosize - # close a changeset every # upload - self._changesetautomulti = changesetautomulti - self._changesetautocpt = 0 - # data to upload for auto group - self._changesetautodata = [] - - # Get API - self._api = api.strip("/") - - # Get created_by - if not appid: - self._created_by = created_by - else: - self._created_by = f"{appid} ({created_by})" - - # Initialisation - self._CurrentChangesetId = 0 - - # 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, - timeout=self._timeout, - ) - - def __enter__(self): - self._session = http.OsmApiSession( - self._api, - self._created_by, - session=self.http_session, - timeout=self._timeout, - ) - return self - - def __exit__(self, *args): - self.close() - - def close(self): - try: - if self._changesetauto: - self._changesetautoflush(True) - except errors.ResponseEmptyApiError: - pass - - if self._session: - self._session.close() - - ################################################## - # Capabilities # - ################################################## - - def Capabilities(self): - """ - Returns the API capabilities as a dict: - - #!python - { - 'area': { - 'maximum': area in square degrees that can be queried, - }, - 'changesets': { - 'maximum_elements': number of elements per changeset, - }, - 'status': { - 'api': online|readonly|offline, - 'database': online|readonly|offline, - 'gpx': online|readonly|offline, - }, - 'timeout': { - 'seconds': timeout in seconds for API calls, - }, - 'tracepoints': { - 'per_page': maximum number of points in a GPX track, - }, - 'version': { - 'maximum': maximum version of API this server supports, - 'minimum': minimum version of API this server supports, - }, - 'waynodes': { - 'maximum': maximum number of nodes that a way may contain, - }, - } - - The capabilities can be used by a client to - gain insights of the server in use. - """ - uri = "/api/capabilities" - data = self._session._get(uri) - - data = dom.OsmResponseToDom(data, tag="api", single=True) - result = {} - for elem in data.childNodes: - if elem.nodeType != elem.ELEMENT_NODE: - continue - result[elem.nodeName] = {} - for k, v in elem.attributes.items(): - try: - result[elem.nodeName][k] = float(v) - except Exception: - result[elem.nodeName][k] = v - return result - - ################################################## - # Node # - ################################################## - - def NodeGet(self, NodeId, NodeVersion=-1): - """ - Returns node with `NodeId` as a dict: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `NodeVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/node/{NodeId}" - if NodeVersion != -1: - uri += f"/{NodeVersion}" - data = self._session._get(uri) - data = dom.OsmResponseToDom(data, tag="node", single=True) - return dom.DomParseNode(data) - - def NodeCreate(self, NodeData): - """ - Creates a node based on the supplied `NodeData` dict: - - #!python - { - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def NodeUpdate(self, NodeData): - """ - Updates node with the supplied `NodeData` dict: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - 'version': version number of node, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def NodeDelete(self, NodeData): - """ - Delete node with `NodeData`: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'version': version number of node, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "node", NodeData) - - def NodeHistory(self, NodeId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of NodeData, - '2': dict of NodeData, - ... - } - - `NodeId` is the unique identifier of a node. - """ - uri = f"/api/0.6/node/{NodeId}/history" - data = self._session._get(uri) - nodes = dom.OsmResponseToDom(data, tag="node") - result = {} - for node in nodes: - data = dom.DomParseNode(node) - result[data["version"]] = data - return result - - def NodeWays(self, NodeId): - """ - Returns a list of dicts of `WayData` containing node `NodeId`: - - #!python - [ - { - 'id': id of Way, - 'nd': [] list of NodeIds in this way - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `NodeId` is a unique identifier for a node. - """ - uri = f"/api/0.6/node/{NodeId}/ways" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way", allow_empty=True) - result = [] - for way in ways: - data = dom.DomParseWay(way) - result.append(data) - return result - - def NodeRelations(self, NodeId): - """ - Returns a list of dicts of `RelationData` containing node `NodeId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {}, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `NodeId` is a unique identifier for a node. - """ - uri = f"/api/0.6/node/{NodeId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result - - def NodesGet(self, NodeIdList): - """ - Returns dict with the id of the Node as a key - for each node in `NodeIdList`: - - #!python - { - '1234': dict of NodeData, - '5678': dict of NodeData, - ... - } - - `NodeIdList` is a list containing unique identifiers - for multiple nodes. - """ - node_list = ",".join([str(x) for x in NodeIdList]) - uri = f"/api/0.6/nodes?nodes={node_list}" - data = self._session._get(uri) - nodes = dom.OsmResponseToDom(data, tag="node") - result = {} - for node in nodes: - data = dom.DomParseNode(node) - result[data["id"]] = data - return result - - ################################################## - # Way # - ################################################## - - def WayGet(self, WayId, WayVersion=-1): - """ - Returns way with `WayId` as a dict: - - #!python - { - 'id': id of way, - 'tag': {} tags of this way, - 'nd': [] list of nodes belonging to this way - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `WayVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/way/{WayId}" - if WayVersion != -1: - uri += f"/{WayVersion}" - data = self._session._get(uri) - way = dom.OsmResponseToDom(data, tag="way", single=True) - return dom.DomParseWay(way) - - def WayCreate(self, WayData): - """ - Creates a way based on the supplied `WayData` dict: - - #!python - { - 'nd': [] list of nodes, - 'tag': {} dict of tags, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the supplied information contain an existing node, - `OsmApi.OsmTypeAlreadyExistsError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def WayUpdate(self, WayData): - """ - Updates way with the supplied `WayData` dict: - - #!python - { - 'id': id of way, - 'nd': [] list of nodes, - 'tag': {}, - 'version': version number of way, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def WayDelete(self, WayData): - """ - Delete way with `WayData`: - - #!python - { - 'id': id of way, - 'nd': [] list of nodes, - 'tag': dict of tags, - 'version': version number of way, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "way", WayData) - - def WayHistory(self, WayId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of WayData, - '2': dict of WayData, - ... - } - - `WayId` is the unique identifier of a way. - """ - uri = f"/api/0.6/way/{WayId}/history" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way") - result = {} - for way in ways: - data = dom.DomParseWay(way) - result[data["version"]] = data - return result - - def WayRelations(self, WayId): - """ - Returns a list of dicts of `RelationData` containing way `WayId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `WayId` is a unique identifier for a way. - """ - uri = f"/api/0.6/way/{WayId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result - - def WayFull(self, WayId): - """ - Returns the full data for way `WayId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `WayId` is a unique identifier for a way. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/way/{WayId}/full" - data = self._session._get(uri) - return parser.ParseOsm(data) - - def WaysGet(self, WayIdList): - """ - Returns dict with the id of the way as a key for - each way in `WayIdList`: - - #!python - { - '1234': dict of WayData, - '5678': dict of WayData, - ... - } - - `WayIdList` is a list containing unique identifiers for multiple ways. - """ - way_list = ",".join([str(x) for x in WayIdList]) - uri = f"/api/0.6/ways?ways={way_list}" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way") - result = {} - for way in ways: - data = dom.DomParseWay(way) - result[data["id"]] = data - return result - - ################################################## - # Relation # - ################################################## - - def RelationGet(self, RelationId, RelationVersion=-1): - """ - Returns relation with `RelationId` as a dict: - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `RelationVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/relation/{RelationId}" - if RelationVersion != -1: - uri += f"/{RelationVersion}" - data = self._session._get(uri) - relation = dom.OsmResponseToDom(data, tag="relation", single=True) - return dom.DomParseRelation(relation) - - def RelationCreate(self, RelationData): - """ - Creates a relation based on the supplied `RelationData` dict: - - #!python - { - 'member': [] list of members, - 'tag': {} dict of tags, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the supplied information contain an existing node, - `OsmApi.OsmTypeAlreadyExistsError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def RelationUpdate(self, RelationData): - """ - Updates relation with the supplied `RelationData` dict: - - #!python - { - 'id': id of relation, - 'member': [] list of member dicts, - 'tag': {}, - 'version': version number of relation, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) - - def RelationDelete(self, RelationData): - """ - Delete relation with `RelationData` dict: - - #!python - { - 'id': id of relation, - 'member': [] list of member dicts, - 'tag': {}, - 'version': version number of relation, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "relation", RelationData) - - def RelationHistory(self, RelationId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of RelationData, - '2': dict of RelationData, - ... - } - - `RelationId` is the unique identifier of a relation. - """ - uri = f"/api/0.6/relation/{RelationId}/history" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation") - result = {} - for relation in relations: - data = dom.DomParseRelation(relation) - result[data["version"]] = data - return result - - def RelationRelations(self, RelationId): - """ - Returns a list of dicts of `RelationData` - containing relation `RelationId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `RelationId` is a unique identifier for a relation. - """ - uri = f"/api/0.6/relation/{RelationId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result - - def RelationFullRecur(self, RelationId): - """ - Returns the full data (all levels) for relation - `RelationId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `RelationId` is a unique identifier for a way. - - This function is useful for relations containing other relations. - - If you don't need all levels, use `OsmApi.RelationFull` - instead, which return only 2 levels. - - If any relation (on any level) has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - data = [] - todo = [RelationId] - done = [] - while todo: - rid = todo.pop(0) - done.append(rid) - temp = self.RelationFull(rid) - for item in temp: - if item["type"] != "relation": - continue - if item["data"]["id"] in done: - continue - todo.append(item["data"]["id"]) - data += temp - return data - - def RelationFull(self, RelationId): - """ - Returns the full data (two levels) for relation - `RelationId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `RelationId` is a unique identifier for a way. - - If you need all levels, use `OsmApi.RelationFullRecur`. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/relation/{RelationId}/full" - data = self._session._get(uri) - return parser.ParseOsm(data) - - def RelationsGet(self, RelationIdList): - """ - Returns dict with the id of the relation as a key - for each relation in `RelationIdList`: - - #!python - { - '1234': dict of RelationData, - '5678': dict of RelationData, - ... - } - - `RelationIdList` is a list containing unique identifiers - for multiple relations. - """ - relation_list = ",".join([str(x) for x in RelationIdList]) - uri = f"/api/0.6/relations?relations={relation_list}" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation") - result = {} - for relation in relations: - data = dom.DomParseRelation(relation) - result[data["id"]] = data - return result - - ################################################## - # Changeset # - ################################################## - - @contextmanager - def Changeset(self, ChangesetTags={}): - """ - Context manager for a Changeset. - - It opens a Changeset, uploads the changes and closes the changeset - when used with the `with` statement: - - #!python - import osmapi - - with osmapi.Changeset({"comment": "Import script XYZ"}) as changeset_id: - print(f"Part of changeset {changeset_id}") - api.NodeCreate({"lon":1, "lat":1, "tag": {}}) - - If `ChangesetTags` are given, this tags are applied (key/value). - - Returns `ChangesetId` - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - # Create a new changeset - changeset_id = self.ChangesetCreate(ChangesetTags) - yield changeset_id - - # upload data to changeset - autosize = self._changesetautosize - for i in range(0, len(self._changesetautodata), autosize): - chunk = self._changesetautodata[i : i + autosize] - self.ChangesetUpload(chunk) - self._changesetautodata = [] - self.ChangesetClose() - - def ChangesetGet(self, ChangesetId, include_discussion=False): - """ - Returns changeset with `ChangesetId` as a dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'discussion': [] list of comment dict (-> `include_discussion`) - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - `ChangesetId` is the unique identifier of a changeset. - - If `include_discussion` is set to `True` the changeset discussion - will be available in the result. - """ - path = f"/api/0.6/changeset/{ChangesetId}" - if include_discussion: - path = f"{path}?include_discussion=true" - data = self._session._get(path) - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset, include_discussion=include_discussion) - - def ChangesetUpdate(self, ChangesetTags={}): - """ - Updates current changeset with `ChangesetTags`. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - 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 errors.NoChangesetOpenError("No changeset currently opened") - if "created_by" not in ChangesetTags: - ChangesetTags["created_by"] = self._created_by - try: - self._session._put( - f"/api/0.6/changeset/{self._CurrentChangesetId}", - xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self), - return_value=False, - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - return self._CurrentChangesetId - - def ChangesetCreate(self, ChangesetTags={}): - """ - Opens a changeset. - - If `ChangesetTags` are given, this tags are applied (key/value). - - Returns `ChangesetId` - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - if self._CurrentChangesetId: - raise errors.ChangesetAlreadyOpenError("Changeset already opened") - if "created_by" not in ChangesetTags: - ChangesetTags["created_by"] = self._created_by - - # check if someone tries to create a test changeset to PROD - if ( - self._api == "https://www.openstreetmap.org" - and ChangesetTags.get("comment") == "My first test" - ): - raise errors.OsmApiError( - "DO NOT CREATE test changesets on the production server" - ) - - result = self._session._put( - "/api/0.6/changeset/create", - xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self), - ) - self._CurrentChangesetId = int(result) - return self._CurrentChangesetId - - def ChangesetClose(self): - """ - Closes current changeset. - - Returns `ChangesetId`. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - 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 errors.NoChangesetOpenError("No changeset currently opened") - try: - self._session._put( - f"/api/0.6/changeset/{self._CurrentChangesetId}/close", - "", - return_value=False, - ) - CurrentChangesetId = self._CurrentChangesetId - self._CurrentChangesetId = 0 - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - return CurrentChangesetId - - def ChangesetUpload(self, ChangesData): - """ - Upload data with the `ChangesData` list of dicts: - - #!python - { - type: node|way|relation, - action: create|delete|modify, - data: {} - } - - Returns list with updated ids. - - 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' - data += '<osmChange version="0.6" generator="' - data += self._created_by + '">\n' - for change in ChangesData: - data += "<" + change["action"] + ">\n" - changeData = change["data"] - data += self._add_changeset_data(changeData, change["type"]) - data += "</" + change["action"] + ">\n" - data += "</osmChange>" - try: - data = self._session._post( - f"/api/0.6/changeset/{self._CurrentChangesetId}/upload", - data.encode("utf-8"), - forceAuth=True, - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("diffResult")[0] - data = [x for x in data.childNodes if x.nodeType == x.ELEMENT_NODE] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - for change in ChangesData: - if change["action"] == "delete": - for changeElement in change["data"]: - changeElement.pop("version") - else: - self._assign_id_and_version(data, change["data"]) - - return ChangesData - - def ChangesetDownload(self, ChangesetId): - """ - Download data from changeset `ChangesetId`. - - Returns list of dict: - - #!python - { - 'type': node|way|relation, - 'action': create|delete|modify, - 'data': {} - } - """ - uri = f"/api/0.6/changeset/{ChangesetId}/download" - data = self._session._get(uri) - return parser.ParseOsc(data) - - def ChangesetsGet( # noqa - self, - min_lon=None, - min_lat=None, - max_lon=None, - max_lat=None, - userid=None, - username=None, - closed_after=None, - created_before=None, - only_open=False, - only_closed=False, - ): - """ - Returns a dict with the id of the changeset as key - matching all criteria: - - #!python - { - '1234': dict of ChangesetData, - '5678': dict of ChangesetData, - ... - } - - All parameters are optional. - """ - - uri = "/api/0.6/changesets" - params = {} - if min_lon or min_lat or max_lon or max_lat: - params["bbox"] = f"{min_lon},{min_lat},{max_lon},{max_lat}" - if userid: - params["user"] = userid - if username: - params["display_name"] = username - if closed_after and not created_before: - params["time"] = closed_after - if created_before: - if not closed_after: - closed_after = "1970-01-01T00:00:00Z" - params["time"] = f"{closed_after},{created_before}" - if only_open: - params["open"] = 1 - if only_closed: - params["closed"] = 1 - - if params: - uri += "?" + urllib.parse.urlencode(params) - - data = self._session._get(uri) - changesets = dom.OsmResponseToDom(data, tag="changeset") - result = {} - for curChangeset in changesets: - tmpCS = dom.DomParseChangeset(curChangeset) - result[tmpCS["id"]] = tmpCS - return result - - def ChangesetComment(self, ChangesetId, comment): - """ - Adds a comment to the changeset `ChangesetId` - - `comment` should be a string. - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the changeset is already closed, - `OsmApi.ChangesetClosedApiError` is raised. - """ - params = urllib.parse.urlencode({"text": comment}) - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/comment", params, forceAuth=True - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) - - def ChangesetSubscribe(self, ChangesetId): - """ - Subcribe to the changeset discussion of changeset `ChangesetId`. - - The user will be informed about new comments (i.e. receive an email). - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/subscribe", None, forceAuth=True - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.AlreadySubscribedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) - - def ChangesetUnsubscribe(self, ChangesetId): - """ - Subcribe to the changeset discussion of changeset `ChangesetId`. - - The user will be informed about new comments (i.e. receive an email). - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/unsubscribe", None, forceAuth=True - ) - except errors.ElementNotFoundApiError as e: - raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e - - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) - - ################################################## - # Notes # - ################################################## - - def NotesGet(self, min_lon, min_lat, max_lon, max_lat, limit=100, closed=7): - """ - Returns a list of dicts of notes in the specified bounding box: - - #!python - [ - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - }, - { ... } - ] - - The limit parameter defines how many results should be returned. - - closed specifies the number of days a bug needs to be closed - to no longer be returned. - The value 0 means only open bugs are returned, - -1 means all bugs are returned. - - All parameters are optional. - """ - uri = ( - f"/api/0.6/notes?bbox=" - f"{min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" - f"&limit={limit}&closed={closed}" - ) - data = self._session._get(uri) - return parser.ParseNotes(data) - - def NoteGet(self, id): - """ - Returns a note as dict: - - #!python - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - } - - `id` is the unique identifier of the note. - """ - uri = f"/api/0.6/notes/{id}" - data = self._session._get(uri) - noteElement = dom.OsmResponseToDom(data, tag="note", single=True) - return dom.DomParseNote(noteElement) - - def NoteCreate(self, NoteData): - """ - Creates a note based on the supplied `NoteData` dict: - - #!python - { - 'lat': latitude of note, - 'lon': longitude of note, - 'text': text of the note, - } - - Returns updated `NoteData`: - - #!python - { - 'id': id of note, - 'lat': latitude of note, - 'lon': longitude of note, - 'date_created': date when the note was created - 'date_closed': date when the note was closed or None if it's open, - 'status': status of the note (open or closed), - 'comments': [ - { - 'date': date of the comment, - 'action': status of comment (opened, commented, closed), - 'text': text of the note, - 'html': html version of the text of the note, - 'uid': user id of the user creating this note or None - 'user': username of the user creating this note or None - } - ] - } - - """ - uri = "/api/0.6/notes" - uri += "?" + urllib.parse.urlencode(NoteData) - return self._NoteAction(uri) - - def NoteComment(self, NoteId, comment): - """ - Adds a new comment to a note. - - Returns the updated note. - """ - path = f"/api/0.6/notes/{NoteId}/comment" - return self._NoteAction(path, comment) - - def NoteClose(self, NoteId, comment): - """ - Closes a note. - - Returns the updated note. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - path = f"/api/0.6/notes/{NoteId}/close" - return self._NoteAction(path, comment, optionalAuth=False) - - def NoteReopen(self, NoteId, comment): - """ - Reopens a note. - - Returns the updated note. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - path = f"/api/0.6/notes/{NoteId}/reopen" - return self._NoteAction(path, comment, optionalAuth=False) - - def NotesSearch(self, query, limit=100, closed=7): - """ - Returns a list of dicts of notes that match the given search query. - - The limit parameter defines how many results should be returned. - - closed specifies the number of days a bug needs to be closed - to no longer be returned. - The value 0 means only open bugs are returned, - -1 means all bugs are returned. - """ - uri = "/api/0.6/notes/search" - params = {} - params["q"] = query - params["limit"] = limit - params["closed"] = closed - uri += "?" + urllib.parse.urlencode(params) - data = self._session._get(uri) - - return parser.ParseNotes(data) - - def _NoteAction(self, path, comment=None, optionalAuth=True): - """ - Performs an action on a Note with a comment - - Return the updated note - """ - uri = path - if comment is not None: - params = {} - params["text"] = comment - uri += "?" + urllib.parse.urlencode(params) - try: - result = self._session._post(uri, None, optionalAuth=optionalAuth) - except errors.ApiError as e: - if e.status == 409: - raise errors.NoteAlreadyClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - - # parse the result - noteElement = dom.OsmResponseToDom(result, tag="note", single=True) - return dom.DomParseNote(noteElement) - - ################################################## - # Other # - ################################################## - - def Map(self, min_lon, min_lat, max_lon, max_lat): - """ - Download data in bounding box. - - Returns list of dict: - - #!python - { - type: node|way|relation, - data: {} - } - """ - uri = f"/api/0.6/map?bbox={min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" - data = self._session._get(uri) - return parser.ParseOsm(data) - - def flush(self): - """ - Force the changes to be uploaded to OSM and the changeset to be closed - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - return self._changesetautoflush(True) - - ################################################## - # Internal method # - ################################################## - - def _do(self, action, OsmType, OsmData): - if self._changesetauto: - self._changesetautodata.append( - {"action": action, "type": OsmType, "data": OsmData} - ) - self._changesetautoflush() - return None - else: - return self._do_manu(action, OsmType, OsmData) - - def _do_manu(self, action, OsmType, OsmData): # noqa - if not self._CurrentChangesetId: - raise errors.NoChangesetOpenError( - "You need to open a changeset before uploading data" - ) - if "timestamp" in OsmData: - OsmData.pop("timestamp") - OsmData["changeset"] = self._CurrentChangesetId - if action == "create": - if OsmData.get("id", -1) > 0: - raise errors.OsmTypeAlreadyExistsError(f"This {OsmType} already exists") - try: - result = self._session._put( - f"/api/0.6/{OsmType}/create", - xmlbuilder._XmlBuild(OsmType, OsmData, data=self), - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 409: - raise errors.VersionMismatchApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 412: - raise errors.PreconditionFailedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - OsmData["id"] = int(result.strip()) - OsmData["version"] = 1 - return OsmData - elif action == "modify": - try: - result = self._session._put( - f"/api/0.6/{OsmType}/{OsmData['id']}", - xmlbuilder._XmlBuild(OsmType, OsmData, data=self), - ) - except errors.ApiError as e: - logger.error(e.reason) - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 409: - raise errors.VersionMismatchApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 412: - raise errors.PreconditionFailedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - OsmData["version"] = int(result.strip()) - return OsmData - elif action == "delete": - try: - result = self._session._delete( - f"/api/0.6/{OsmType}/{OsmData['id']}", - xmlbuilder._XmlBuild(OsmType, OsmData, data=self), - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 409: - raise errors.VersionMismatchApiError( - e.status, e.reason, e.payload - ) from e - elif e.status == 412: - raise errors.PreconditionFailedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - OsmData["version"] = int(result.strip()) - OsmData["visible"] = False - return OsmData - - def _changesetautoflush(self, force=False): - autosize = self._changesetautosize - while (len(self._changesetautodata) >= autosize) or ( - force and self._changesetautodata - ): - if self._changesetautocpt == 0: - self.ChangesetCreate(self._changesetautotags) - self.ChangesetUpload(self._changesetautodata[:autosize]) - self._changesetautodata = self._changesetautodata[autosize:] - self._changesetautocpt += 1 - if self._changesetautocpt == self._changesetautomulti: - self.ChangesetClose() - self._changesetautocpt = 0 - if self._changesetautocpt and force: - 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")) -
Main class of osmapi, instanciate this class to use osmapi
64 def __init__( + 65 self, + 66 username: Optional[str] = None, + 67 password: Optional[str] = None, + 68 passwordfile: Optional[str] = None, + 69 appid: str = "", + 70 created_by: str = f"osmapi/{__version__}", + 71 api: str = "https://www.openstreetmap.org", + 72 session: Optional[requests.Session] = None, + 73 timeout: int = 30, + 74 ) -> None: + 75 """ + 76 Initialized the OsmApi object. + 77 + 78 There are two different ways to authenticate a user. + 79 Either `username` and `password` are supplied directly or the path + 80 to a `passwordfile` is given, where on the first line username + 81 and password must be colon-separated (<user>:<pass>). + 82 + 83 To credit the application that supplies changes to OSM, an `appid` + 84 can be provided. This is a string identifying the application. + 85 If this is omitted "osmapi" is used. + 86 + 87 It is possible to configure the URL to connect to using the `api` + 88 parameter. By default this is the SSL version of the production API + 89 of OpenStreetMap, for testing purposes, one might prefer the official + 90 test instance at "api06.dev.openstreetmap.org" or any other valid + 91 OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' + 92 in front of the hostname of the `api` parameter (e.g. + 93 https://api.openstreetmap.com). + 94 + 95 The `session` parameter can be used to provide a custom requests + 96 http session object (requests.Session). This might be useful for + 97 OAuth authentication, custom adapters, hooks etc. + 98 + 99 Finally the `timeout` parameter is used by the http session to +100 throw an expcetion if the the timeout (in seconds) has passed without +101 an answer from the server. +102 """ +103 # Get username +104 self._username: Optional[str] = None +105 if username: +106 self._username = username +107 elif passwordfile: +108 with open(passwordfile) as f: +109 pass_line = f.readline() +110 self._username = pass_line.partition(":")[0].strip() +111 +112 # Get password +113 self._password: Optional[str] = None +114 if password: +115 self._password = password +116 elif passwordfile: +117 with open(passwordfile) as f: +118 for line in f: +119 key, _, value = line.strip().partition(":") +120 if key == self._username: +121 self._password = value +122 +123 # Get API +124 self._api: str = api.strip("/") +125 +126 # Get created_by +127 if not appid: +128 self._created_by: str = created_by +129 else: +130 self._created_by = f"{appid} ({created_by})" +131 +132 # Initialisation +133 self._current_changeset_id: int = 0 +134 +135 # Http connection +136 self.http_session: Optional[requests.Session] = session +137 self._timeout: int = timeout +138 auth: Optional[tuple[str, str]] = None +139 if self._username and self._password: +140 auth = (self._username, self._password) +141 self._session: http.OsmApiSession = http.OsmApiSession( +142 self._api, +143 self._created_by, +144 auth=auth, +145 session=self.http_session, +146 timeout=self._timeout, +147 ) +
def __init__( - self, - username=None, - password=None, - passwordfile=None, - appid="", - created_by=f"osmapi/{__version__}", - api="https://www.openstreetmap.org", - changesetauto=False, - changesetautotags={}, - changesetautosize=500, - changesetautomulti=1, - session=None, - timeout=30, - ): - """ - Initialized the OsmApi object. - - There are two different ways to authenticate a user. - Either `username` and `password` are supplied directly or the path - to a `passwordfile` is given, where on the first line username - and password must be colon-separated (<user>:<pass>). - - To credit the application that supplies changes to OSM, an `appid` - can be provided. This is a string identifying the application. - If this is omitted "osmapi" is used. - - It is possible to configure the URL to connect to using the `api` - parameter. By default this is the SSL version of the production API - of OpenStreetMap, for testing purposes, one might prefer the official - test instance at "api06.dev.openstreetmap.org" or any other valid - OSM-API. To use an encrypted connection (HTTPS) simply add 'https://' - in front of the hostname of the `api` parameter (e.g. - https://api.openstreetmap.com). - - There are several options to control the changeset behaviour. By - default, a programmer has to take care to open and close a changeset - prior to make changes to OSM. - By setting `changesetauto` to `True`, osmapi automatically opens - changesets. - The `changesetautotags` parameter takes a `dict`, where each key/value - pair is applied as tags to the changeset. - The option `changesetautosize` defines the size of each - 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 - self._username = None - if username: - self._username = username - elif passwordfile: - with open(passwordfile) as f: - pass_line = f.readline() - self._username = pass_line.partition(":")[0].strip() - - # Get password - self._password = None - if password: - self._password = password - elif passwordfile: - with open(passwordfile) as f: - for line in f: - key, _, value = line.strip().partition(":") - if key == self._username: - self._password = value - - # Changest informations - # auto create and close changesets - self._changesetauto = changesetauto - # tags for automatic created changesets - self._changesetautotags = changesetautotags - # change count for auto changeset - self._changesetautosize = changesetautosize - # change count for auto changeset - self._changesetautosize = changesetautosize - # close a changeset every # upload - self._changesetautomulti = changesetautomulti - self._changesetautocpt = 0 - # data to upload for auto group - self._changesetautodata = [] - - # Get API - self._api = api.strip("/") - - # Get created_by - if not appid: - self._created_by = created_by - else: - self._created_by = f"{appid} ({created_by})" - - # Initialisation - self._CurrentChangesetId = 0 - - # 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, - timeout=self._timeout, - ) -
Initialized the OsmApi object.
@@ -4285,18 +1864,6 @@api parameter (e.g.
https://api.openstreetmap.com).
-There are several options to control the changeset behaviour. By
-default, a programmer has to take care to open and close a changeset
-prior to make changes to OSM.
-By setting changesetauto to True, osmapi automatically opens
-changesets.
-The changesetautotags parameter takes a dict, where each key/value
-pair is applied as tags to the changeset.
-The option changesetautosize defines the size of each
-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.
def close(self): - try: - if self._changesetauto: - self._changesetautoflush(True) - except errors.ResponseEmptyApiError: - pass + - if self._session: - self._session.close() -
def Capabilities(self): - """ - Returns the API capabilities as a dict: - - #!python - { - 'area': { - 'maximum': area in square degrees that can be queried, - }, - 'changesets': { - 'maximum_elements': number of elements per changeset, - }, - 'status': { - 'api': online|readonly|offline, - 'database': online|readonly|offline, - 'gpx': online|readonly|offline, - }, - 'timeout': { - 'seconds': timeout in seconds for API calls, - }, - 'tracepoints': { - 'per_page': maximum number of points in a GPX track, - }, - 'version': { - 'maximum': maximum version of API this server supports, - 'minimum': minimum version of API this server supports, - }, - 'waynodes': { - 'maximum': maximum number of nodes that a way may contain, - }, - } - - The capabilities can be used by a client to - gain insights of the server in use. - """ - uri = "/api/capabilities" - data = self._session._get(uri) - - data = dom.OsmResponseToDom(data, tag="api", single=True) - result = {} - for elem in data.childNodes: - if elem.nodeType != elem.ELEMENT_NODE: - continue - result[elem.nodeName] = {} - for k, v in elem.attributes.items(): - try: - result[elem.nodeName][k] = float(v) - except Exception: - result[elem.nodeName][k] = v - return result -
Returns the API capabilities as a dict:
- -#!python
-{
- 'area': {
- 'maximum': area in square degrees that can be queried,
- },
- 'changesets': {
- 'maximum_elements': number of elements per changeset,
- },
- 'status': {
- 'api': online|readonly|offline,
- 'database': online|readonly|offline,
- 'gpx': online|readonly|offline,
- },
- 'timeout': {
- 'seconds': timeout in seconds for API calls,
- },
- 'tracepoints': {
- 'per_page': maximum number of points in a GPX track,
- },
- 'version': {
- 'maximum': maximum version of API this server supports,
- 'minimum': minimum version of API this server supports,
- },
- 'waynodes': {
- 'maximum': maximum number of nodes that a way may contain,
- },
-}
-
+ 169 def Capabilities(self) -> dict[str, dict[str, Any]]: +170 """ +171 Returns the API capabilities as a dict. +172 +173 .. deprecated:: +174 Use :meth:`capabilities` instead. +175 +176 The capabilities can be used by a client to +177 gain insights of the server in use. +178 """ +179 warnings.warn( +180 "Capabilities() is deprecated, use capabilities() instead", +181 DeprecationWarning, +182 stacklevel=2, +183 ) +184 return self.capabilities() +
Returns the API capabilities as a dict.
+ +Deprecated since version :
+Use capabilities() instead.
The capabilities can be used by a client to gain insights of the server in use.
@@ -4436,3348 +1948,1304 @@def NodeGet(self, NodeId, NodeVersion=-1): - """ - Returns node with `NodeId` as a dict: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `NodeVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/node/{NodeId}" - if NodeVersion != -1: - uri += f"/{NodeVersion}" - data = self._session._get(uri) - data = dom.OsmResponseToDom(data, tag="node", single=True) - return dom.DomParseNode(data) -
Returns node with NodeId as a dict:
#!python
-{
- 'id': id of node,
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': {},
- 'changeset': id of changeset of last change,
- 'version': version number of node,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'timestamp': timestamp of last change,
- 'visible': True|False
-}
-
-
-If NodeVersion is supplied, this specific version is returned,
-otherwise the latest version is returned.
If the requested element has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
190 def NodeGet(self, NodeId: int, NodeVersion: int = -1) -> dict[str, Any]: +191 """.. deprecated:: Use :meth:`node_get` instead.""" +192 warnings.warn( +193 "NodeGet() is deprecated, use node_get() instead", +194 DeprecationWarning, +195 stacklevel=2, +196 ) +197 return self.node_get(NodeId, NodeVersion) +
Deprecated since version Use node_get() instead..
def NodeCreate(self, NodeData): - """ - Creates a node based on the supplied `NodeData` dict: - - #!python - { - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) -
Creates a node based on the supplied NodeData dict:
#!python
-{
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': {},
-}
-
-
-Returns updated NodeData (without timestamp):
#!python
-{
- 'id': id of node,
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of node,
- 'user': username of last change,
- 'uid': id of user of last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If the supplied information contain an existing node,
-OsmApi.OsmTypeAlreadyExistsError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
199 def NodeCreate(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +200 """.. deprecated:: Use :meth:`node_create` instead.""" +201 warnings.warn( +202 "NodeCreate() is deprecated, use node_create() instead", +203 DeprecationWarning, +204 stacklevel=2, +205 ) +206 return self.node_create(NodeData) +
Deprecated since version Use node_create() instead..
def NodeUpdate(self, NodeData): - """ - Updates node with the supplied `NodeData` dict: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': {}, - 'version': version number of node, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) -
Updates node with the supplied NodeData dict:
#!python
-{
- 'id': id of node,
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': {},
- 'version': version number of node,
-}
-
-
-Returns updated NodeData (without timestamp):
#!python
-{
- 'id': id of node,
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of node,
- 'user': username of last change,
- 'uid': id of user of last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
208 def NodeUpdate(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +209 """.. deprecated:: Use :meth:`node_update` instead.""" +210 warnings.warn( +211 "NodeUpdate() is deprecated, use node_update() instead", +212 DeprecationWarning, +213 stacklevel=2, +214 ) +215 return self.node_update(NodeData) +
Deprecated since version Use node_update() instead..
def NodeDelete(self, NodeData): - """ - Delete node with `NodeData`: - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'version': version number of node, - } - - Returns updated `NodeData` (without timestamp): - - #!python - { - 'id': id of node, - 'lat': latitude of node, - 'lon': longitude of node, - 'tag': dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of node, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "node", NodeData) -
Delete node with NodeData:
#!python
-{
- 'id': id of node,
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': dict of tags,
- 'version': version number of node,
-}
-
-
-Returns updated NodeData (without timestamp):
#!python
-{
- 'id': id of node,
- 'lat': latitude of node,
- 'lon': longitude of node,
- 'tag': dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of node,
- 'user': username of last change,
- 'uid': id of user of last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
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.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
217 def NodeDelete(self, NodeData: dict[str, Any]) -> Optional[dict[str, Any]]: +218 """.. deprecated:: Use :meth:`node_delete` instead.""" +219 warnings.warn( +220 "NodeDelete() is deprecated, use node_delete() instead", +221 DeprecationWarning, +222 stacklevel=2, +223 ) +224 return self.node_delete(NodeData) +
Deprecated since version Use node_delete() instead..
def NodeHistory(self, NodeId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of NodeData, - '2': dict of NodeData, - ... - } - - `NodeId` is the unique identifier of a node. - """ - uri = f"/api/0.6/node/{NodeId}/history" - data = self._session._get(uri) - nodes = dom.OsmResponseToDom(data, tag="node") - result = {} - for node in nodes: - data = dom.DomParseNode(node) - result[data["version"]] = data - return result -
Returns dict with version as key:
- -#!python
-{
- '1': dict of NodeData,
- '2': dict of NodeData,
- ...
-}
-
-
-NodeId is the unique identifier of a node.
226 def NodeHistory(self, NodeId: int) -> dict[int, dict[str, Any]]: +227 """.. deprecated:: Use :meth:`node_history` instead.""" +228 warnings.warn( +229 "NodeHistory() is deprecated, use node_history() instead", +230 DeprecationWarning, +231 stacklevel=2, +232 ) +233 return self.node_history(NodeId) +
Deprecated since version Use node_history() instead..
def NodeWays(self, NodeId): - """ - Returns a list of dicts of `WayData` containing node `NodeId`: - - #!python - [ - { - 'id': id of Way, - 'nd': [] list of NodeIds in this way - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `NodeId` is a unique identifier for a node. - """ - uri = f"/api/0.6/node/{NodeId}/ways" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way", allow_empty=True) - result = [] - for way in ways: - data = dom.DomParseWay(way) - result.append(data) - return result -
Returns a list of dicts of WayData containing node NodeId:
#!python
-[
- {
- 'id': id of Way,
- 'nd': [] list of NodeIds in this way
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of Way,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
- },
- {
- ...
- },
-]
-
-
-The NodeId is a unique identifier for a node.
235 def NodeWays(self, NodeId: int) -> list[dict[str, Any]]: +236 """.. deprecated:: Use :meth:`node_ways` instead.""" +237 warnings.warn( +238 "NodeWays() is deprecated, use node_ways() instead", +239 DeprecationWarning, +240 stacklevel=2, +241 ) +242 return self.node_ways(NodeId) +
Deprecated since version Use node_ways() instead..
def NodeRelations(self, NodeId): - """ - Returns a list of dicts of `RelationData` containing node `NodeId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {}, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `NodeId` is a unique identifier for a node. - """ - uri = f"/api/0.6/node/{NodeId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result -
Returns a list of dicts of RelationData containing node NodeId:
#!python
-[
- {
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {},
- 'changeset': id of changeset of last change,
- 'version': version number of Way,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
- },
- {
- ...
- },
-]
-
-
-The NodeId is a unique identifier for a node.
244 def NodeRelations(self, NodeId: int) -> list[dict[str, Any]]: +245 """.. deprecated:: Use :meth:`node_relations` instead.""" +246 warnings.warn( +247 "NodeRelations() is deprecated, use node_relations() instead", +248 DeprecationWarning, +249 stacklevel=2, +250 ) +251 return self.node_relations(NodeId) +
Deprecated since version Use node_relations() instead..
def NodesGet(self, NodeIdList): - """ - Returns dict with the id of the Node as a key - for each node in `NodeIdList`: - - #!python - { - '1234': dict of NodeData, - '5678': dict of NodeData, - ... - } - - `NodeIdList` is a list containing unique identifiers - for multiple nodes. - """ - node_list = ",".join([str(x) for x in NodeIdList]) - uri = f"/api/0.6/nodes?nodes={node_list}" - data = self._session._get(uri) - nodes = dom.OsmResponseToDom(data, tag="node") - result = {} - for node in nodes: - data = dom.DomParseNode(node) - result[data["id"]] = data - return result -
Returns dict with the id of the Node as a key
-for each node in NodeIdList:
#!python
-{
- '1234': dict of NodeData,
- '5678': dict of NodeData,
- ...
-}
-
-
-NodeIdList is a list containing unique identifiers
-for multiple nodes.
253 def NodesGet(self, NodeIdList: list[int]) -> dict[int, dict[str, Any]]: +254 """.. deprecated:: Use :meth:`nodes_get` instead.""" +255 warnings.warn( +256 "NodesGet() is deprecated, use nodes_get() instead", +257 DeprecationWarning, +258 stacklevel=2, +259 ) +260 return self.nodes_get(NodeIdList) +
Deprecated since version Use nodes_get() instead..
def WayGet(self, WayId, WayVersion=-1): - """ - Returns way with `WayId` as a dict: - - #!python - { - 'id': id of way, - 'tag': {} tags of this way, - 'nd': [] list of nodes belonging to this way - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `WayVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/way/{WayId}" - if WayVersion != -1: - uri += f"/{WayVersion}" - data = self._session._get(uri) - way = dom.OsmResponseToDom(data, tag="way", single=True) - return dom.DomParseWay(way) -
Returns way with WayId as a dict:
#!python
-{
- 'id': id of way,
- 'tag': {} tags of this way,
- 'nd': [] list of nodes belonging to this way
- 'changeset': id of changeset of last change,
- 'version': version number of way,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'timestamp': timestamp of last change,
- 'visible': True|False
-}
-
-
-If WayVersion is supplied, this specific version is returned,
-otherwise the latest version is returned.
If the requested element has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
266 def WayGet(self, WayId: int, WayVersion: int = -1) -> dict[str, Any]: +267 """.. deprecated:: Use :meth:`way_get` instead.""" +268 warnings.warn( +269 "WayGet() is deprecated, use way_get() instead", +270 DeprecationWarning, +271 stacklevel=2, +272 ) +273 return self.way_get(WayId, WayVersion) +
Deprecated since version Use way_get() instead..
def WayCreate(self, WayData): - """ - Creates a way based on the supplied `WayData` dict: - - #!python - { - 'nd': [] list of nodes, - 'tag': {} dict of tags, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the supplied information contain an existing node, - `OsmApi.OsmTypeAlreadyExistsError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) -
Creates a way based on the supplied WayData dict:
#!python
-{
- 'nd': [] list of nodes,
- 'tag': {} dict of tags,
-}
-
-
-Returns updated WayData (without timestamp):
#!python
-{
- 'id': id of node,
- 'nd': [] list of nodes,
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of way,
- 'user': username of last change,
- 'uid': id of user of last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing node,
-OsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
275 def WayCreate(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +276 """.. deprecated:: Use :meth:`way_create` instead.""" +277 warnings.warn( +278 "WayCreate() is deprecated, use way_create() instead", +279 DeprecationWarning, +280 stacklevel=2, +281 ) +282 return self.way_create(WayData) +
Deprecated since version Use way_create() instead..
def WayUpdate(self, WayData): - """ - Updates way with the supplied `WayData` dict: - - #!python - { - 'id': id of way, - 'nd': [] list of nodes, - 'tag': {}, - 'version': version number of way, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) -
Updates way with the supplied WayData dict:
#!python
-{
- 'id': id of way,
- 'nd': [] list of nodes,
- 'tag': {},
- 'version': version number of way,
-}
-
-
-Returns updated WayData (without timestamp):
#!python
-{
- 'id': id of node,
- 'nd': [] list of nodes,
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of way,
- 'user': username of last change,
- 'uid': id of user of last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
284 def WayUpdate(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +285 """.. deprecated:: Use :meth:`way_update` instead.""" +286 warnings.warn( +287 "WayUpdate() is deprecated, use way_update() instead", +288 DeprecationWarning, +289 stacklevel=2, +290 ) +291 return self.way_update(WayData) +
Deprecated since version Use way_update() instead..
def WayDelete(self, WayData): - """ - Delete way with `WayData`: - - #!python - { - 'id': id of way, - 'nd': [] list of nodes, - 'tag': dict of tags, - 'version': version number of way, - } - - Returns updated `WayData` (without timestamp): - - #!python - { - 'id': id of node, - 'nd': [] list of nodes, - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of way, - 'user': username of last change, - 'uid': id of user of last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "way", WayData) -
Delete way with WayData:
#!python
-{
- 'id': id of way,
- 'nd': [] list of nodes,
- 'tag': dict of tags,
- 'version': version number of way,
-}
-
-
-Returns updated WayData (without timestamp):
#!python
-{
- 'id': id of node,
- 'nd': [] list of nodes,
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of way,
- 'user': username of last change,
- 'uid': id of user of last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
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.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
293 def WayDelete(self, WayData: dict[str, Any]) -> Optional[dict[str, Any]]: +294 """.. deprecated:: Use :meth:`way_delete` instead.""" +295 warnings.warn( +296 "WayDelete() is deprecated, use way_delete() instead", +297 DeprecationWarning, +298 stacklevel=2, +299 ) +300 return self.way_delete(WayData) +
Deprecated since version Use way_delete() instead..
def WayHistory(self, WayId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of WayData, - '2': dict of WayData, - ... - } - - `WayId` is the unique identifier of a way. - """ - uri = f"/api/0.6/way/{WayId}/history" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way") - result = {} - for way in ways: - data = dom.DomParseWay(way) - result[data["version"]] = data - return result -
Returns dict with version as key:
- -#!python
-{
- '1': dict of WayData,
- '2': dict of WayData,
- ...
-}
-
-
-WayId is the unique identifier of a way.
302 def WayHistory(self, WayId: int) -> dict[int, dict[str, Any]]: +303 """.. deprecated:: Use :meth:`way_history` instead.""" +304 warnings.warn( +305 "WayHistory() is deprecated, use way_history() instead", +306 DeprecationWarning, +307 stacklevel=2, +308 ) +309 return self.way_history(WayId) +
Deprecated since version Use way_history() instead..
def WayRelations(self, WayId): - """ - Returns a list of dicts of `RelationData` containing way `WayId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `WayId` is a unique identifier for a way. - """ - uri = f"/api/0.6/way/{WayId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result -
Returns a list of dicts of RelationData containing way WayId:
#!python
-[
- {
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of Way,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
- },
- {
- ...
- },
-]
-
-
-The WayId is a unique identifier for a way.
311 def WayRelations(self, WayId: int) -> list[dict[str, Any]]: +312 """.. deprecated:: Use :meth:`way_relations` instead.""" +313 warnings.warn( +314 "WayRelations() is deprecated, use way_relations() instead", +315 DeprecationWarning, +316 stacklevel=2, +317 ) +318 return self.way_relations(WayId) +
Deprecated since version Use way_relations() instead..
def WayFull(self, WayId): - """ - Returns the full data for way `WayId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `WayId` is a unique identifier for a way. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/way/{WayId}/full" - data = self._session._get(uri) - return parser.ParseOsm(data) -
Returns the full data for way WayId as list of dicts:
#!python
-[
- {
- 'type': node|way|relation,
- 'data': {} data dict for node|way|relation
- },
- { ... }
-]
-
-
-The WayId is a unique identifier for a way.
If the requested element has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
320 def WayFull(self, WayId: int) -> list[dict[str, Any]]: +321 """.. deprecated:: Use :meth:`way_full` instead.""" +322 warnings.warn( +323 "WayFull() is deprecated, use way_full() instead", +324 DeprecationWarning, +325 stacklevel=2, +326 ) +327 return self.way_full(WayId) +
Deprecated since version Use way_full() instead..
def WaysGet(self, WayIdList): - """ - Returns dict with the id of the way as a key for - each way in `WayIdList`: - - #!python - { - '1234': dict of WayData, - '5678': dict of WayData, - ... - } - - `WayIdList` is a list containing unique identifiers for multiple ways. - """ - way_list = ",".join([str(x) for x in WayIdList]) - uri = f"/api/0.6/ways?ways={way_list}" - data = self._session._get(uri) - ways = dom.OsmResponseToDom(data, tag="way") - result = {} - for way in ways: - data = dom.DomParseWay(way) - result[data["id"]] = data - return result -
Returns dict with the id of the way as a key for
-each way in WayIdList:
#!python
-{
- '1234': dict of WayData,
- '5678': dict of WayData,
- ...
-}
-
-
-WayIdList is a list containing unique identifiers for multiple ways.
329 def WaysGet(self, WayIdList: list[int]) -> dict[int, dict[str, Any]]: +330 """.. deprecated:: Use :meth:`ways_get` instead.""" +331 warnings.warn( +332 "WaysGet() is deprecated, use ways_get() instead", +333 DeprecationWarning, +334 stacklevel=2, +335 ) +336 return self.ways_get(WayIdList) +
Deprecated since version Use ways_get() instead..
def RelationGet(self, RelationId, RelationVersion=-1): - """ - Returns relation with `RelationId` as a dict: - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'timestamp': timestamp of last change, - 'visible': True|False - } - - If `RelationVersion` is supplied, this specific version is returned, - otherwise the latest version is returned. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/relation/{RelationId}" - if RelationVersion != -1: - uri += f"/{RelationVersion}" - data = self._session._get(uri) - relation = dom.OsmResponseToDom(data, tag="relation", single=True) - return dom.DomParseRelation(relation) -
Returns relation with RelationId as a dict:
#!python
-{
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of Relation,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'timestamp': timestamp of last change,
- 'visible': True|False
-}
-
-
-If RelationVersion is supplied, this specific version is returned,
-otherwise the latest version is returned.
If the requested element has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
342 def RelationGet(self, RelationId: int, RelationVersion: int = -1) -> dict[str, Any]: +343 """.. deprecated:: Use :meth:`relation_get` instead.""" +344 warnings.warn( +345 "RelationGet() is deprecated, use relation_get() instead", +346 DeprecationWarning, +347 stacklevel=2, +348 ) +349 return self.relation_get(RelationId, RelationVersion) +
Deprecated since version Use relation_get() instead..
def RelationCreate(self, RelationData): - """ - Creates a relation based on the supplied `RelationData` dict: - - #!python - { - 'member': [] list of members, - 'tag': {} dict of tags, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the supplied information contain an existing node, - `OsmApi.OsmTypeAlreadyExistsError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) -
Creates a relation based on the supplied RelationData dict:
#!python
-{
- 'member': [] list of members,
- 'tag': {} dict of tags,
-}
-
-
-Returns updated RelationData (without timestamp):
#!python
-{
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of Relation,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing node,
-OsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
351 def RelationCreate(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +352 """.. deprecated:: Use :meth:`relation_create` instead.""" +353 warnings.warn( +354 "RelationCreate() is deprecated, use relation_create() instead", +355 DeprecationWarning, +356 stacklevel=2, +357 ) +358 return self.relation_create(RelationData) +
Deprecated since version Use relation_create() instead..
def RelationUpdate(self, RelationData): - """ - Updates relation with the supplied `RelationData` dict: - - #!python - { - 'id': id of relation, - 'member': [] list of member dicts, - 'tag': {}, - 'version': version number of relation, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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) -
Updates relation with the supplied RelationData dict:
#!python
-{
- 'id': id of relation,
- 'member': [] list of member dicts,
- 'tag': {},
- 'version': version number of relation,
-}
-
-
-Returns updated RelationData (without timestamp):
#!python
-{
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {} dict of tags
- 'changeset': id of changeset of last change,
- 'version': version number of Relation,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
360 def RelationUpdate(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +361 """.. deprecated:: Use :meth:`relation_update` instead.""" +362 warnings.warn( +363 "RelationUpdate() is deprecated, use relation_update() instead", +364 DeprecationWarning, +365 stacklevel=2, +366 ) +367 return self.relation_update(RelationData) +
Deprecated since version Use relation_update() instead..
def RelationDelete(self, RelationData): - """ - Delete relation with `RelationData` dict: - - #!python - { - 'id': id of relation, - 'member': [] list of member dicts, - 'tag': {}, - 'version': version number of relation, - } - - Returns updated `RelationData` (without timestamp): - - #!python - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Relation, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - 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. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - return self._do("delete", "relation", RelationData) -
Delete relation with RelationData dict:
#!python
-{
- 'id': id of relation,
- 'member': [] list of member dicts,
- 'tag': {},
- 'version': version number of relation,
-}
-
-
-Returns updated RelationData (without timestamp):
#!python
-{
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of Relation,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
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.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
369 def RelationDelete(self, RelationData: dict[str, Any]) -> Optional[dict[str, Any]]: +370 """.. deprecated:: Use :meth:`relation_delete` instead.""" +371 warnings.warn( +372 "RelationDelete() is deprecated, use relation_delete() instead", +373 DeprecationWarning, +374 stacklevel=2, +375 ) +376 return self.relation_delete(RelationData) +
Deprecated since version Use relation_delete() instead..
def RelationHistory(self, RelationId): - """ - Returns dict with version as key: - - #!python - { - '1': dict of RelationData, - '2': dict of RelationData, - ... - } - - `RelationId` is the unique identifier of a relation. - """ - uri = f"/api/0.6/relation/{RelationId}/history" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation") - result = {} - for relation in relations: - data = dom.DomParseRelation(relation) - result[data["version"]] = data - return result -
Returns dict with version as key:
- -#!python
-{
- '1': dict of RelationData,
- '2': dict of RelationData,
- ...
-}
-
-
-RelationId is the unique identifier of a relation.
378 def RelationHistory(self, RelationId: int) -> dict[int, dict[str, Any]]: +379 """.. deprecated:: Use :meth:`relation_history` instead.""" +380 warnings.warn( +381 "RelationHistory() is deprecated, use relation_history() instead", +382 DeprecationWarning, +383 stacklevel=2, +384 ) +385 return self.relation_history(RelationId) +
Deprecated since version Use relation_history() instead..
def RelationRelations(self, RelationId): - """ - Returns a list of dicts of `RelationData` - containing relation `RelationId`: - - #!python - [ - { - 'id': id of Relation, - 'member': [ - { - 'ref': ID of referenced element, - 'role': optional description of role in relation - 'type': node|way|relation - }, - { - ... - } - ] - 'tag': {} dict of tags, - 'changeset': id of changeset of last change, - 'version': version number of Way, - 'user': username of user that made the last change, - 'uid': id of user that made the last change, - 'visible': True|False - }, - { - ... - }, - ] - - The `RelationId` is a unique identifier for a relation. - """ - uri = f"/api/0.6/relation/{RelationId}/relations" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation", allow_empty=True) - result = [] - for relation in relations: - data = dom.DomParseRelation(relation) - result.append(data) - return result -
Returns a list of dicts of RelationData
-containing relation RelationId:
#!python
-[
- {
- 'id': id of Relation,
- 'member': [
- {
- 'ref': ID of referenced element,
- 'role': optional description of role in relation
- 'type': node|way|relation
- },
- {
- ...
- }
- ]
- 'tag': {} dict of tags,
- 'changeset': id of changeset of last change,
- 'version': version number of Way,
- 'user': username of user that made the last change,
- 'uid': id of user that made the last change,
- 'visible': True|False
- },
- {
- ...
- },
-]
-
-
-The RelationId is a unique identifier for a relation.
387 def RelationRelations(self, RelationId: int) -> list[dict[str, Any]]: +388 """.. deprecated:: Use :meth:`relation_relations` instead.""" +389 warnings.warn( +390 "RelationRelations() is deprecated, use relation_relations() instead", +391 DeprecationWarning, +392 stacklevel=2, +393 ) +394 return self.relation_relations(RelationId) +
Deprecated since version Use relation_relations() instead..
def RelationFullRecur(self, RelationId): - """ - Returns the full data (all levels) for relation - `RelationId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `RelationId` is a unique identifier for a way. - - This function is useful for relations containing other relations. - - If you don't need all levels, use `OsmApi.RelationFull` - instead, which return only 2 levels. - - If any relation (on any level) has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - data = [] - todo = [RelationId] - done = [] - while todo: - rid = todo.pop(0) - done.append(rid) - temp = self.RelationFull(rid) - for item in temp: - if item["type"] != "relation": - continue - if item["data"]["id"] in done: - continue - todo.append(item["data"]["id"]) - data += temp - return data -
Returns the full data (all levels) for relation
-RelationId as list of dicts:
#!python
-[
- {
- 'type': node|way|relation,
- 'data': {} data dict for node|way|relation
- },
- { ... }
-]
-
-
-The RelationId is a unique identifier for a way.
This function is useful for relations containing other relations.
- -If you don't need all levels, use OsmApi.RelationFull
-instead, which return only 2 levels.
If any relation (on any level) has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
396 def RelationFullRecur(self, RelationId: int) -> list[dict[str, Any]]: +397 """.. deprecated:: Use :meth:`relation_full_recur` instead.""" +398 warnings.warn( +399 "RelationFullRecur() is deprecated, use relation_full_recur() instead", +400 DeprecationWarning, +401 stacklevel=2, +402 ) +403 return self.relation_full_recur(RelationId) +
Deprecated since version Use relation_full_recur() instead..
def RelationFull(self, RelationId): - """ - Returns the full data (two levels) for relation - `RelationId` as list of dicts: - - #!python - [ - { - 'type': node|way|relation, - 'data': {} data dict for node|way|relation - }, - { ... } - ] - - The `RelationId` is a unique identifier for a way. - - If you need all levels, use `OsmApi.RelationFullRecur`. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - uri = f"/api/0.6/relation/{RelationId}/full" - data = self._session._get(uri) - return parser.ParseOsm(data) -
Returns the full data (two levels) for relation
-RelationId as list of dicts:
#!python
-[
- {
- 'type': node|way|relation,
- 'data': {} data dict for node|way|relation
- },
- { ... }
-]
-
-
-The RelationId is a unique identifier for a way.
If you need all levels, use OsmApi.RelationFullRecur.
If the requested element has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
405 def RelationFull(self, RelationId: int) -> list[dict[str, Any]]: +406 """.. deprecated:: Use :meth:`relation_full` instead.""" +407 warnings.warn( +408 "RelationFull() is deprecated, use relation_full() instead", +409 DeprecationWarning, +410 stacklevel=2, +411 ) +412 return self.relation_full(RelationId) +
Deprecated since version Use relation_full() instead..
def RelationsGet(self, RelationIdList): - """ - Returns dict with the id of the relation as a key - for each relation in `RelationIdList`: - - #!python - { - '1234': dict of RelationData, - '5678': dict of RelationData, - ... - } - - `RelationIdList` is a list containing unique identifiers - for multiple relations. - """ - relation_list = ",".join([str(x) for x in RelationIdList]) - uri = f"/api/0.6/relations?relations={relation_list}" - data = self._session._get(uri) - relations = dom.OsmResponseToDom(data, tag="relation") - result = {} - for relation in relations: - data = dom.DomParseRelation(relation) - result[data["id"]] = data - return result -
Returns dict with the id of the relation as a key
-for each relation in RelationIdList:
#!python
-{
- '1234': dict of RelationData,
- '5678': dict of RelationData,
- ...
-}
-
-
-RelationIdList is a list containing unique identifiers
-for multiple relations.
414 def RelationsGet(self, RelationIdList: list[int]) -> dict[int, dict[str, Any]]: +415 """.. deprecated:: Use :meth:`relations_get` instead.""" +416 warnings.warn( +417 "RelationsGet() is deprecated, use relations_get() instead", +418 DeprecationWarning, +419 stacklevel=2, +420 ) +421 return self.relations_get(RelationIdList) +
Deprecated since version Use relations_get() instead..
@contextmanager - def Changeset(self, ChangesetTags={}): - """ - Context manager for a Changeset. - - It opens a Changeset, uploads the changes and closes the changeset - when used with the `with` statement: + ++- - - -@contextmanager- #!python - import osmapi + def + Changeset( self, ChangesetTags: Optional[dict[str, str]] = None) -> Generator[int, NoneType, NoneType]: - with osmapi.Changeset({"comment": "Import script XYZ"}) as changeset_id: - print(f"Part of changeset {changeset_id}") - api.NodeCreate({"lon":1, "lat":1, "tag": {}}) + - If `ChangesetTags` are given, this tags are applied (key/value). - - Returns `ChangesetId` - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - # Create a new changeset - changeset_id = self.ChangesetCreate(ChangesetTags) - yield changeset_id - - # upload data to changeset - autosize = self._changesetautosize - for i in range(0, len(self._changesetautodata), autosize): - chunk = self._changesetautodata[i : i + autosize] - self.ChangesetUpload(chunk) - self._changesetautodata = [] - self.ChangesetClose() -+ +Context manager for a Changeset.
- -It opens a Changeset, uploads the changes and closes the changeset -when used with the
- -withstatement:- -#!python -import osmapi - -with osmapi.Changeset({"comment": "Import script XYZ"}) as changeset_id: - print(f"Part of changeset {changeset_id}") - api.NodeCreate({"lon":1, "lat":1, "tag": {}}) -If
- -ChangesetTagsare given, this tags are applied (key/value).Returns
- -ChangesetIdIf no authentication information are provided, -
- -OsmApi.UsernamePasswordMissingErroris raised.If there is already an open changeset, -
+OsmApi.ChangesetAlreadyOpenErroris raised.+ + +427 @contextmanager +428 def Changeset( +429 self, ChangesetTags: Optional[dict[str, str]] = None +430 ) -> Generator[int, None, None]: +431 """.. deprecated:: Use :meth:`changeset` instead.""" +432 warnings.warn( +433 "Changeset() is deprecated, use changeset() instead", +434 DeprecationWarning, +435 stacklevel=2, +436 ) +437 with self.changeset(ChangesetTags) as changeset_id: +438 yield changeset_id +Deprecated since version Use
changeset()instead..
def ChangesetGet(self, ChangesetId, include_discussion=False): - """ - Returns changeset with `ChangesetId` as a dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'discussion': [] list of comment dict (-> `include_discussion`) - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - `ChangesetId` is the unique identifier of a changeset. - - If `include_discussion` is set to `True` the changeset discussion - will be available in the result. - """ - path = f"/api/0.6/changeset/{ChangesetId}" - if include_discussion: - path = f"{path}?include_discussion=true" - data = self._session._get(path) - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset, include_discussion=include_discussion) -
Returns changeset with ChangesetId as a dict:
#!python
-{
- 'id': id of Changeset,
- 'open': True|False, wheter or not this changeset is open
- 'tag': {} dict of tags,
- 'created_at': timestamp of creation of this changeset
- 'closed_at': timestamp when changeset was closed
- 'comments_count': amount of comments
- 'discussion': [] list of comment dict (-> `include_discussion`)
- 'max_lon': maximum longitude of changes in this changeset
- 'max_lat': maximum latitude of changes in this changeset
- 'min_lon': minimum longitude of changes in this changeset
- 'min_lat': minimum longitude of changes in this changeset
- 'user': username of user that created this changeset,
- 'uid': id of user that created this changeset,
-}
-
-
-ChangesetId is the unique identifier of a changeset.
If include_discussion is set to True the changeset discussion
-will be available in the result.
440 def ChangesetGet( +441 self, ChangesetId: int, include_discussion: bool = False +442 ) -> dict[str, Any]: +443 """.. deprecated:: Use :meth:`changeset_get` instead.""" +444 warnings.warn( +445 "ChangesetGet() is deprecated, use changeset_get() instead", +446 DeprecationWarning, +447 stacklevel=2, +448 ) +449 return self.changeset_get(ChangesetId, include_discussion) +
Deprecated since version Use changeset_get() instead..
def ChangesetUpdate(self, ChangesetTags={}): - """ - Updates current changeset with `ChangesetTags`. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - 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 errors.NoChangesetOpenError("No changeset currently opened") - if "created_by" not in ChangesetTags: - ChangesetTags["created_by"] = self._created_by - try: - self._session._put( - f"/api/0.6/changeset/{self._CurrentChangesetId}", - xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self), - return_value=False, - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - return self._CurrentChangesetId -
Updates current changeset with ChangesetTags.
If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
451 def ChangesetUpdate(self, ChangesetTags: Optional[dict[str, str]] = None) -> int: +452 """.. deprecated:: Use :meth:`changeset_update` instead.""" +453 warnings.warn( +454 "ChangesetUpdate() is deprecated, use changeset_update() instead", +455 DeprecationWarning, +456 stacklevel=2, +457 ) +458 return self.changeset_update(ChangesetTags) +
Deprecated since version Use changeset_update() instead..
def ChangesetCreate(self, ChangesetTags={}): - """ - Opens a changeset. - - If `ChangesetTags` are given, this tags are applied (key/value). - - Returns `ChangesetId` - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - if self._CurrentChangesetId: - raise errors.ChangesetAlreadyOpenError("Changeset already opened") - if "created_by" not in ChangesetTags: - ChangesetTags["created_by"] = self._created_by - - # check if someone tries to create a test changeset to PROD - if ( - self._api == "https://www.openstreetmap.org" - and ChangesetTags.get("comment") == "My first test" - ): - raise errors.OsmApiError( - "DO NOT CREATE test changesets on the production server" - ) - - result = self._session._put( - "/api/0.6/changeset/create", - xmlbuilder._XmlBuild("changeset", {"tag": ChangesetTags}, data=self), - ) - self._CurrentChangesetId = int(result) - return self._CurrentChangesetId -
Opens a changeset.
+ +If ChangesetTags are given, this tags are applied (key/value).
Returns ChangesetId
If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
460 def ChangesetCreate(self, ChangesetTags: Optional[dict[str, str]] = None) -> int: +461 """.. deprecated:: Use :meth:`changeset_create` instead.""" +462 warnings.warn( +463 "ChangesetCreate() is deprecated, use changeset_create() instead", +464 DeprecationWarning, +465 stacklevel=2, +466 ) +467 return self.changeset_create(ChangesetTags) +
Deprecated since version Use changeset_create() instead..
def ChangesetClose(self): - """ - Closes current changeset. - - Returns `ChangesetId`. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - 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 errors.NoChangesetOpenError("No changeset currently opened") - try: - self._session._put( - f"/api/0.6/changeset/{self._CurrentChangesetId}/close", - "", - return_value=False, - ) - CurrentChangesetId = self._CurrentChangesetId - self._CurrentChangesetId = 0 - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - return CurrentChangesetId -
Closes current changeset.
- -Returns ChangesetId.
If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
469 def ChangesetClose(self) -> int: +470 """.. deprecated:: Use :meth:`changeset_close` instead.""" +471 warnings.warn( +472 "ChangesetClose() is deprecated, use changeset_close() instead", +473 DeprecationWarning, +474 stacklevel=2, +475 ) +476 return self.changeset_close() +
Deprecated since version Use changeset_close() instead..
def ChangesetUpload(self, ChangesData): - """ - Upload data with the `ChangesData` list of dicts: - - #!python - { - type: node|way|relation, - action: create|delete|modify, - data: {} - } - - Returns list with updated ids. - - 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' - data += '<osmChange version="0.6" generator="' - data += self._created_by + '">\n' - for change in ChangesData: - data += "<" + change["action"] + ">\n" - changeData = change["data"] - data += self._add_changeset_data(changeData, change["type"]) - data += "</" + change["action"] + ">\n" - data += "</osmChange>" - try: - data = self._session._post( - f"/api/0.6/changeset/{self._CurrentChangesetId}/upload", - data.encode("utf-8"), - forceAuth=True, - ) - except errors.ApiError as e: - if e.status == 409 and re.search( - r"The changeset .* was closed at .*", e.payload - ): - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("diffResult")[0] - data = [x for x in data.childNodes if x.nodeType == x.ELEMENT_NODE] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - for change in ChangesData: - if change["action"] == "delete": - for changeElement in change["data"]: - changeElement.pop("version") - else: - self._assign_id_and_version(data, change["data"]) - - return ChangesData -
Upload data with the ChangesData list of dicts:
#!python
-{
- type: node|way|relation,
- action: create|delete|modify,
- data: {}
-}
-
-
-Returns list with updated ids.
- -If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
478 def ChangesetUpload( +479 self, ChangesData: list[dict[str, Any]] +480 ) -> list[dict[str, Any]]: +481 """.. deprecated:: Use :meth:`changeset_upload` instead.""" +482 warnings.warn( +483 "ChangesetUpload() is deprecated, use changeset_upload() instead", +484 DeprecationWarning, +485 stacklevel=2, +486 ) +487 return self.changeset_upload(ChangesData) +
Deprecated since version Use changeset_upload() instead..
def ChangesetDownload(self, ChangesetId): - """ - Download data from changeset `ChangesetId`. - - Returns list of dict: - - #!python - { - 'type': node|way|relation, - 'action': create|delete|modify, - 'data': {} - } - """ - uri = f"/api/0.6/changeset/{ChangesetId}/download" - data = self._session._get(uri) - return parser.ParseOsc(data) -
Download data from changeset ChangesetId.
Returns list of dict:
- -#!python
-{
- 'type': node|way|relation,
- 'action': create|delete|modify,
- 'data': {}
-}
-
+ 489 def ChangesetDownload(self, ChangesetId: int) -> list[dict[str, Any]]: +490 """.. deprecated:: Use :meth:`changeset_download` instead.""" +491 warnings.warn( +492 "ChangesetDownload() is deprecated, use changeset_download() instead", +493 DeprecationWarning, +494 stacklevel=2, +495 ) +496 return self.changeset_download(ChangesetId) +
Deprecated since version Use changeset_download() instead..
def ChangesetsGet( # noqa - self, - min_lon=None, - min_lat=None, - max_lon=None, - max_lat=None, - userid=None, - username=None, - closed_after=None, - created_before=None, - only_open=False, - only_closed=False, - ): - """ - Returns a dict with the id of the changeset as key - matching all criteria: - - #!python - { - '1234': dict of ChangesetData, - '5678': dict of ChangesetData, - ... - } - - All parameters are optional. - """ - - uri = "/api/0.6/changesets" - params = {} - if min_lon or min_lat or max_lon or max_lat: - params["bbox"] = f"{min_lon},{min_lat},{max_lon},{max_lat}" - if userid: - params["user"] = userid - if username: - params["display_name"] = username - if closed_after and not created_before: - params["time"] = closed_after - if created_before: - if not closed_after: - closed_after = "1970-01-01T00:00:00Z" - params["time"] = f"{closed_after},{created_before}" - if only_open: - params["open"] = 1 - if only_closed: - params["closed"] = 1 - - if params: - uri += "?" + urllib.parse.urlencode(params) - - data = self._session._get(uri) - changesets = dom.OsmResponseToDom(data, tag="changeset") - result = {} - for curChangeset in changesets: - tmpCS = dom.DomParseChangeset(curChangeset) - result[tmpCS["id"]] = tmpCS - return result -
Returns a dict with the id of the changeset as key -matching all criteria:
- -#!python
-{
- '1234': dict of ChangesetData,
- '5678': dict of ChangesetData,
- ...
-}
-
-
-All parameters are optional.
+498 def ChangesetsGet( # noqa +499 self, +500 min_lon: Optional[float] = None, +501 min_lat: Optional[float] = None, +502 max_lon: Optional[float] = None, +503 max_lat: Optional[float] = None, +504 userid: Optional[int] = None, +505 username: Optional[str] = None, +506 closed_after: Optional[str] = None, +507 created_before: Optional[str] = None, +508 only_open: bool = False, +509 only_closed: bool = False, +510 ) -> dict[int, dict[str, Any]]: +511 """.. deprecated:: Use :meth:`changesets_get` instead.""" +512 warnings.warn( +513 "ChangesetsGet() is deprecated, use changesets_get() instead", +514 DeprecationWarning, +515 stacklevel=2, +516 ) +517 return self.changesets_get( +518 min_lon, +519 min_lat, +520 max_lon, +521 max_lat, +522 userid, +523 username, +524 closed_after, +525 created_before, +526 only_open, +527 only_closed, +528 ) +
Deprecated since version Use changesets_get() instead..
def ChangesetComment(self, ChangesetId, comment): - """ - Adds a comment to the changeset `ChangesetId` - - `comment` should be a string. - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the changeset is already closed, - `OsmApi.ChangesetClosedApiError` is raised. - """ - params = urllib.parse.urlencode({"text": comment}) - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/comment", params, forceAuth=True - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.ChangesetClosedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) -
Adds a comment to the changeset ChangesetId
comment should be a string.
Returns the updated ChangesetData dict:
#!python
-{
- 'id': id of Changeset,
- 'open': True|False, wheter or not this changeset is open
- 'tag': {} dict of tags,
- 'created_at': timestamp of creation of this changeset
- 'closed_at': timestamp when changeset was closed
- 'comments_count': amount of comments
- 'max_lon': maximum longitude of changes in this changeset
- 'max_lat': maximum latitude of changes in this changeset
- 'min_lon': minimum longitude of changes in this changeset
- 'min_lat': minimum longitude of changes in this changeset
- 'user': username of user that created this changeset,
- 'uid': id of user that created this changeset,
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,
-OsmApi.ChangesetClosedApiError is raised.
530 def ChangesetComment(self, ChangesetId: int, comment: str) -> dict[str, Any]: +531 """.. deprecated:: Use :meth:`changeset_comment` instead.""" +532 warnings.warn( +533 "ChangesetComment() is deprecated, use changeset_comment() instead", +534 DeprecationWarning, +535 stacklevel=2, +536 ) +537 return self.changeset_comment(ChangesetId, comment) +
Deprecated since version Use changeset_comment() instead..
def ChangesetSubscribe(self, ChangesetId): - """ - Subcribe to the changeset discussion of changeset `ChangesetId`. - - The user will be informed about new comments (i.e. receive an email). - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/subscribe", None, forceAuth=True - ) - except errors.ApiError as e: - if e.status == 409: - raise errors.AlreadySubscribedApiError( - e.status, e.reason, e.payload - ) from e - else: - raise - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) -
Subcribe to the changeset discussion of changeset ChangesetId.
The user will be informed about new comments (i.e. receive an email).
- -Returns the updated ChangesetData dict:
#!python
-{
- 'id': id of Changeset,
- 'open': True|False, wheter or not this changeset is open
- 'tag': {} dict of tags,
- 'created_at': timestamp of creation of this changeset
- 'closed_at': timestamp when changeset was closed
- 'comments_count': amount of comments
- 'max_lon': maximum longitude of changes in this changeset
- 'max_lat': maximum latitude of changes in this changeset
- 'min_lon': minimum longitude of changes in this changeset
- 'min_lat': minimum longitude of changes in this changeset
- 'user': username of user that created this changeset,
- 'uid': id of user that created this changeset,
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
539 def ChangesetSubscribe(self, ChangesetId: int) -> dict[str, Any]: +540 """.. deprecated:: Use :meth:`changeset_subscribe` instead.""" +541 warnings.warn( +542 "ChangesetSubscribe() is deprecated, use changeset_subscribe() instead", +543 DeprecationWarning, +544 stacklevel=2, +545 ) +546 return self.changeset_subscribe(ChangesetId) +
Deprecated since version Use changeset_subscribe() instead..
def ChangesetUnsubscribe(self, ChangesetId): - """ - Subcribe to the changeset discussion of changeset `ChangesetId`. - - The user will be informed about new comments (i.e. receive an email). - - Returns the updated `ChangesetData` dict: - - #!python - { - 'id': id of Changeset, - 'open': True|False, wheter or not this changeset is open - 'tag': {} dict of tags, - 'created_at': timestamp of creation of this changeset - 'closed_at': timestamp when changeset was closed - 'comments_count': amount of comments - 'max_lon': maximum longitude of changes in this changeset - 'max_lat': maximum latitude of changes in this changeset - 'min_lon': minimum longitude of changes in this changeset - 'min_lat': minimum longitude of changes in this changeset - 'user': username of user that created this changeset, - 'uid': id of user that created this changeset, - } - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - try: - data = self._session._post( - f"/api/0.6/changeset/{ChangesetId}/unsubscribe", None, forceAuth=True - ) - except errors.ElementNotFoundApiError as e: - raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e - - changeset = dom.OsmResponseToDom(data, tag="changeset", single=True) - return dom.DomParseChangeset(changeset) -
Subcribe to the changeset discussion of changeset ChangesetId.
The user will be informed about new comments (i.e. receive an email).
- -Returns the updated ChangesetData dict:
#!python
-{
- 'id': id of Changeset,
- 'open': True|False, wheter or not this changeset is open
- 'tag': {} dict of tags,
- 'created_at': timestamp of creation of this changeset
- 'closed_at': timestamp when changeset was closed
- 'comments_count': amount of comments
- 'max_lon': maximum longitude of changes in this changeset
- 'max_lat': maximum latitude of changes in this changeset
- 'min_lon': minimum longitude of changes in this changeset
- 'min_lat': minimum longitude of changes in this changeset
- 'user': username of user that created this changeset,
- 'uid': id of user that created this changeset,
-}
-
-
-If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
548 def ChangesetUnsubscribe(self, ChangesetId: int) -> dict[str, Any]: +549 """.. deprecated:: Use :meth:`changeset_unsubscribe` instead.""" +550 warnings.warn( +551 "ChangesetUnsubscribe() is deprecated, use changeset_unsubscribe() instead", +552 DeprecationWarning, +553 stacklevel=2, +554 ) +555 return self.changeset_unsubscribe(ChangesetId) +
Deprecated since version Use changeset_unsubscribe() instead..
def NotesGet(self, min_lon, min_lat, max_lon, max_lat, limit=100, closed=7): - """ - Returns a list of dicts of notes in the specified bounding box: - - #!python - [ - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - }, - { ... } - ] - - The limit parameter defines how many results should be returned. - - closed specifies the number of days a bug needs to be closed - to no longer be returned. - The value 0 means only open bugs are returned, - -1 means all bugs are returned. - - All parameters are optional. - """ - uri = ( - f"/api/0.6/notes?bbox=" - f"{min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" - f"&limit={limit}&closed={closed}" - ) - data = self._session._get(uri) - return parser.ParseNotes(data) -
Returns a list of dicts of notes in the specified bounding box:
- -#!python
-[
- {
- 'id': integer,
- 'action': opened|commented|closed,
- 'status': open|closed
- 'date_created': creation date
- 'date_closed': closing data|None
- 'uid': User ID|None
- 'user': User name|None
- 'comments': {}
- },
- { ... }
-]
-
-
-The limit parameter defines how many results should be returned.
- -closed specifies the number of days a bug needs to be closed -to no longer be returned. -The value 0 means only open bugs are returned, --1 means all bugs are returned.
- -All parameters are optional.
+561 def NotesGet( +562 self, +563 min_lon: float, +564 min_lat: float, +565 max_lon: float, +566 max_lat: float, +567 limit: int = 100, +568 closed: int = 7, +569 ) -> list[dict[str, Any]]: +570 """.. deprecated:: Use :meth:`notes_get` instead.""" +571 warnings.warn( +572 "NotesGet() is deprecated, use notes_get() instead", +573 DeprecationWarning, +574 stacklevel=2, +575 ) +576 return self.notes_get(min_lon, min_lat, max_lon, max_lat, limit, closed) +
Deprecated since version Use notes_get() instead..
def NoteGet(self, id): - """ - Returns a note as dict: - - #!python - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - } - - `id` is the unique identifier of the note. - """ - uri = f"/api/0.6/notes/{id}" - data = self._session._get(uri) - noteElement = dom.OsmResponseToDom(data, tag="note", single=True) - return dom.DomParseNote(noteElement) -
Returns a note as dict:
- -#!python
-{
- 'id': integer,
- 'action': opened|commented|closed,
- 'status': open|closed
- 'date_created': creation date
- 'date_closed': closing data|None
- 'uid': User ID|None
- 'user': User name|None
- 'comments': {}
-}
-
-
-id is the unique identifier of the note.
578 def NoteGet(self, id: int) -> dict[str, Any]: +579 """.. deprecated:: Use :meth:`note_get` instead.""" +580 warnings.warn( +581 "NoteGet() is deprecated, use note_get() instead", +582 DeprecationWarning, +583 stacklevel=2, +584 ) +585 return self.note_get(id) +
Deprecated since version Use note_get() instead..
def NoteCreate(self, NoteData): - """ - Creates a note based on the supplied `NoteData` dict: - - #!python - { - 'lat': latitude of note, - 'lon': longitude of note, - 'text': text of the note, - } - - Returns updated `NoteData`: - - #!python - { - 'id': id of note, - 'lat': latitude of note, - 'lon': longitude of note, - 'date_created': date when the note was created - 'date_closed': date when the note was closed or None if it's open, - 'status': status of the note (open or closed), - 'comments': [ - { - 'date': date of the comment, - 'action': status of comment (opened, commented, closed), - 'text': text of the note, - 'html': html version of the text of the note, - 'uid': user id of the user creating this note or None - 'user': username of the user creating this note or None - } - ] - } - - """ - uri = "/api/0.6/notes" - uri += "?" + urllib.parse.urlencode(NoteData) - return self._NoteAction(uri) -
Creates a note based on the supplied NoteData dict:
#!python
-{
- 'lat': latitude of note,
- 'lon': longitude of note,
- 'text': text of the note,
-}
-
-
-Returns updated NoteData:
#!python
-{
- 'id': id of note,
- 'lat': latitude of note,
- 'lon': longitude of note,
- 'date_created': date when the note was created
- 'date_closed': date when the note was closed or None if it's open,
- 'status': status of the note (open or closed),
- 'comments': [
- {
- 'date': date of the comment,
- 'action': status of comment (opened, commented, closed),
- 'text': text of the note,
- 'html': html version of the text of the note,
- 'uid': user id of the user creating this note or None
- 'user': username of the user creating this note or None
- }
- ]
-}
-
+ 587 def NoteCreate(self, NoteData: dict[str, Any]) -> dict[str, Any]: +588 """.. deprecated:: Use :meth:`note_create` instead.""" +589 warnings.warn( +590 "NoteCreate() is deprecated, use note_create() instead", +591 DeprecationWarning, +592 stacklevel=2, +593 ) +594 return self.note_create(NoteData) +
Deprecated since version Use note_create() instead..
def NoteComment(self, NoteId, comment): - """ - Adds a new comment to a note. - - Returns the updated note. - """ - path = f"/api/0.6/notes/{NoteId}/comment" - return self._NoteAction(path, comment) -
Adds a new comment to a note.
- -Returns the updated note.
+596 def NoteComment(self, note_id: int, comment: str) -> dict[str, Any]: +597 """.. deprecated:: Use :meth:`note_comment` instead.""" +598 warnings.warn( +599 "NoteComment() is deprecated, use note_comment() instead", +600 DeprecationWarning, +601 stacklevel=2, +602 ) +603 return self.note_comment(note_id, comment) +
Deprecated since version Use note_comment() instead..
def NoteClose(self, NoteId, comment): - """ - Closes a note. - - Returns the updated note. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - """ - path = f"/api/0.6/notes/{NoteId}/close" - return self._NoteAction(path, comment, optionalAuth=False) -
Closes a note.
- -Returns the updated note.
- -If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
605 def NoteClose(self, note_id: int, comment: Optional[str] = None) -> dict[str, Any]: +606 """.. deprecated:: Use :meth:`note_close` instead.""" +607 warnings.warn( +608 "NoteClose() is deprecated, use note_close() instead", +609 DeprecationWarning, +610 stacklevel=2, +611 ) +612 return self.note_close(note_id, comment) +
Deprecated since version Use note_close() instead..
def NoteReopen(self, NoteId, comment): - """ - Reopens a note. - - Returns the updated note. - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - """ - path = f"/api/0.6/notes/{NoteId}/reopen" - return self._NoteAction(path, comment, optionalAuth=False) -
Reopens a note.
- -Returns the updated note.
- -If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If the requested element has been deleted,
-OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
-OsmApi.ElementNotFoundApiError is raised.
614 def NoteReopen(self, note_id: int, comment: Optional[str] = None) -> dict[str, Any]: +615 """.. deprecated:: Use :meth:`note_reopen` instead.""" +616 warnings.warn( +617 "NoteReopen() is deprecated, use note_reopen() instead", +618 DeprecationWarning, +619 stacklevel=2, +620 ) +621 return self.note_reopen(note_id, comment) +
Deprecated since version Use note_reopen() instead..
def NotesSearch(self, query, limit=100, closed=7): - """ - Returns a list of dicts of notes that match the given search query. - - The limit parameter defines how many results should be returned. - - closed specifies the number of days a bug needs to be closed - to no longer be returned. - The value 0 means only open bugs are returned, - -1 means all bugs are returned. - """ - uri = "/api/0.6/notes/search" - params = {} - params["q"] = query - params["limit"] = limit - params["closed"] = closed - uri += "?" + urllib.parse.urlencode(params) - data = self._session._get(uri) - - return parser.ParseNotes(data) -
Returns a list of dicts of notes that match the given search query.
- -The limit parameter defines how many results should be returned.
- -closed specifies the number of days a bug needs to be closed -to no longer be returned. -The value 0 means only open bugs are returned, --1 means all bugs are returned.
+623 def NotesSearch( +624 self, query: str, limit: int = 100, closed: int = 7 +625 ) -> list[dict[str, Any]]: +626 """.. deprecated:: Use :meth:`notes_search` instead.""" +627 warnings.warn( +628 "NotesSearch() is deprecated, use notes_search() instead", +629 DeprecationWarning, +630 stacklevel=2, +631 ) +632 return self.notes_search(query, limit, closed) +
Deprecated since version Use notes_search() instead..
def Map(self, min_lon, min_lat, max_lon, max_lat): - """ - Download data in bounding box. - - Returns list of dict: - - #!python - { - type: node|way|relation, - data: {} - } - """ - uri = f"/api/0.6/map?bbox={min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" - data = self._session._get(uri) - return parser.ParseOsm(data) -
Download data in bounding box.
- -Returns list of dict:
- -#!python
-{
- type: node|way|relation,
- data: {}
-}
-
+ 644 def Map( +645 self, min_lon: float, min_lat: float, max_lon: float, max_lat: float +646 ) -> list[dict[str, Any]]: +647 """.. deprecated:: Use :meth:`map` instead.""" +648 warnings.warn( +649 "Map() is deprecated, use map() instead", +650 DeprecationWarning, +651 stacklevel=2, +652 ) +653 return self.map(min_lon, min_lat, max_lon, max_lat) +
Deprecated since version Use map() instead..
def flush(self): - """ - Force the changes to be uploaded to OSM and the changeset to be closed - - If no authentication information are provided, - `OsmApi.UsernamePasswordMissingError` is raised. - - If there is no open changeset, - `OsmApi.NoChangesetOpenError` is raised. - - If there is already an open changeset, - `OsmApi.ChangesetAlreadyOpenError` is raised. - """ - return self._changesetautoflush(True) -
Force the changes to be uploaded to OSM and the changeset to be closed
+If no authentication information are provided,
-OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
-OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
-OsmApi.ChangesetAlreadyOpenError is raised.
Capabilities and miscellaneous operations for the OpenStreetMap API.
+1""" + 2Capabilities and miscellaneous operations for the OpenStreetMap API. + 3""" + 4 + 5from typing import Any, TYPE_CHECKING, cast + 6from xml.dom.minidom import Element + 7 + 8from . import dom, parser + 9 +10if TYPE_CHECKING: +11 from .OsmApi import OsmApi +12 +13 +14class CapabilitiesMixin: +15 """Mixin providing capabilities and misc operations with pythonic method names.""" +16 +17 def capabilities(self: "OsmApi") -> dict[str, dict[str, Any]]: +18 """ +19 Returns the API capabilities as a dict. +20 +21 The capabilities can be used by a client to +22 gain insights of the server in use. +23 """ +24 uri = "/api/capabilities" +25 data = self._session._get(uri) +26 +27 api_element = cast(Element, dom.OsmResponseToDom(data, tag="api", single=True)) +28 result: dict[str, Any] = {} +29 for elem in api_element.childNodes: +30 if elem.nodeType != elem.ELEMENT_NODE: +31 continue +32 result[elem.nodeName] = {} +33 for k, v in elem.attributes.items(): +34 try: +35 result[elem.nodeName][k] = float(v) +36 except Exception: +37 result[elem.nodeName][k] = v +38 return result +39 +40 def map( +41 self: "OsmApi", min_lon: float, min_lat: float, max_lon: float, max_lat: float +42 ) -> list[dict[str, Any]]: +43 """ +44 Download data in bounding box. +45 +46 Returns list of dict with type and data. +47 """ +48 uri = f"/api/0.6/map?bbox={min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" +49 data = self._session._get(uri) +50 return parser.parse_osm(data) +
15class CapabilitiesMixin: +16 """Mixin providing capabilities and misc operations with pythonic method names.""" +17 +18 def capabilities(self: "OsmApi") -> dict[str, dict[str, Any]]: +19 """ +20 Returns the API capabilities as a dict. +21 +22 The capabilities can be used by a client to +23 gain insights of the server in use. +24 """ +25 uri = "/api/capabilities" +26 data = self._session._get(uri) +27 +28 api_element = cast(Element, dom.OsmResponseToDom(data, tag="api", single=True)) +29 result: dict[str, Any] = {} +30 for elem in api_element.childNodes: +31 if elem.nodeType != elem.ELEMENT_NODE: +32 continue +33 result[elem.nodeName] = {} +34 for k, v in elem.attributes.items(): +35 try: +36 result[elem.nodeName][k] = float(v) +37 except Exception: +38 result[elem.nodeName][k] = v +39 return result +40 +41 def map( +42 self: "OsmApi", min_lon: float, min_lat: float, max_lon: float, max_lat: float +43 ) -> list[dict[str, Any]]: +44 """ +45 Download data in bounding box. +46 +47 Returns list of dict with type and data. +48 """ +49 uri = f"/api/0.6/map?bbox={min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" +50 data = self._session._get(uri) +51 return parser.parse_osm(data) +
Mixin providing capabilities and misc operations with pythonic method names.
+18 def capabilities(self: "OsmApi") -> dict[str, dict[str, Any]]: +19 """ +20 Returns the API capabilities as a dict. +21 +22 The capabilities can be used by a client to +23 gain insights of the server in use. +24 """ +25 uri = "/api/capabilities" +26 data = self._session._get(uri) +27 +28 api_element = cast(Element, dom.OsmResponseToDom(data, tag="api", single=True)) +29 result: dict[str, Any] = {} +30 for elem in api_element.childNodes: +31 if elem.nodeType != elem.ELEMENT_NODE: +32 continue +33 result[elem.nodeName] = {} +34 for k, v in elem.attributes.items(): +35 try: +36 result[elem.nodeName][k] = float(v) +37 except Exception: +38 result[elem.nodeName][k] = v +39 return result +
Returns the API capabilities as a dict.
+ +The capabilities can be used by a client to +gain insights of the server in use.
+41 def map( +42 self: "OsmApi", min_lon: float, min_lat: float, max_lon: float, max_lat: float +43 ) -> list[dict[str, Any]]: +44 """ +45 Download data in bounding box. +46 +47 Returns list of dict with type and data. +48 """ +49 uri = f"/api/0.6/map?bbox={min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}" +50 data = self._session._get(uri) +51 return parser.parse_osm(data) +
Download data in bounding box.
+ +Returns list of dict with type and data.
+Changeset operations for the OpenStreetMap API.
+1""" + 2Changeset operations for the OpenStreetMap API. + 3""" + 4 + 5import re + 6import urllib.parse + 7import xml.dom.minidom + 8import xml.parsers.expat + 9from contextlib import contextmanager + 10from typing import Any, Optional, TYPE_CHECKING, Generator, cast + 11from xml.dom.minidom import Element + 12 + 13from . import dom, errors, xmlbuilder, parser + 14 + 15if TYPE_CHECKING: + 16 from .OsmApi import OsmApi + 17 + 18 + 19class ChangesetMixin: + 20 """Mixin providing changeset-related operations with pythonic method names.""" + 21 + 22 @contextmanager + 23 def changeset( + 24 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None + 25 ) -> Generator[int, None, None]: + 26 """ + 27 Context manager for a Changeset. + 28 + 29 It opens a Changeset, uploads the changes and closes the changeset + 30 when used with the `with` statement: + 31 + 32 #!python + 33 import osmapi + 34 + 35 with api.changeset({"comment": "Import script XYZ"}) as changeset_id: + 36 print(f"Part of changeset {changeset_id}") + 37 api.node_create({"lon":1, "lat":1, "tag": {}}) + 38 + 39 If `changeset_tags` are given, this tags are applied (key/value). + 40 + 41 Returns `changeset_id` + 42 + 43 If no authentication information are provided, + 44 `OsmApi.UsernamePasswordMissingError` is raised. + 45 + 46 If there is already an open changeset, + 47 `OsmApi.ChangesetAlreadyOpenError` is raised. + 48 """ + 49 if changeset_tags is None: + 50 changeset_tags = {} + 51 # Create a new changeset + 52 changeset_id = self.changeset_create(changeset_tags) + 53 yield changeset_id + 54 self.changeset_close() + 55 + 56 def changeset_get( + 57 self: "OsmApi", changeset_id: int, include_discussion: bool = False + 58 ) -> dict[str, Any]: + 59 """ + 60 Returns changeset with `changeset_id` as a dict. + 61 + 62 `changeset_id` is the unique identifier of a changeset. + 63 + 64 If `include_discussion` is set to `True` the changeset discussion + 65 will be available in the result. + 66 """ + 67 path = f"/api/0.6/changeset/{changeset_id}" + 68 if include_discussion: + 69 path = f"{path}?include_discussion=true" + 70 data = self._session._get(path) + 71 changeset = cast( + 72 Element, dom.OsmResponseToDom(data, tag="changeset", single=True) + 73 ) + 74 return dom.dom_parse_changeset(changeset, include_discussion=include_discussion) + 75 + 76 def changeset_update( + 77 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None + 78 ) -> int: + 79 """ + 80 Updates current changeset with `changeset_tags`. + 81 + 82 If no authentication information are provided, + 83 `OsmApi.UsernamePasswordMissingError` is raised. + 84 + 85 If there is no open changeset, + 86 `OsmApi.NoChangesetOpenError` is raised. + 87 + 88 If the changeset is already closed, + 89 `OsmApi.ChangesetClosedApiError` is raised. + 90 """ + 91 if changeset_tags is None: + 92 changeset_tags = {} + 93 if not self._current_changeset_id: + 94 raise errors.NoChangesetOpenError("No changeset currently opened") + 95 if "created_by" not in changeset_tags: + 96 changeset_tags["created_by"] = self._created_by + 97 try: + 98 self._session._put( + 99 f"/api/0.6/changeset/{self._current_changeset_id}", +100 xmlbuilder._xml_build("changeset", {"tag": changeset_tags}, data=self), +101 return_value=False, +102 ) +103 except errors.ApiError as e: +104 if e.status == 409: +105 raise errors.ChangesetClosedApiError( +106 e.status, e.reason, e.payload +107 ) from e +108 else: +109 raise +110 return self._current_changeset_id +111 +112 def changeset_create( +113 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None +114 ) -> int: +115 """ +116 Opens a changeset. +117 +118 If `changeset_tags` are given, this tags are applied (key/value). +119 +120 Returns `changeset_id` +121 +122 If no authentication information are provided, +123 `OsmApi.UsernamePasswordMissingError` is raised. +124 +125 If there is already an open changeset, +126 `OsmApi.ChangesetAlreadyOpenError` is raised. +127 """ +128 if changeset_tags is None: +129 changeset_tags = {} +130 if self._current_changeset_id: +131 raise errors.ChangesetAlreadyOpenError("Changeset already opened") +132 if "created_by" not in changeset_tags: +133 changeset_tags["created_by"] = self._created_by +134 +135 # check if someone tries to create a test changeset to PROD +136 if ( +137 self._api == "https://www.openstreetmap.org" +138 and changeset_tags.get("comment") == "My first test" +139 ): +140 raise errors.OsmApiError( +141 "DO NOT CREATE test changesets on the production server" +142 ) +143 +144 result = self._session._put( +145 "/api/0.6/changeset/create", +146 xmlbuilder._xml_build("changeset", {"tag": changeset_tags}, data=self), +147 ) +148 self._current_changeset_id = int(result) +149 return self._current_changeset_id +150 +151 def changeset_close(self: "OsmApi") -> int: +152 """ +153 Closes current changeset. +154 +155 Returns `changeset_id`. +156 +157 If no authentication information are provided, +158 `OsmApi.UsernamePasswordMissingError` is raised. +159 +160 If there is no open changeset, +161 `OsmApi.NoChangesetOpenError` is raised. +162 +163 If the changeset is already closed, +164 `OsmApi.ChangesetClosedApiError` is raised. +165 """ +166 if not self._current_changeset_id: +167 raise errors.NoChangesetOpenError("No changeset currently opened") +168 try: +169 self._session._put( +170 f"/api/0.6/changeset/{self._current_changeset_id}/close", +171 None, +172 return_value=False, +173 ) +174 current_changeset_id = self._current_changeset_id +175 self._current_changeset_id = 0 +176 except errors.ApiError as e: +177 if e.status == 409: +178 raise errors.ChangesetClosedApiError( +179 e.status, e.reason, e.payload +180 ) from e +181 else: +182 raise +183 return current_changeset_id +184 +185 def changeset_upload( +186 self: "OsmApi", changes_data: list[dict[str, Any]] +187 ) -> list[dict[str, Any]]: +188 """ +189 Upload data with the `changes_data` list of dicts. +190 +191 Returns list with updated ids. +192 +193 If no authentication information are provided, +194 `OsmApi.UsernamePasswordMissingError` is raised. +195 +196 If the changeset is already closed, +197 `OsmApi.ChangesetClosedApiError` is raised. +198 """ +199 data = "" +200 data += '<?xml version="1.0" encoding="UTF-8"?>\n' +201 data += '<osmChange version="0.6" generator="' +202 data += self._created_by + '">\n' +203 for change in changes_data: +204 data += "<" + change["action"] + ">\n" +205 change_data = change["data"] +206 data += self._add_changeset_data(change_data, change["type"]) +207 data += "</" + change["action"] + ">\n" +208 data += "</osmChange>" +209 try: +210 response_data = self._session._post( +211 f"/api/0.6/changeset/{self._current_changeset_id}/upload", +212 data.encode("utf-8"), +213 forceAuth=True, +214 ) +215 except errors.ApiError as e: +216 if e.status == 409 and re.search( +217 r"The changeset .* was closed at .*", e.payload +218 ): +219 raise errors.ChangesetClosedApiError( +220 e.status, e.reason, e.payload +221 ) from e +222 else: +223 raise +224 try: +225 result_dom = xml.dom.minidom.parseString(response_data) +226 diff_result = result_dom.getElementsByTagName("diffResult")[0] +227 result_elements = [ +228 x for x in diff_result.childNodes if x.nodeType == x.ELEMENT_NODE +229 ] +230 except (xml.parsers.expat.ExpatError, IndexError) as e: +231 raise errors.XmlResponseInvalidError( +232 f"The XML response from the OSM API is invalid: {e!r}" +233 ) from e +234 +235 for change in changes_data: +236 if change["action"] == "delete": +237 for change_element in change["data"]: +238 change_element.pop("version") +239 else: +240 self._assign_id_and_version(result_elements, change["data"]) +241 +242 return changes_data +243 +244 def changeset_download(self: "OsmApi", changeset_id: int) -> list[dict[str, Any]]: +245 """ +246 Download data from changeset `changeset_id`. +247 +248 Returns list of dict with type, action, and data. +249 """ +250 uri = f"/api/0.6/changeset/{changeset_id}/download" +251 data = self._session._get(uri) +252 return parser.parse_osc(data) +253 +254 def changesets_get( # noqa: C901 +255 self: "OsmApi", +256 min_lon: Optional[float] = None, +257 min_lat: Optional[float] = None, +258 max_lon: Optional[float] = None, +259 max_lat: Optional[float] = None, +260 userid: Optional[int] = None, +261 username: Optional[str] = None, +262 closed_after: Optional[str] = None, +263 created_before: Optional[str] = None, +264 only_open: bool = False, +265 only_closed: bool = False, +266 ) -> dict[int, dict[str, Any]]: +267 """ +268 Returns a dict with the id of the changeset as key matching all criteria. +269 +270 All parameters are optional. +271 """ +272 uri = "/api/0.6/changesets" +273 params: dict[str, Any] = {} +274 if min_lon or min_lat or max_lon or max_lat: +275 params["bbox"] = f"{min_lon},{min_lat},{max_lon},{max_lat}" +276 if userid: +277 params["user"] = userid +278 if username: +279 params["display_name"] = username +280 if closed_after and not created_before: +281 params["time"] = closed_after +282 if created_before: +283 if not closed_after: +284 closed_after = "1970-01-01T00:00:00Z" +285 params["time"] = f"{closed_after},{created_before}" +286 if only_open: +287 params["open"] = 1 +288 if only_closed: +289 params["closed"] = 1 +290 +291 if params: +292 uri += "?" + urllib.parse.urlencode(params) +293 +294 data = self._session._get(uri) +295 changesets = cast(list[Element], dom.OsmResponseToDom(data, tag="changeset")) +296 result: dict[int, dict[str, Any]] = {} +297 for cur_changeset in changesets: +298 tmp_cs = dom.dom_parse_changeset(cur_changeset) +299 result[tmp_cs["id"]] = tmp_cs +300 return result +301 +302 def changeset_comment( +303 self: "OsmApi", changeset_id: int, comment: str +304 ) -> dict[str, Any]: +305 """ +306 Adds a comment to the changeset `changeset_id`. +307 +308 `comment` should be a string. +309 +310 Returns the updated changeset data dict. +311 +312 If no authentication information are provided, +313 `OsmApi.UsernamePasswordMissingError` is raised. +314 +315 If the changeset is already closed, +316 `OsmApi.ChangesetClosedApiError` is raised. +317 """ +318 params = urllib.parse.urlencode({"text": comment}) +319 try: +320 data = self._session._post( +321 f"/api/0.6/changeset/{changeset_id}/comment", +322 params, +323 forceAuth=True, +324 ) +325 except errors.ApiError as e: +326 if e.status == 409: +327 raise errors.ChangesetClosedApiError( +328 e.status, e.reason, e.payload +329 ) from e +330 else: +331 raise +332 changeset = cast( +333 Element, +334 dom.OsmResponseToDom(data, tag="changeset", single=True), +335 ) +336 return dom.dom_parse_changeset(changeset, include_discussion=False) +337 +338 def changeset_subscribe(self: "OsmApi", changeset_id: int) -> dict[str, Any]: +339 """ +340 Subscribe to the changeset `changeset_id`. +341 +342 Returns the updated changeset data dict. +343 +344 If no authentication information are provided, +345 `OsmApi.UsernamePasswordMissingError` is raised. +346 +347 If already subscribed to this changeset, +348 `OsmApi.AlreadySubscribedApiError` is raised. +349 """ +350 try: +351 data = self._session._post( +352 f"/api/0.6/changeset/{changeset_id}/subscribe", +353 None, +354 forceAuth=True, +355 ) +356 except errors.ApiError as e: +357 if e.status == 409: +358 raise errors.AlreadySubscribedApiError( +359 e.status, e.reason, e.payload +360 ) from e +361 else: +362 raise +363 changeset = cast( +364 Element, +365 dom.OsmResponseToDom(data, tag="changeset", single=True), +366 ) +367 return dom.dom_parse_changeset(changeset, include_discussion=False) +368 +369 def changeset_unsubscribe(self: "OsmApi", changeset_id: int) -> dict[str, Any]: +370 """ +371 Unsubscribe from the changeset `changeset_id`. +372 +373 Returns the updated changeset data dict. +374 +375 If no authentication information are provided, +376 `OsmApi.UsernamePasswordMissingError` is raised. +377 +378 If not subscribed to this changeset, +379 `OsmApi.NotSubscribedApiError` is raised. +380 """ +381 try: +382 data = self._session._post( +383 f"/api/0.6/changeset/{changeset_id}/unsubscribe", +384 None, +385 forceAuth=True, +386 ) +387 except errors.ApiError as e: +388 if e.status == 404: +389 raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e +390 else: +391 raise +392 changeset = cast( +393 Element, +394 dom.OsmResponseToDom(data, tag="changeset", single=True), +395 ) +396 return dom.dom_parse_changeset(changeset, include_discussion=False) +
20class ChangesetMixin: + 21 """Mixin providing changeset-related operations with pythonic method names.""" + 22 + 23 @contextmanager + 24 def changeset( + 25 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None + 26 ) -> Generator[int, None, None]: + 27 """ + 28 Context manager for a Changeset. + 29 + 30 It opens a Changeset, uploads the changes and closes the changeset + 31 when used with the `with` statement: + 32 + 33 #!python + 34 import osmapi + 35 + 36 with api.changeset({"comment": "Import script XYZ"}) as changeset_id: + 37 print(f"Part of changeset {changeset_id}") + 38 api.node_create({"lon":1, "lat":1, "tag": {}}) + 39 + 40 If `changeset_tags` are given, this tags are applied (key/value). + 41 + 42 Returns `changeset_id` + 43 + 44 If no authentication information are provided, + 45 `OsmApi.UsernamePasswordMissingError` is raised. + 46 + 47 If there is already an open changeset, + 48 `OsmApi.ChangesetAlreadyOpenError` is raised. + 49 """ + 50 if changeset_tags is None: + 51 changeset_tags = {} + 52 # Create a new changeset + 53 changeset_id = self.changeset_create(changeset_tags) + 54 yield changeset_id + 55 self.changeset_close() + 56 + 57 def changeset_get( + 58 self: "OsmApi", changeset_id: int, include_discussion: bool = False + 59 ) -> dict[str, Any]: + 60 """ + 61 Returns changeset with `changeset_id` as a dict. + 62 + 63 `changeset_id` is the unique identifier of a changeset. + 64 + 65 If `include_discussion` is set to `True` the changeset discussion + 66 will be available in the result. + 67 """ + 68 path = f"/api/0.6/changeset/{changeset_id}" + 69 if include_discussion: + 70 path = f"{path}?include_discussion=true" + 71 data = self._session._get(path) + 72 changeset = cast( + 73 Element, dom.OsmResponseToDom(data, tag="changeset", single=True) + 74 ) + 75 return dom.dom_parse_changeset(changeset, include_discussion=include_discussion) + 76 + 77 def changeset_update( + 78 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None + 79 ) -> int: + 80 """ + 81 Updates current changeset with `changeset_tags`. + 82 + 83 If no authentication information are provided, + 84 `OsmApi.UsernamePasswordMissingError` is raised. + 85 + 86 If there is no open changeset, + 87 `OsmApi.NoChangesetOpenError` is raised. + 88 + 89 If the changeset is already closed, + 90 `OsmApi.ChangesetClosedApiError` is raised. + 91 """ + 92 if changeset_tags is None: + 93 changeset_tags = {} + 94 if not self._current_changeset_id: + 95 raise errors.NoChangesetOpenError("No changeset currently opened") + 96 if "created_by" not in changeset_tags: + 97 changeset_tags["created_by"] = self._created_by + 98 try: + 99 self._session._put( +100 f"/api/0.6/changeset/{self._current_changeset_id}", +101 xmlbuilder._xml_build("changeset", {"tag": changeset_tags}, data=self), +102 return_value=False, +103 ) +104 except errors.ApiError as e: +105 if e.status == 409: +106 raise errors.ChangesetClosedApiError( +107 e.status, e.reason, e.payload +108 ) from e +109 else: +110 raise +111 return self._current_changeset_id +112 +113 def changeset_create( +114 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None +115 ) -> int: +116 """ +117 Opens a changeset. +118 +119 If `changeset_tags` are given, this tags are applied (key/value). +120 +121 Returns `changeset_id` +122 +123 If no authentication information are provided, +124 `OsmApi.UsernamePasswordMissingError` is raised. +125 +126 If there is already an open changeset, +127 `OsmApi.ChangesetAlreadyOpenError` is raised. +128 """ +129 if changeset_tags is None: +130 changeset_tags = {} +131 if self._current_changeset_id: +132 raise errors.ChangesetAlreadyOpenError("Changeset already opened") +133 if "created_by" not in changeset_tags: +134 changeset_tags["created_by"] = self._created_by +135 +136 # check if someone tries to create a test changeset to PROD +137 if ( +138 self._api == "https://www.openstreetmap.org" +139 and changeset_tags.get("comment") == "My first test" +140 ): +141 raise errors.OsmApiError( +142 "DO NOT CREATE test changesets on the production server" +143 ) +144 +145 result = self._session._put( +146 "/api/0.6/changeset/create", +147 xmlbuilder._xml_build("changeset", {"tag": changeset_tags}, data=self), +148 ) +149 self._current_changeset_id = int(result) +150 return self._current_changeset_id +151 +152 def changeset_close(self: "OsmApi") -> int: +153 """ +154 Closes current changeset. +155 +156 Returns `changeset_id`. +157 +158 If no authentication information are provided, +159 `OsmApi.UsernamePasswordMissingError` is raised. +160 +161 If there is no open changeset, +162 `OsmApi.NoChangesetOpenError` is raised. +163 +164 If the changeset is already closed, +165 `OsmApi.ChangesetClosedApiError` is raised. +166 """ +167 if not self._current_changeset_id: +168 raise errors.NoChangesetOpenError("No changeset currently opened") +169 try: +170 self._session._put( +171 f"/api/0.6/changeset/{self._current_changeset_id}/close", +172 None, +173 return_value=False, +174 ) +175 current_changeset_id = self._current_changeset_id +176 self._current_changeset_id = 0 +177 except errors.ApiError as e: +178 if e.status == 409: +179 raise errors.ChangesetClosedApiError( +180 e.status, e.reason, e.payload +181 ) from e +182 else: +183 raise +184 return current_changeset_id +185 +186 def changeset_upload( +187 self: "OsmApi", changes_data: list[dict[str, Any]] +188 ) -> list[dict[str, Any]]: +189 """ +190 Upload data with the `changes_data` list of dicts. +191 +192 Returns list with updated ids. +193 +194 If no authentication information are provided, +195 `OsmApi.UsernamePasswordMissingError` is raised. +196 +197 If the changeset is already closed, +198 `OsmApi.ChangesetClosedApiError` is raised. +199 """ +200 data = "" +201 data += '<?xml version="1.0" encoding="UTF-8"?>\n' +202 data += '<osmChange version="0.6" generator="' +203 data += self._created_by + '">\n' +204 for change in changes_data: +205 data += "<" + change["action"] + ">\n" +206 change_data = change["data"] +207 data += self._add_changeset_data(change_data, change["type"]) +208 data += "</" + change["action"] + ">\n" +209 data += "</osmChange>" +210 try: +211 response_data = self._session._post( +212 f"/api/0.6/changeset/{self._current_changeset_id}/upload", +213 data.encode("utf-8"), +214 forceAuth=True, +215 ) +216 except errors.ApiError as e: +217 if e.status == 409 and re.search( +218 r"The changeset .* was closed at .*", e.payload +219 ): +220 raise errors.ChangesetClosedApiError( +221 e.status, e.reason, e.payload +222 ) from e +223 else: +224 raise +225 try: +226 result_dom = xml.dom.minidom.parseString(response_data) +227 diff_result = result_dom.getElementsByTagName("diffResult")[0] +228 result_elements = [ +229 x for x in diff_result.childNodes if x.nodeType == x.ELEMENT_NODE +230 ] +231 except (xml.parsers.expat.ExpatError, IndexError) as e: +232 raise errors.XmlResponseInvalidError( +233 f"The XML response from the OSM API is invalid: {e!r}" +234 ) from e +235 +236 for change in changes_data: +237 if change["action"] == "delete": +238 for change_element in change["data"]: +239 change_element.pop("version") +240 else: +241 self._assign_id_and_version(result_elements, change["data"]) +242 +243 return changes_data +244 +245 def changeset_download(self: "OsmApi", changeset_id: int) -> list[dict[str, Any]]: +246 """ +247 Download data from changeset `changeset_id`. +248 +249 Returns list of dict with type, action, and data. +250 """ +251 uri = f"/api/0.6/changeset/{changeset_id}/download" +252 data = self._session._get(uri) +253 return parser.parse_osc(data) +254 +255 def changesets_get( # noqa: C901 +256 self: "OsmApi", +257 min_lon: Optional[float] = None, +258 min_lat: Optional[float] = None, +259 max_lon: Optional[float] = None, +260 max_lat: Optional[float] = None, +261 userid: Optional[int] = None, +262 username: Optional[str] = None, +263 closed_after: Optional[str] = None, +264 created_before: Optional[str] = None, +265 only_open: bool = False, +266 only_closed: bool = False, +267 ) -> dict[int, dict[str, Any]]: +268 """ +269 Returns a dict with the id of the changeset as key matching all criteria. +270 +271 All parameters are optional. +272 """ +273 uri = "/api/0.6/changesets" +274 params: dict[str, Any] = {} +275 if min_lon or min_lat or max_lon or max_lat: +276 params["bbox"] = f"{min_lon},{min_lat},{max_lon},{max_lat}" +277 if userid: +278 params["user"] = userid +279 if username: +280 params["display_name"] = username +281 if closed_after and not created_before: +282 params["time"] = closed_after +283 if created_before: +284 if not closed_after: +285 closed_after = "1970-01-01T00:00:00Z" +286 params["time"] = f"{closed_after},{created_before}" +287 if only_open: +288 params["open"] = 1 +289 if only_closed: +290 params["closed"] = 1 +291 +292 if params: +293 uri += "?" + urllib.parse.urlencode(params) +294 +295 data = self._session._get(uri) +296 changesets = cast(list[Element], dom.OsmResponseToDom(data, tag="changeset")) +297 result: dict[int, dict[str, Any]] = {} +298 for cur_changeset in changesets: +299 tmp_cs = dom.dom_parse_changeset(cur_changeset) +300 result[tmp_cs["id"]] = tmp_cs +301 return result +302 +303 def changeset_comment( +304 self: "OsmApi", changeset_id: int, comment: str +305 ) -> dict[str, Any]: +306 """ +307 Adds a comment to the changeset `changeset_id`. +308 +309 `comment` should be a string. +310 +311 Returns the updated changeset data dict. +312 +313 If no authentication information are provided, +314 `OsmApi.UsernamePasswordMissingError` is raised. +315 +316 If the changeset is already closed, +317 `OsmApi.ChangesetClosedApiError` is raised. +318 """ +319 params = urllib.parse.urlencode({"text": comment}) +320 try: +321 data = self._session._post( +322 f"/api/0.6/changeset/{changeset_id}/comment", +323 params, +324 forceAuth=True, +325 ) +326 except errors.ApiError as e: +327 if e.status == 409: +328 raise errors.ChangesetClosedApiError( +329 e.status, e.reason, e.payload +330 ) from e +331 else: +332 raise +333 changeset = cast( +334 Element, +335 dom.OsmResponseToDom(data, tag="changeset", single=True), +336 ) +337 return dom.dom_parse_changeset(changeset, include_discussion=False) +338 +339 def changeset_subscribe(self: "OsmApi", changeset_id: int) -> dict[str, Any]: +340 """ +341 Subscribe to the changeset `changeset_id`. +342 +343 Returns the updated changeset data dict. +344 +345 If no authentication information are provided, +346 `OsmApi.UsernamePasswordMissingError` is raised. +347 +348 If already subscribed to this changeset, +349 `OsmApi.AlreadySubscribedApiError` is raised. +350 """ +351 try: +352 data = self._session._post( +353 f"/api/0.6/changeset/{changeset_id}/subscribe", +354 None, +355 forceAuth=True, +356 ) +357 except errors.ApiError as e: +358 if e.status == 409: +359 raise errors.AlreadySubscribedApiError( +360 e.status, e.reason, e.payload +361 ) from e +362 else: +363 raise +364 changeset = cast( +365 Element, +366 dom.OsmResponseToDom(data, tag="changeset", single=True), +367 ) +368 return dom.dom_parse_changeset(changeset, include_discussion=False) +369 +370 def changeset_unsubscribe(self: "OsmApi", changeset_id: int) -> dict[str, Any]: +371 """ +372 Unsubscribe from the changeset `changeset_id`. +373 +374 Returns the updated changeset data dict. +375 +376 If no authentication information are provided, +377 `OsmApi.UsernamePasswordMissingError` is raised. +378 +379 If not subscribed to this changeset, +380 `OsmApi.NotSubscribedApiError` is raised. +381 """ +382 try: +383 data = self._session._post( +384 f"/api/0.6/changeset/{changeset_id}/unsubscribe", +385 None, +386 forceAuth=True, +387 ) +388 except errors.ApiError as e: +389 if e.status == 404: +390 raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e +391 else: +392 raise +393 changeset = cast( +394 Element, +395 dom.OsmResponseToDom(data, tag="changeset", single=True), +396 ) +397 return dom.dom_parse_changeset(changeset, include_discussion=False) +
Mixin providing changeset-related operations with pythonic method names.
+23 @contextmanager +24 def changeset( +25 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None +26 ) -> Generator[int, None, None]: +27 """ +28 Context manager for a Changeset. +29 +30 It opens a Changeset, uploads the changes and closes the changeset +31 when used with the `with` statement: +32 +33 #!python +34 import osmapi +35 +36 with api.changeset({"comment": "Import script XYZ"}) as changeset_id: +37 print(f"Part of changeset {changeset_id}") +38 api.node_create({"lon":1, "lat":1, "tag": {}}) +39 +40 If `changeset_tags` are given, this tags are applied (key/value). +41 +42 Returns `changeset_id` +43 +44 If no authentication information are provided, +45 `OsmApi.UsernamePasswordMissingError` is raised. +46 +47 If there is already an open changeset, +48 `OsmApi.ChangesetAlreadyOpenError` is raised. +49 """ +50 if changeset_tags is None: +51 changeset_tags = {} +52 # Create a new changeset +53 changeset_id = self.changeset_create(changeset_tags) +54 yield changeset_id +55 self.changeset_close() +
Context manager for a Changeset.
+ +It opens a Changeset, uploads the changes and closes the changeset
+when used with the with statement:
#!python
+import osmapi
+
+with api.changeset({"comment": "Import script XYZ"}) as changeset_id:
+ print(f"Part of changeset {changeset_id}")
+ api.node_create({"lon":1, "lat":1, "tag": {}})
+
+
+If changeset_tags are given, this tags are applied (key/value).
Returns changeset_id
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,
+OsmApi.ChangesetAlreadyOpenError is raised.
57 def changeset_get( +58 self: "OsmApi", changeset_id: int, include_discussion: bool = False +59 ) -> dict[str, Any]: +60 """ +61 Returns changeset with `changeset_id` as a dict. +62 +63 `changeset_id` is the unique identifier of a changeset. +64 +65 If `include_discussion` is set to `True` the changeset discussion +66 will be available in the result. +67 """ +68 path = f"/api/0.6/changeset/{changeset_id}" +69 if include_discussion: +70 path = f"{path}?include_discussion=true" +71 data = self._session._get(path) +72 changeset = cast( +73 Element, dom.OsmResponseToDom(data, tag="changeset", single=True) +74 ) +75 return dom.dom_parse_changeset(changeset, include_discussion=include_discussion) +
Returns changeset with changeset_id as a dict.
changeset_id is the unique identifier of a changeset.
If include_discussion is set to True the changeset discussion
+will be available in the result.
77 def changeset_update( + 78 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None + 79 ) -> int: + 80 """ + 81 Updates current changeset with `changeset_tags`. + 82 + 83 If no authentication information are provided, + 84 `OsmApi.UsernamePasswordMissingError` is raised. + 85 + 86 If there is no open changeset, + 87 `OsmApi.NoChangesetOpenError` is raised. + 88 + 89 If the changeset is already closed, + 90 `OsmApi.ChangesetClosedApiError` is raised. + 91 """ + 92 if changeset_tags is None: + 93 changeset_tags = {} + 94 if not self._current_changeset_id: + 95 raise errors.NoChangesetOpenError("No changeset currently opened") + 96 if "created_by" not in changeset_tags: + 97 changeset_tags["created_by"] = self._created_by + 98 try: + 99 self._session._put( +100 f"/api/0.6/changeset/{self._current_changeset_id}", +101 xmlbuilder._xml_build("changeset", {"tag": changeset_tags}, data=self), +102 return_value=False, +103 ) +104 except errors.ApiError as e: +105 if e.status == 409: +106 raise errors.ChangesetClosedApiError( +107 e.status, e.reason, e.payload +108 ) from e +109 else: +110 raise +111 return self._current_changeset_id +
Updates current changeset with changeset_tags.
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
113 def changeset_create( +114 self: "OsmApi", changeset_tags: Optional[dict[str, str]] = None +115 ) -> int: +116 """ +117 Opens a changeset. +118 +119 If `changeset_tags` are given, this tags are applied (key/value). +120 +121 Returns `changeset_id` +122 +123 If no authentication information are provided, +124 `OsmApi.UsernamePasswordMissingError` is raised. +125 +126 If there is already an open changeset, +127 `OsmApi.ChangesetAlreadyOpenError` is raised. +128 """ +129 if changeset_tags is None: +130 changeset_tags = {} +131 if self._current_changeset_id: +132 raise errors.ChangesetAlreadyOpenError("Changeset already opened") +133 if "created_by" not in changeset_tags: +134 changeset_tags["created_by"] = self._created_by +135 +136 # check if someone tries to create a test changeset to PROD +137 if ( +138 self._api == "https://www.openstreetmap.org" +139 and changeset_tags.get("comment") == "My first test" +140 ): +141 raise errors.OsmApiError( +142 "DO NOT CREATE test changesets on the production server" +143 ) +144 +145 result = self._session._put( +146 "/api/0.6/changeset/create", +147 xmlbuilder._xml_build("changeset", {"tag": changeset_tags}, data=self), +148 ) +149 self._current_changeset_id = int(result) +150 return self._current_changeset_id +
Opens a changeset.
+ +If changeset_tags are given, this tags are applied (key/value).
Returns changeset_id
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,
+OsmApi.ChangesetAlreadyOpenError is raised.
152 def changeset_close(self: "OsmApi") -> int: +153 """ +154 Closes current changeset. +155 +156 Returns `changeset_id`. +157 +158 If no authentication information are provided, +159 `OsmApi.UsernamePasswordMissingError` is raised. +160 +161 If there is no open changeset, +162 `OsmApi.NoChangesetOpenError` is raised. +163 +164 If the changeset is already closed, +165 `OsmApi.ChangesetClosedApiError` is raised. +166 """ +167 if not self._current_changeset_id: +168 raise errors.NoChangesetOpenError("No changeset currently opened") +169 try: +170 self._session._put( +171 f"/api/0.6/changeset/{self._current_changeset_id}/close", +172 None, +173 return_value=False, +174 ) +175 current_changeset_id = self._current_changeset_id +176 self._current_changeset_id = 0 +177 except errors.ApiError as e: +178 if e.status == 409: +179 raise errors.ChangesetClosedApiError( +180 e.status, e.reason, e.payload +181 ) from e +182 else: +183 raise +184 return current_changeset_id +
Closes current changeset.
+ +Returns changeset_id.
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
186 def changeset_upload( +187 self: "OsmApi", changes_data: list[dict[str, Any]] +188 ) -> list[dict[str, Any]]: +189 """ +190 Upload data with the `changes_data` list of dicts. +191 +192 Returns list with updated ids. +193 +194 If no authentication information are provided, +195 `OsmApi.UsernamePasswordMissingError` is raised. +196 +197 If the changeset is already closed, +198 `OsmApi.ChangesetClosedApiError` is raised. +199 """ +200 data = "" +201 data += '<?xml version="1.0" encoding="UTF-8"?>\n' +202 data += '<osmChange version="0.6" generator="' +203 data += self._created_by + '">\n' +204 for change in changes_data: +205 data += "<" + change["action"] + ">\n" +206 change_data = change["data"] +207 data += self._add_changeset_data(change_data, change["type"]) +208 data += "</" + change["action"] + ">\n" +209 data += "</osmChange>" +210 try: +211 response_data = self._session._post( +212 f"/api/0.6/changeset/{self._current_changeset_id}/upload", +213 data.encode("utf-8"), +214 forceAuth=True, +215 ) +216 except errors.ApiError as e: +217 if e.status == 409 and re.search( +218 r"The changeset .* was closed at .*", e.payload +219 ): +220 raise errors.ChangesetClosedApiError( +221 e.status, e.reason, e.payload +222 ) from e +223 else: +224 raise +225 try: +226 result_dom = xml.dom.minidom.parseString(response_data) +227 diff_result = result_dom.getElementsByTagName("diffResult")[0] +228 result_elements = [ +229 x for x in diff_result.childNodes if x.nodeType == x.ELEMENT_NODE +230 ] +231 except (xml.parsers.expat.ExpatError, IndexError) as e: +232 raise errors.XmlResponseInvalidError( +233 f"The XML response from the OSM API is invalid: {e!r}" +234 ) from e +235 +236 for change in changes_data: +237 if change["action"] == "delete": +238 for change_element in change["data"]: +239 change_element.pop("version") +240 else: +241 self._assign_id_and_version(result_elements, change["data"]) +242 +243 return changes_data +
Upload data with the changes_data list of dicts.
Returns list with updated ids.
+ +If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
245 def changeset_download(self: "OsmApi", changeset_id: int) -> list[dict[str, Any]]: +246 """ +247 Download data from changeset `changeset_id`. +248 +249 Returns list of dict with type, action, and data. +250 """ +251 uri = f"/api/0.6/changeset/{changeset_id}/download" +252 data = self._session._get(uri) +253 return parser.parse_osc(data) +
Download data from changeset changeset_id.
Returns list of dict with type, action, and data.
+255 def changesets_get( # noqa: C901 +256 self: "OsmApi", +257 min_lon: Optional[float] = None, +258 min_lat: Optional[float] = None, +259 max_lon: Optional[float] = None, +260 max_lat: Optional[float] = None, +261 userid: Optional[int] = None, +262 username: Optional[str] = None, +263 closed_after: Optional[str] = None, +264 created_before: Optional[str] = None, +265 only_open: bool = False, +266 only_closed: bool = False, +267 ) -> dict[int, dict[str, Any]]: +268 """ +269 Returns a dict with the id of the changeset as key matching all criteria. +270 +271 All parameters are optional. +272 """ +273 uri = "/api/0.6/changesets" +274 params: dict[str, Any] = {} +275 if min_lon or min_lat or max_lon or max_lat: +276 params["bbox"] = f"{min_lon},{min_lat},{max_lon},{max_lat}" +277 if userid: +278 params["user"] = userid +279 if username: +280 params["display_name"] = username +281 if closed_after and not created_before: +282 params["time"] = closed_after +283 if created_before: +284 if not closed_after: +285 closed_after = "1970-01-01T00:00:00Z" +286 params["time"] = f"{closed_after},{created_before}" +287 if only_open: +288 params["open"] = 1 +289 if only_closed: +290 params["closed"] = 1 +291 +292 if params: +293 uri += "?" + urllib.parse.urlencode(params) +294 +295 data = self._session._get(uri) +296 changesets = cast(list[Element], dom.OsmResponseToDom(data, tag="changeset")) +297 result: dict[int, dict[str, Any]] = {} +298 for cur_changeset in changesets: +299 tmp_cs = dom.dom_parse_changeset(cur_changeset) +300 result[tmp_cs["id"]] = tmp_cs +301 return result +
Returns a dict with the id of the changeset as key matching all criteria.
+ +All parameters are optional.
+303 def changeset_comment( +304 self: "OsmApi", changeset_id: int, comment: str +305 ) -> dict[str, Any]: +306 """ +307 Adds a comment to the changeset `changeset_id`. +308 +309 `comment` should be a string. +310 +311 Returns the updated changeset data dict. +312 +313 If no authentication information are provided, +314 `OsmApi.UsernamePasswordMissingError` is raised. +315 +316 If the changeset is already closed, +317 `OsmApi.ChangesetClosedApiError` is raised. +318 """ +319 params = urllib.parse.urlencode({"text": comment}) +320 try: +321 data = self._session._post( +322 f"/api/0.6/changeset/{changeset_id}/comment", +323 params, +324 forceAuth=True, +325 ) +326 except errors.ApiError as e: +327 if e.status == 409: +328 raise errors.ChangesetClosedApiError( +329 e.status, e.reason, e.payload +330 ) from e +331 else: +332 raise +333 changeset = cast( +334 Element, +335 dom.OsmResponseToDom(data, tag="changeset", single=True), +336 ) +337 return dom.dom_parse_changeset(changeset, include_discussion=False) +
Adds a comment to the changeset changeset_id.
comment should be a string.
Returns the updated changeset data dict.
+ +If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
339 def changeset_subscribe(self: "OsmApi", changeset_id: int) -> dict[str, Any]: +340 """ +341 Subscribe to the changeset `changeset_id`. +342 +343 Returns the updated changeset data dict. +344 +345 If no authentication information are provided, +346 `OsmApi.UsernamePasswordMissingError` is raised. +347 +348 If already subscribed to this changeset, +349 `OsmApi.AlreadySubscribedApiError` is raised. +350 """ +351 try: +352 data = self._session._post( +353 f"/api/0.6/changeset/{changeset_id}/subscribe", +354 None, +355 forceAuth=True, +356 ) +357 except errors.ApiError as e: +358 if e.status == 409: +359 raise errors.AlreadySubscribedApiError( +360 e.status, e.reason, e.payload +361 ) from e +362 else: +363 raise +364 changeset = cast( +365 Element, +366 dom.OsmResponseToDom(data, tag="changeset", single=True), +367 ) +368 return dom.dom_parse_changeset(changeset, include_discussion=False) +
Subscribe to the changeset changeset_id.
Returns the updated changeset data dict.
+ +If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If already subscribed to this changeset,
+OsmApi.AlreadySubscribedApiError is raised.
370 def changeset_unsubscribe(self: "OsmApi", changeset_id: int) -> dict[str, Any]: +371 """ +372 Unsubscribe from the changeset `changeset_id`. +373 +374 Returns the updated changeset data dict. +375 +376 If no authentication information are provided, +377 `OsmApi.UsernamePasswordMissingError` is raised. +378 +379 If not subscribed to this changeset, +380 `OsmApi.NotSubscribedApiError` is raised. +381 """ +382 try: +383 data = self._session._post( +384 f"/api/0.6/changeset/{changeset_id}/unsubscribe", +385 None, +386 forceAuth=True, +387 ) +388 except errors.ApiError as e: +389 if e.status == 404: +390 raise errors.NotSubscribedApiError(e.status, e.reason, e.payload) from e +391 else: +392 raise +393 changeset = cast( +394 Element, +395 dom.OsmResponseToDom(data, tag="changeset", single=True), +396 ) +397 return dom.dom_parse_changeset(changeset, include_discussion=False) +
Unsubscribe from the changeset changeset_id.
Returns the updated changeset data dict.
+ +If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If not subscribed to this changeset,
+OsmApi.NotSubscribedApiError is raised.
from datetime import datetime -import xml.dom.minidom -import xml.parsers.expat -import logging - -from . import errors -from . import xmlbuilder - - -logger = logging.getLogger(__name__) - - -def OsmResponseToDom(response, tag, single=False, allow_empty=False): - """ - Returns the (sub-) DOM parsed from an OSM response - """ - try: - dom = xml.dom.minidom.parseString(response) - osm_dom = dom.getElementsByTagName("osm")[0] - all_data = osm_dom.getElementsByTagName(tag) - first_element = all_data[0] - except IndexError as e: - if allow_empty: - return [] - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) - except xml.parsers.expat.ExpatError as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) - - if single: - return first_element - return all_data - - -def DomParseNode(DomElement): - """ - Returns NodeData for the node. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - return result - - -def DomParseWay(DomElement): - """ - Returns WayData for the way. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - result["nd"] = _DomGetNd(DomElement) - return result - - -def DomParseRelation(DomElement): - """ - Returns RelationData for the relation. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - result["member"] = _DomGetMember(DomElement) - return result - - -def DomParseChangeset(DomElement, include_discussion=False): - """ - Returns ChangesetData for the changeset. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - if include_discussion: - result["discussion"] = _DomGetDiscussion(DomElement) - - return result - - -def DomParseNote(DomElement): - """ - Returns NoteData for the note. - """ - result = _DomGetAttributes(DomElement) - result["id"] = xmlbuilder._GetXmlValue(DomElement, "id") - result["status"] = xmlbuilder._GetXmlValue(DomElement, "status") - - result["date_created"] = _ParseDate( - xmlbuilder._GetXmlValue(DomElement, "date_created") - ) - result["date_closed"] = _ParseDate( - xmlbuilder._GetXmlValue(DomElement, "date_closed") - ) - result["comments"] = _DomGetComments(DomElement) - - return result - - -def _DomGetAttributes(DomElement): - """ - Returns a formated dictionnary of attributes of a DomElement. - """ - - def is_true(v): - return v == "true" - - attribute_mapping = { - "uid": int, - "changeset": int, - "version": int, - "id": int, - "lat": float, - "lon": float, - "open": is_true, - "visible": is_true, - "ref": int, - "comments_count": int, - "timestamp": _ParseDate, - "created_at": _ParseDate, - "closed_at": _ParseDate, - "date": _ParseDate, - } - result = {} - for k, v in DomElement.attributes.items(): - try: - result[k] = attribute_mapping[k](v) - except KeyError: - result[k] = v - return result - - -def _DomGetTag(DomElement): - """ - Returns the dictionnary of tags of a DomElement. - """ - result = {} - for t in DomElement.getElementsByTagName("tag"): - k = t.attributes["k"].value - v = t.attributes["v"].value - result[k] = v - return result - - -def _DomGetNd(DomElement): - """ - Returns the list of nodes of a DomElement. - """ - result = [] - for t in DomElement.getElementsByTagName("nd"): - result.append(int(int(t.attributes["ref"].value))) - return result - - -def _DomGetDiscussion(DomElement): - """ - Returns the dictionnary of comments of a DomElement. - """ - result = [] - try: - discussion = DomElement.getElementsByTagName("discussion")[0] - for t in discussion.getElementsByTagName("comment"): - comment = _DomGetAttributes(t) - comment["text"] = xmlbuilder._GetXmlValue(t, "text") - result.append(comment) - except IndexError: - pass - return result - - -def _DomGetComments(DomElement): - """ - Returns the list of comments of a DomElement. - """ - result = [] - for t in DomElement.getElementsByTagName("comment"): - comment = {} - comment["date"] = _ParseDate(xmlbuilder._GetXmlValue(t, "date")) - comment["action"] = xmlbuilder._GetXmlValue(t, "action") - comment["text"] = xmlbuilder._GetXmlValue(t, "text") - comment["html"] = xmlbuilder._GetXmlValue(t, "html") - comment["uid"] = xmlbuilder._GetXmlValue(t, "uid") - comment["user"] = xmlbuilder._GetXmlValue(t, "user") - result.append(comment) - return result - - -def _DomGetMember(DomElement): - """ - Returns a list of relation members. - """ - result = [] - for m in DomElement.getElementsByTagName("member"): - result.append(_DomGetAttributes(m)) - return result - - -def _ParseDate(DateString): - date_formats = ["%Y-%m-%d %H:%M:%S UTC", "%Y-%m-%dT%H:%M:%SZ"] - for date_format in date_formats: - try: - result = datetime.strptime(DateString, date_format) - return result - except (ValueError, TypeError): - logger.debug(f"{DateString} does not match {date_format}") - - return DateString -
DOM parsing for the OpenStreetMap API.
+1""" + 2DOM parsing for the OpenStreetMap API. + 3""" + 4 + 5from datetime import datetime + 6import xml.dom.minidom + 7import xml.parsers.expat + 8import logging + 9from typing import Any, Union, Optional + 10from xml.dom.minidom import Element + 11 + 12from . import errors + 13from . import xmlbuilder + 14 + 15logger = logging.getLogger(__name__) + 16 + 17 + 18def OsmResponseToDom( + 19 response: bytes, tag: str, single: bool = False, allow_empty: bool = False + 20) -> Union[Element, list[Element]]: + 21 """ + 22 Returns the (sub-) DOM parsed from an OSM response + 23 """ + 24 try: + 25 dom = xml.dom.minidom.parseString(response) + 26 osm_dom = dom.getElementsByTagName("osm")[0] + 27 all_data = osm_dom.getElementsByTagName(tag) + 28 first_element = all_data[0] + 29 except IndexError as e: + 30 if allow_empty: + 31 return [] + 32 raise errors.XmlResponseInvalidError( + 33 f"The XML response from the OSM API is invalid: {e!r}" + 34 ) + 35 except xml.parsers.expat.ExpatError as e: + 36 raise errors.XmlResponseInvalidError( + 37 f"The XML response from the OSM API is invalid: {e!r}" + 38 ) + 39 + 40 if single: + 41 return first_element + 42 return list(all_data) + 43 + 44 + 45def dom_parse_node(dom_element: Element) -> dict[str, Any]: + 46 """ + 47 Returns NodeData for the node. + 48 """ + 49 result = _dom_get_attributes(dom_element) + 50 result["tag"] = _dom_get_tag(dom_element) + 51 return result + 52 + 53 + 54def dom_parse_way(dom_element: Element) -> dict[str, Any]: + 55 """ + 56 Returns WayData for the way. + 57 """ + 58 result = _dom_get_attributes(dom_element) + 59 result["tag"] = _dom_get_tag(dom_element) + 60 result["nd"] = _dom_get_nd(dom_element) + 61 return result + 62 + 63 + 64def dom_parse_relation(dom_element: Element) -> dict[str, Any]: + 65 """ + 66 Returns RelationData for the relation. + 67 """ + 68 result = _dom_get_attributes(dom_element) + 69 result["tag"] = _dom_get_tag(dom_element) + 70 result["member"] = _dom_get_member(dom_element) + 71 return result + 72 + 73 + 74def dom_parse_changeset( + 75 dom_element: Element, include_discussion: bool = False + 76) -> dict[str, Any]: + 77 """ + 78 Returns ChangesetData for the changeset. + 79 """ + 80 result = _dom_get_attributes(dom_element) + 81 result["tag"] = _dom_get_tag(dom_element) + 82 if include_discussion: + 83 result["discussion"] = _dom_get_discussion(dom_element) + 84 + 85 return result + 86 + 87 + 88def dom_parse_note(dom_element: Element) -> dict[str, Any]: + 89 """ + 90 Returns NoteData for the note. + 91 """ + 92 result = _dom_get_attributes(dom_element) + 93 result["id"] = xmlbuilder._get_xml_value(dom_element, "id") + 94 result["status"] = xmlbuilder._get_xml_value(dom_element, "status") + 95 + 96 result["date_created"] = _parse_date( + 97 xmlbuilder._get_xml_value(dom_element, "date_created") + 98 ) + 99 result["date_closed"] = _parse_date( +100 xmlbuilder._get_xml_value(dom_element, "date_closed") +101 ) +102 result["comments"] = _dom_get_comments(dom_element) +103 +104 return result +105 +106 +107def _dom_get_attributes(dom_element: Element) -> dict[str, Any]: +108 """ +109 Returns a formated dictionnary of attributes of a dom_element. +110 """ +111 +112 def is_true(v: str) -> bool: +113 return v == "true" +114 +115 attribute_mapping: dict[str, Any] = { +116 "uid": int, +117 "changeset": int, +118 "version": int, +119 "id": int, +120 "lat": float, +121 "lon": float, +122 "open": is_true, +123 "visible": is_true, +124 "ref": int, +125 "comments_count": int, +126 "timestamp": _parse_date, +127 "created_at": _parse_date, +128 "closed_at": _parse_date, +129 "date": _parse_date, +130 } +131 result: dict[str, Any] = {} +132 for k, v in dom_element.attributes.items(): +133 try: +134 result[k] = attribute_mapping[k](v) +135 except KeyError: +136 result[k] = v +137 return result +138 +139 +140def _dom_get_tag(dom_element: Element) -> dict[str, str]: +141 """ +142 Returns the dictionnary of tags of a dom_element. +143 """ +144 result: dict[str, str] = {} +145 for t in dom_element.getElementsByTagName("tag"): +146 k = t.attributes["k"].value +147 v = t.attributes["v"].value +148 result[k] = v +149 return result +150 +151 +152def _dom_get_nd(dom_element: Element) -> list[int]: +153 """ +154 Returns the list of nodes of a dom_element. +155 """ +156 result: list[int] = [] +157 for t in dom_element.getElementsByTagName("nd"): +158 result.append(int(int(t.attributes["ref"].value))) +159 return result +160 +161 +162def _dom_get_discussion(dom_element: Element) -> list[dict[str, Any]]: +163 """ +164 Returns the dictionnary of comments of a dom_element. +165 """ +166 result: list[dict[str, Any]] = [] +167 try: +168 discussion = dom_element.getElementsByTagName("discussion")[0] +169 for t in discussion.getElementsByTagName("comment"): +170 comment = _dom_get_attributes(t) +171 comment["text"] = xmlbuilder._get_xml_value(t, "text") +172 result.append(comment) +173 except IndexError: +174 pass +175 return result +176 +177 +178def _dom_get_comments(dom_element: Element) -> list[dict[str, Any]]: +179 """ +180 Returns the list of comments of a dom_element. +181 """ +182 result: list[dict[str, Any]] = [] +183 for t in dom_element.getElementsByTagName("comment"): +184 comment: dict[str, Any] = {} +185 comment["date"] = _parse_date(xmlbuilder._get_xml_value(t, "date")) +186 comment["action"] = xmlbuilder._get_xml_value(t, "action") +187 comment["text"] = xmlbuilder._get_xml_value(t, "text") +188 comment["html"] = xmlbuilder._get_xml_value(t, "html") +189 comment["uid"] = xmlbuilder._get_xml_value(t, "uid") +190 comment["user"] = xmlbuilder._get_xml_value(t, "user") +191 result.append(comment) +192 return result +193 +194 +195def _dom_get_member(dom_element: Element) -> list[dict[str, Any]]: +196 """ +197 Returns a list of relation members. +198 """ +199 result: list[dict[str, Any]] = [] +200 for m in dom_element.getElementsByTagName("member"): +201 result.append(_dom_get_attributes(m)) +202 return result +203 +204 +205def _parse_date(date_string: Optional[str]) -> Union[datetime, str, None]: +206 date_formats = ["%Y-%m-%d %H:%M:%S UTC", "%Y-%m-%dT%H:%M:%SZ"] +207 for date_format in date_formats: +208 try: +209 result = datetime.strptime(date_string, date_format) # type: ignore[arg-type] # noqa: E501 +210 return result +211 except (ValueError, TypeError): +212 logger.debug(f"{date_string} does not match {date_format}") +213 +214 return date_string +
19def OsmResponseToDom( +20 response: bytes, tag: str, single: bool = False, allow_empty: bool = False +21) -> Union[Element, list[Element]]: +22 """ +23 Returns the (sub-) DOM parsed from an OSM response +24 """ +25 try: +26 dom = xml.dom.minidom.parseString(response) +27 osm_dom = dom.getElementsByTagName("osm")[0] +28 all_data = osm_dom.getElementsByTagName(tag) +29 first_element = all_data[0] +30 except IndexError as e: +31 if allow_empty: +32 return [] +33 raise errors.XmlResponseInvalidError( +34 f"The XML response from the OSM API is invalid: {e!r}" +35 ) +36 except xml.parsers.expat.ExpatError as e: +37 raise errors.XmlResponseInvalidError( +38 f"The XML response from the OSM API is invalid: {e!r}" +39 ) +40 +41 if single: +42 return first_element +43 return list(all_data) +
def OsmResponseToDom(response, tag, single=False, allow_empty=False): - """ - Returns the (sub-) DOM parsed from an OSM response - """ - try: - dom = xml.dom.minidom.parseString(response) - osm_dom = dom.getElementsByTagName("osm")[0] - all_data = osm_dom.getElementsByTagName(tag) - first_element = all_data[0] - except IndexError as e: - if allow_empty: - return [] - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) - except xml.parsers.expat.ExpatError as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) - - if single: - return first_element - return all_data -
Returns the (sub-) DOM parsed from an OSM response
def DomParseNode(DomElement): - """ - Returns NodeData for the node. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - return result -
46def dom_parse_node(dom_element: Element) -> dict[str, Any]: +47 """ +48 Returns NodeData for the node. +49 """ +50 result = _dom_get_attributes(dom_element) +51 result["tag"] = _dom_get_tag(dom_element) +52 return result +
Returns NodeData for the node.
def DomParseWay(DomElement): - """ - Returns WayData for the way. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - result["nd"] = _DomGetNd(DomElement) - return result -
55def dom_parse_way(dom_element: Element) -> dict[str, Any]: +56 """ +57 Returns WayData for the way. +58 """ +59 result = _dom_get_attributes(dom_element) +60 result["tag"] = _dom_get_tag(dom_element) +61 result["nd"] = _dom_get_nd(dom_element) +62 return result +
Returns WayData for the way.
def DomParseRelation(DomElement): - """ - Returns RelationData for the relation. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - result["member"] = _DomGetMember(DomElement) - return result -
65def dom_parse_relation(dom_element: Element) -> dict[str, Any]: +66 """ +67 Returns RelationData for the relation. +68 """ +69 result = _dom_get_attributes(dom_element) +70 result["tag"] = _dom_get_tag(dom_element) +71 result["member"] = _dom_get_member(dom_element) +72 return result +
Returns RelationData for the relation.
def DomParseChangeset(DomElement, include_discussion=False): - """ - Returns ChangesetData for the changeset. - """ - result = _DomGetAttributes(DomElement) - result["tag"] = _DomGetTag(DomElement) - if include_discussion: - result["discussion"] = _DomGetDiscussion(DomElement) + - return result -
75def dom_parse_changeset( +76 dom_element: Element, include_discussion: bool = False +77) -> dict[str, Any]: +78 """ +79 Returns ChangesetData for the changeset. +80 """ +81 result = _dom_get_attributes(dom_element) +82 result["tag"] = _dom_get_tag(dom_element) +83 if include_discussion: +84 result["discussion"] = _dom_get_discussion(dom_element) +85 +86 return result +
Returns ChangesetData for the changeset.
89def dom_parse_note(dom_element: Element) -> dict[str, Any]: + 90 """ + 91 Returns NoteData for the note. + 92 """ + 93 result = _dom_get_attributes(dom_element) + 94 result["id"] = xmlbuilder._get_xml_value(dom_element, "id") + 95 result["status"] = xmlbuilder._get_xml_value(dom_element, "status") + 96 + 97 result["date_created"] = _parse_date( + 98 xmlbuilder._get_xml_value(dom_element, "date_created") + 99 ) +100 result["date_closed"] = _parse_date( +101 xmlbuilder._get_xml_value(dom_element, "date_closed") +102 ) +103 result["comments"] = _dom_get_comments(dom_element) +104 +105 return result +
def DomParseNote(DomElement): - """ - Returns NoteData for the note. - """ - result = _DomGetAttributes(DomElement) - result["id"] = xmlbuilder._GetXmlValue(DomElement, "id") - result["status"] = xmlbuilder._GetXmlValue(DomElement, "status") - - result["date_created"] = _ParseDate( - xmlbuilder._GetXmlValue(DomElement, "date_created") - ) - result["date_closed"] = _ParseDate( - xmlbuilder._GetXmlValue(DomElement, "date_closed") - ) - result["comments"] = _DomGetComments(DomElement) - - return result -
Returns NoteData for the note.
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 f"Request failed: {self.status} - {self.reason} - {self.payload}" - - -class UnauthorizedApiError(ApiError): - """ - Error when the API returned an Unauthorized error, - e.g. when the provided OAuth token is expired - """ - - pass - - -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 ElementNotFoundApiError(ApiError): - """ - Error if the the requested element was not found - """ - - -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 NoteAlreadyClosedApiError(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 - """ - - -class TimeoutApiError(ApiError): - """ - Error if the http request ran into a timeout - """ - +-class ConnectionApiError(ApiError): - """ - Error if there was a network error (e.g. DNS failure, refused connection) - while connecting to the remote server. - """ -Error classes for the OpenStreetMap API.
+
1""" + 2Error classes for the OpenStreetMap API.""" + 3 + 4from typing import Any + 5 + 6 + 7class OsmApiError(Exception): + 8 """ + 9 General OsmApi error class to provide a superclass for all other errors + 10 """ + 11 + 12 + 13class MaximumRetryLimitReachedError(OsmApiError): + 14 """ + 15 Error when the maximum amount of retries is reached and we have to give up + 16 """ + 17 + 18 + 19class UsernamePasswordMissingError(OsmApiError): + 20 """ + 21 Error when username or password is missing for an authenticated request + 22 """ + 23 + 24 pass + 25 + 26 + 27class NoChangesetOpenError(OsmApiError): + 28 """ + 29 Error when an operation requires an open changeset, but currently + 30 no changeset _is_ open + 31 """ + 32 + 33 pass + 34 + 35 + 36class ChangesetAlreadyOpenError(OsmApiError): + 37 """ + 38 Error when a user tries to open a changeset when there is already + 39 an open changeset + 40 """ + 41 + 42 pass + 43 + 44 + 45class OsmTypeAlreadyExistsError(OsmApiError): + 46 """ + 47 Error when a user tries to create an object that already exsits + 48 """ + 49 + 50 pass + 51 + 52 + 53class XmlResponseInvalidError(OsmApiError): + 54 """ + 55 Error if the XML response from the OpenStreetMap API is invalid + 56 """ + 57 + 58 + 59class ApiError(OsmApiError): + 60 """ + 61 Error class, is thrown when an API request fails + 62 """ + 63 + 64 def __init__(self, status: int, reason: str, payload: Any) -> None: + 65 self.status = status + 66 """HTTP error code""" + 67 + 68 self.reason = reason + 69 """Error message""" + 70 + 71 self.payload = payload + 72 """Payload of API when this error occured""" + 73 + 74 def __str__(self) -> str: + 75 return f"Request failed: {self.status} - {self.reason} - {self.payload}" + 76 + 77 + 78class UnauthorizedApiError(ApiError): + 79 """ + 80 Error when the API returned an Unauthorized error, + 81 e.g. when the provided OAuth token is expired + 82 """ + 83 + 84 pass + 85 + 86 + 87class AlreadySubscribedApiError(ApiError): + 88 """ + 89 Error when a user tries to subscribe to a changeset + 90 that she is already subscribed to + 91 """ + 92 + 93 pass + 94 + 95 + 96class NotSubscribedApiError(ApiError): + 97 """ + 98 Error when user tries to unsubscribe from a changeset + 99 that he is not subscribed to +100 """ +101 +102 pass +103 +104 +105class ElementDeletedApiError(ApiError): +106 """ +107 Error when the requested element is deleted +108 """ +109 +110 pass +111 +112 +113class ElementNotFoundApiError(ApiError): +114 """ +115 Error if the the requested element was not found +116 """ +117 +118 +119class ResponseEmptyApiError(ApiError): +120 """ +121 Error when the response to the request is empty +122 """ +123 +124 pass +125 +126 +127class ChangesetClosedApiError(ApiError): +128 """ +129 Error if the the changeset in question has already been closed +130 """ +131 +132 +133class NoteAlreadyClosedApiError(ApiError): +134 """ +135 Error if the the note in question has already been closed +136 """ +137 +138 +139class VersionMismatchApiError(ApiError): +140 """ +141 Error if the provided version does not match the database version +142 of the element +143 """ +144 +145 +146class PreconditionFailedApiError(ApiError): +147 """ +148 Error if the precondition of the operation was not met: +149 - When a way has nodes that do not exist or are not visible +150 - When a relation has elements that do not exist or are not visible +151 - When a node/way/relation is still used in a way/relation +152 """ +153 +154 +155class TimeoutApiError(ApiError): +156 """ +157 Error if the http request ran into a timeout +158 """ +159 +160 +161class ConnectionApiError(ApiError): +162 """ +163 Error if there was a network error (e.g. DNS failure, refused connection) +164 while connecting to the remote server. +165 """ +
class OsmApiError(Exception): - """ - General OsmApi error class to provide a superclass for all other errors - """ -
8class OsmApiError(Exception): + 9 """ +10 General OsmApi error class to provide a superclass for all other errors +11 """ +
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 - """ -
14class MaximumRetryLimitReachedError(OsmApiError): +15 """ +16 Error when the maximum amount of retries is reached and we have to give up +17 """ +
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 -
20class UsernamePasswordMissingError(OsmApiError): +21 """ +22 Error when username or password is missing for an authenticated request +23 """ +24 +25 pass +
Error when username or password is missing for an authenticated request
class NoChangesetOpenError(OsmApiError): - """ - Error when an operation requires an open changeset, but currently - no changeset _is_ open - """ - - pass -
28class NoChangesetOpenError(OsmApiError): +29 """ +30 Error when an operation requires an open changeset, but currently +31 no changeset _is_ open +32 """ +33 +34 pass +
Error when an operation requires an open changeset, but currently no changeset _is_ open
@@ -508,26 +513,25 @@class ChangesetAlreadyOpenError(OsmApiError): - """ - Error when a user tries to open a changeset when there is already - an open changeset - """ - - pass -
37class ChangesetAlreadyOpenError(OsmApiError): +38 """ +39 Error when a user tries to open a changeset when there is already +40 an open changeset +41 """ +42 +43 pass +
Error when a user tries to open a changeset when there is already an open changeset
@@ -551,25 +555,24 @@class OsmTypeAlreadyExistsError(OsmApiError): - """ - Error when a user tries to create an object that already exsits - """ + - pass -
46class OsmTypeAlreadyExistsError(OsmApiError): +47 """ +48 Error when a user tries to create an object that already exsits +49 """ +50 +51 pass +
Error when a user tries to create an object that already exsits
class XmlResponseInvalidError(OsmApiError): - """ - Error if the XML response from the OpenStreetMap API is invalid - """ -
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""" + ++ + class + ApiError+(OsmApiError): - self.reason = reason - """Error message""" + - self.payload = payload - """Payload of API when this error occured""" - - def __str__(self): - return f"Request failed: {self.status} - {self.reason} - {self.payload}" -
60class ApiError(OsmApiError): +61 """ +62 Error class, is thrown when an API request fails +63 """ +64 +65 def __init__(self, status: int, reason: str, payload: Any) -> None: +66 self.status = status +67 """HTTP error code""" +68 +69 self.reason = reason +70 """Error message""" +71 +72 self.payload = payload +73 """Payload of API when this error occured""" +74 +75 def __str__(self) -> str: +76 return f"Request failed: {self.status} - {self.reason} - {self.payload}" +
Error class, is thrown when an API request fails
Payload of API when this error occured
class AlreadySubscribedApiError(ApiError): - """ - Error when a user tries to subscribe to a changeset - that she is already subscribed to - """ - - pass -
88class AlreadySubscribedApiError(ApiError): +89 """ +90 Error when a user tries to subscribe to a changeset +91 that she is already subscribed to +92 """ +93 +94 pass +
Error when a user tries to subscribe to a changeset that she is already subscribed to
@@ -829,26 +834,25 @@class NotSubscribedApiError(ApiError): - """ - Error when user tries to unsubscribe from a changeset - that he is not subscribed to - """ + - pass -
97class NotSubscribedApiError(ApiError): + 98 """ + 99 Error when user tries to unsubscribe from a changeset +100 that he is not subscribed to +101 """ +102 +103 pass +
Error when user tries to unsubscribe from a changeset that he is not subscribed to
@@ -875,25 +879,24 @@class ElementDeletedApiError(ApiError): - """ - Error when the requested element is deleted - """ + - pass -
106class ElementDeletedApiError(ApiError): +107 """ +108 Error when the requested element is deleted +109 """ +110 +111 pass +
Error when the requested element is deleted
class ElementNotFoundApiError(ApiError): - """ - Error if the the requested element was not found - """ -
114class ElementNotFoundApiError(ApiError): +115 """ +116 Error if the the requested element was not found +117 """ +
Error if the the requested element was not found
class ResponseEmptyApiError(ApiError): - """ - Error when the response to the request is empty - """ + - pass -
120class ResponseEmptyApiError(ApiError): +121 """ +122 Error when the response to the request is empty +123 """ +124 +125 pass +
Error when the response to the request is empty
class ChangesetClosedApiError(ApiError): - """ - Error if the the changeset in question has already been closed - """ -
128class ChangesetClosedApiError(ApiError): +129 """ +130 Error if the the changeset in question has already been closed +131 """ +
Error if the the changeset in question has already been closed
class NoteAlreadyClosedApiError(ApiError): - """ - Error if the the note in question has already been closed - """ -
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 - """ -
Error if the provided version does not match the database version of the element
@@ -1133,26 +1131,25 @@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 - """ -
147class PreconditionFailedApiError(ApiError): +148 """ +149 Error if the precondition of the operation was not met: +150 - When a way has nodes that do not exist or are not visible +151 - When a relation has elements that do not exist or are not visible +152 - When a node/way/relation is still used in a way/relation +153 """ +
Error if the precondition of the operation was not met:
@@ -1184,23 +1181,22 @@class TimeoutApiError(ApiError): - """ - Error if the http request ran into a timeout - """ -
156class TimeoutApiError(ApiError): +157 """ +158 Error if the http request ran into a timeout +159 """ +
Error if the http request ran into a timeout
class ConnectionApiError(ApiError): - """ - Error if there was a network error (e.g. DNS failure, refused connection) - while connecting to the remote server. - """ -
162class ConnectionApiError(ApiError): +163 """ +164 Error if there was a network error (e.g. DNS failure, refused connection) +165 while connecting to the remote server. +166 """ +
Error if there was a network error (e.g. DNS failure, refused connection) while connecting to the remote server.
@@ -1370,12 +1365,26 @@import datetime -import itertools as it -import logging -import requests -import time - -from . import errors - - -logger = logging.getLogger(__name__) - - -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, timeout=30): - self._api = base_url - self._created_by = created_by - self._timeout = timeout - - try: - self._auth = auth - if not auth and session.auth: - self._auth = session.auth - except AttributeError: - pass - - self._http_session = session - self._session = self._get_http_session() - - def close(self): - if self._session: - self._session.close() - - def _http_request(self, method, path, auth, send, return_value=True): # noqa - """ - Returns the response generated by an HTTP request. - - `method` is a HTTP method to be executed - with the request data. For example: 'GET' or 'POST'. - `path` is the path to the requested resource relative to the - base API address stored in self._api. Should start with a - slash character to separate the URL. - `auth` is a boolean indicating whether authentication should - be preformed on this request. - `send` contains additional data that might be sent in a - request. - `return_value` indicates wheter this request should return - any data or not. - - If the username or password is missing, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - - If the response status code indicates an error, - `OsmApi.ApiError` is raised. - """ - logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") - - # Add API base URL to path - path = self._api + path - - if auth and not self._auth: - raise errors.UsernamePasswordMissingError("Username/Password missing") - - try: - response = self._session.request( - method, path, data=send, timeout=self._timeout - ) - except requests.exceptions.Timeout as e: - raise errors.TimeoutApiError( - 0, f"Request timed out (timeout={self._timeout})", "" - ) from e - except requests.exceptions.ConnectionError as e: - raise errors.ConnectionApiError(0, f"Connection error: {str(e)}", "") from e - except requests.exceptions.RequestException as e: - raise errors.ApiError(0, str(e), "") from e - - if response.status_code != 200: - payload = response.content.strip() - if response.status_code == 401: - raise errors.UnauthorizedApiError( - response.status_code, response.reason, payload - ) - if response.status_code == 404: - raise errors.ElementNotFoundApiError( - response.status_code, response.reason, payload - ) - elif response.status_code == 410: - raise errors.ElementDeletedApiError( - response.status_code, response.reason, payload - ) - raise errors.ApiError(response.status_code, response.reason, payload) - if return_value and not response.content: - raise errors.ResponseEmptyApiError( - response.status_code, response.reason, "" - ) - - logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") - return response.content - - def _http(self, cmd, path, auth, send, return_value=True): # noqa - for i in it.count(1): - try: - return self._http_request( - cmd, path, auth, send, return_value=return_value - ) - except errors.ApiError as e: - if e.status >= 500: - if i == self.MAX_RETRY_LIMIT: - raise - if i != 1: - self._sleep() - self._session = self._get_http_session() - else: - logger.exception("ApiError Exception occured") - raise - except errors.UsernamePasswordMissingError: - raise - except Exception as e: - logger.exception("General exception occured") - if i == self.MAX_RETRY_LIMIT: - if isinstance(e, errors.OsmApiError): - raise - raise errors.MaximumRetryLimitReachedError( - f"Give up after {i} retries" - ) from e - if i != 1: - self._sleep() - self._session = self._get_http_session() - - def _get_http_session(self): - """ - Creates a requests session for connection pooling. - """ - if self._http_session: - session = self._http_session - else: - session = requests.Session() - - session.auth = self._auth - session.headers.update({"user-agent": self._created_by}) - return session - - def _sleep(self): - time.sleep(5) - - def _get(self, path): - return self._http("GET", path, False, None) - - def _put(self, path, data, return_value=True): - return self._http("PUT", path, True, data, return_value=return_value) - - def _post(self, path, data, optionalAuth=False, forceAuth=False): - # the Notes API allows certain POSTs by non-authenticated users - auth = optionalAuth and self._auth - if forceAuth: - auth = True - return self._http("POST", path, auth, data) - - def _delete(self, path, data): - return self._http("DELETE", path, True, data) -
HTTP session management for the OpenStreetMap API.
+1""" + 2HTTP session management for the OpenStreetMap API. + 3""" + 4 + 5import datetime + 6import itertools as it + 7import logging + 8import requests + 9import time + 10from typing import Any, Optional, Tuple, Union + 11 + 12from . import errors + 13 + 14logger = logging.getLogger(__name__) + 15 + 16 + 17class OsmApiSession: + 18 MAX_RETRY_LIMIT = 5 + 19 """Maximum retries if a call to the remote API fails (default: 5)""" + 20 + 21 def __init__( + 22 self, + 23 base_url: str, + 24 created_by: str, + 25 auth: Optional[Tuple[str, str]] = None, + 26 session: Optional[requests.Session] = None, + 27 timeout: int = 30, + 28 ) -> None: + 29 self._api = base_url + 30 self._created_by = created_by + 31 self._timeout = timeout + 32 + 33 try: + 34 self._auth: Optional[Any] = auth + 35 if not auth and session.auth: # type: ignore[union-attr] + 36 self._auth = session.auth # type: ignore[union-attr] + 37 except AttributeError: + 38 pass + 39 + 40 self._http_session = session + 41 self._session = self._get_http_session() + 42 + 43 def close(self) -> None: + 44 if self._session: + 45 self._session.close() + 46 + 47 def _http_request( # noqa: C901 + 48 self, + 49 method: str, + 50 path: str, + 51 auth: bool, + 52 send: Optional[Union[str, bytes]], + 53 return_value: bool = True, + 54 params: Optional[dict] = None, + 55 ) -> bytes: + 56 """ + 57 Returns the response generated by an HTTP request. + 58 + 59 `method` is a HTTP method to be executed + 60 with the request data. For example: 'GET' or 'POST'. + 61 `path` is the path to the requested resource relative to the + 62 base API address stored in self._api. Should start with a + 63 slash character to separate the URL. + 64 `auth` is a boolean indicating whether authentication should + 65 be preformed on this request. + 66 `send` contains additional data that might be sent in a + 67 request. + 68 `return_value` indicates wheter this request should return + 69 any data or not. + 70 + 71 If the username or password is missing, + 72 `OsmApi.UsernamePasswordMissingError` is raised. + 73 + 74 If the requested element has been deleted, + 75 `OsmApi.ElementDeletedApiError` is raised. + 76 + 77 If the requested element can not be found, + 78 `OsmApi.ElementNotFoundApiError` is raised. + 79 + 80 If the response status code indicates an error, + 81 `OsmApi.ApiError` is raised. + 82 """ + 83 logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") + 84 + 85 # Add API base URL to path + 86 path = self._api + path + 87 + 88 if auth and not self._auth: + 89 raise errors.UsernamePasswordMissingError("Username/Password missing") + 90 + 91 try: + 92 response = self._session.request( + 93 method, path, data=send, timeout=self._timeout, params=params + 94 ) + 95 except requests.exceptions.Timeout as e: + 96 raise errors.TimeoutApiError( + 97 0, f"Request timed out (timeout={self._timeout})", "" + 98 ) from e + 99 except requests.exceptions.ConnectionError as e: +100 raise errors.ConnectionApiError(0, f"Connection error: {str(e)}", "") from e +101 except requests.exceptions.RequestException as e: +102 raise errors.ApiError(0, str(e), "") from e +103 +104 if response.status_code != 200: +105 payload = response.content.strip() +106 if response.status_code == 401: +107 raise errors.UnauthorizedApiError( +108 response.status_code, response.reason, payload +109 ) +110 if response.status_code == 404: +111 raise errors.ElementNotFoundApiError( +112 response.status_code, response.reason, payload +113 ) +114 elif response.status_code == 410: +115 raise errors.ElementDeletedApiError( +116 response.status_code, response.reason, payload +117 ) +118 raise errors.ApiError(response.status_code, response.reason, payload) +119 if return_value and not response.content: +120 raise errors.ResponseEmptyApiError( +121 response.status_code, response.reason, "" +122 ) +123 +124 logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") +125 return response.content +126 +127 def _http( # type: ignore[return-value] # noqa: C901 +128 self, +129 cmd: str, +130 path: str, +131 auth: bool, +132 send: Optional[Union[str, bytes]], +133 return_value: bool = True, +134 params: Optional[dict] = None, +135 ) -> bytes: +136 for i in it.count(1): +137 try: +138 return self._http_request( +139 cmd, path, auth, send, return_value=return_value, params=params +140 ) +141 except errors.ApiError as e: +142 if e.status >= 500: +143 if i == self.MAX_RETRY_LIMIT: +144 raise +145 if i != 1: +146 self._sleep() +147 self._session = self._get_http_session() +148 else: +149 logger.debug("ApiError Exception occured") +150 raise +151 except errors.UsernamePasswordMissingError: +152 raise +153 except Exception as e: +154 logger.exception("General exception occured") +155 if i == self.MAX_RETRY_LIMIT: +156 if isinstance(e, errors.OsmApiError): +157 raise +158 raise errors.MaximumRetryLimitReachedError( +159 f"Give up after {i} retries" +160 ) from e +161 if i != 1: +162 self._sleep() +163 self._session = self._get_http_session() +164 +165 def _get_http_session(self) -> requests.Session: +166 """ +167 Creates a requests session for connection pooling. +168 """ +169 if self._http_session: +170 session = self._http_session +171 else: +172 session = requests.Session() +173 +174 session.auth = self._auth +175 session.headers.update({"user-agent": self._created_by}) +176 return session +177 +178 def _sleep(self) -> None: +179 time.sleep(5) +180 +181 def _get(self, path: str, params: Optional[dict] = None) -> bytes: +182 return self._http("GET", path, False, None, params=params) +183 +184 def _put( +185 self, path: str, data: Optional[Union[str, bytes]], return_value: bool = True +186 ) -> bytes: +187 return self._http("PUT", path, True, data, return_value=return_value) +188 +189 def _post( +190 self, +191 path: str, +192 data: Optional[Union[str, bytes]], +193 optionalAuth: bool = False, +194 forceAuth: bool = False, +195 params: Optional[dict] = None, +196 ) -> bytes: +197 # the Notes API allows certain POSTs by non-authenticated users +198 auth = optionalAuth and self._auth +199 if forceAuth: +200 auth = True +201 return self._http("POST", path, bool(auth), data, params=params) +202 +203 def _delete(self, path: str, data: Optional[Union[str, bytes]]) -> bytes: +204 return self._http("DELETE", path, True, data) +
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, timeout=30): - self._api = base_url - self._created_by = created_by - self._timeout = timeout - - try: - self._auth = auth - if not auth and session.auth: - self._auth = session.auth - except AttributeError: - pass - - self._http_session = session - self._session = self._get_http_session() - - def close(self): - if self._session: - self._session.close() - - def _http_request(self, method, path, auth, send, return_value=True): # noqa - """ - Returns the response generated by an HTTP request. - - `method` is a HTTP method to be executed - with the request data. For example: 'GET' or 'POST'. - `path` is the path to the requested resource relative to the - base API address stored in self._api. Should start with a - slash character to separate the URL. - `auth` is a boolean indicating whether authentication should - be preformed on this request. - `send` contains additional data that might be sent in a - request. - `return_value` indicates wheter this request should return - any data or not. - - If the username or password is missing, - `OsmApi.UsernamePasswordMissingError` is raised. - - If the requested element has been deleted, - `OsmApi.ElementDeletedApiError` is raised. - - If the requested element can not be found, - `OsmApi.ElementNotFoundApiError` is raised. - - If the response status code indicates an error, - `OsmApi.ApiError` is raised. - """ - logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") - - # Add API base URL to path - path = self._api + path - - if auth and not self._auth: - raise errors.UsernamePasswordMissingError("Username/Password missing") - - try: - response = self._session.request( - method, path, data=send, timeout=self._timeout - ) - except requests.exceptions.Timeout as e: - raise errors.TimeoutApiError( - 0, f"Request timed out (timeout={self._timeout})", "" - ) from e - except requests.exceptions.ConnectionError as e: - raise errors.ConnectionApiError(0, f"Connection error: {str(e)}", "") from e - except requests.exceptions.RequestException as e: - raise errors.ApiError(0, str(e), "") from e - - if response.status_code != 200: - payload = response.content.strip() - if response.status_code == 401: - raise errors.UnauthorizedApiError( - response.status_code, response.reason, payload - ) - if response.status_code == 404: - raise errors.ElementNotFoundApiError( - response.status_code, response.reason, payload - ) - elif response.status_code == 410: - raise errors.ElementDeletedApiError( - response.status_code, response.reason, payload - ) - raise errors.ApiError(response.status_code, response.reason, payload) - if return_value and not response.content: - raise errors.ResponseEmptyApiError( - response.status_code, response.reason, "" - ) - - logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") - return response.content - - def _http(self, cmd, path, auth, send, return_value=True): # noqa - for i in it.count(1): - try: - return self._http_request( - cmd, path, auth, send, return_value=return_value - ) - except errors.ApiError as e: - if e.status >= 500: - if i == self.MAX_RETRY_LIMIT: - raise - if i != 1: - self._sleep() - self._session = self._get_http_session() - else: - logger.exception("ApiError Exception occured") - raise - except errors.UsernamePasswordMissingError: - raise - except Exception as e: - logger.exception("General exception occured") - if i == self.MAX_RETRY_LIMIT: - if isinstance(e, errors.OsmApiError): - raise - raise errors.MaximumRetryLimitReachedError( - f"Give up after {i} retries" - ) from e - if i != 1: - self._sleep() - self._session = self._get_http_session() - - def _get_http_session(self): - """ - Creates a requests session for connection pooling. - """ - if self._http_session: - session = self._http_session - else: - session = requests.Session() - - session.auth = self._auth - session.headers.update({"user-agent": self._created_by}) - return session - - def _sleep(self): - time.sleep(5) - - def _get(self, path): - return self._http("GET", path, False, None) - - def _put(self, path, data, return_value=True): - return self._http("PUT", path, True, data, return_value=return_value) - - def _post(self, path, data, optionalAuth=False, forceAuth=False): - # the Notes API allows certain POSTs by non-authenticated users - auth = optionalAuth and self._auth - if forceAuth: - auth = True - return self._http("POST", path, auth, data) - - def _delete(self, path, data): - return self._http("DELETE", path, True, data) -
18class OsmApiSession: + 19 MAX_RETRY_LIMIT = 5 + 20 """Maximum retries if a call to the remote API fails (default: 5)""" + 21 + 22 def __init__( + 23 self, + 24 base_url: str, + 25 created_by: str, + 26 auth: Optional[Tuple[str, str]] = None, + 27 session: Optional[requests.Session] = None, + 28 timeout: int = 30, + 29 ) -> None: + 30 self._api = base_url + 31 self._created_by = created_by + 32 self._timeout = timeout + 33 + 34 try: + 35 self._auth: Optional[Any] = auth + 36 if not auth and session.auth: # type: ignore[union-attr] + 37 self._auth = session.auth # type: ignore[union-attr] + 38 except AttributeError: + 39 pass + 40 + 41 self._http_session = session + 42 self._session = self._get_http_session() + 43 + 44 def close(self) -> None: + 45 if self._session: + 46 self._session.close() + 47 + 48 def _http_request( # noqa: C901 + 49 self, + 50 method: str, + 51 path: str, + 52 auth: bool, + 53 send: Optional[Union[str, bytes]], + 54 return_value: bool = True, + 55 params: Optional[dict] = None, + 56 ) -> bytes: + 57 """ + 58 Returns the response generated by an HTTP request. + 59 + 60 `method` is a HTTP method to be executed + 61 with the request data. For example: 'GET' or 'POST'. + 62 `path` is the path to the requested resource relative to the + 63 base API address stored in self._api. Should start with a + 64 slash character to separate the URL. + 65 `auth` is a boolean indicating whether authentication should + 66 be preformed on this request. + 67 `send` contains additional data that might be sent in a + 68 request. + 69 `return_value` indicates wheter this request should return + 70 any data or not. + 71 + 72 If the username or password is missing, + 73 `OsmApi.UsernamePasswordMissingError` is raised. + 74 + 75 If the requested element has been deleted, + 76 `OsmApi.ElementDeletedApiError` is raised. + 77 + 78 If the requested element can not be found, + 79 `OsmApi.ElementNotFoundApiError` is raised. + 80 + 81 If the response status code indicates an error, + 82 `OsmApi.ApiError` is raised. + 83 """ + 84 logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") + 85 + 86 # Add API base URL to path + 87 path = self._api + path + 88 + 89 if auth and not self._auth: + 90 raise errors.UsernamePasswordMissingError("Username/Password missing") + 91 + 92 try: + 93 response = self._session.request( + 94 method, path, data=send, timeout=self._timeout, params=params + 95 ) + 96 except requests.exceptions.Timeout as e: + 97 raise errors.TimeoutApiError( + 98 0, f"Request timed out (timeout={self._timeout})", "" + 99 ) from e +100 except requests.exceptions.ConnectionError as e: +101 raise errors.ConnectionApiError(0, f"Connection error: {str(e)}", "") from e +102 except requests.exceptions.RequestException as e: +103 raise errors.ApiError(0, str(e), "") from e +104 +105 if response.status_code != 200: +106 payload = response.content.strip() +107 if response.status_code == 401: +108 raise errors.UnauthorizedApiError( +109 response.status_code, response.reason, payload +110 ) +111 if response.status_code == 404: +112 raise errors.ElementNotFoundApiError( +113 response.status_code, response.reason, payload +114 ) +115 elif response.status_code == 410: +116 raise errors.ElementDeletedApiError( +117 response.status_code, response.reason, payload +118 ) +119 raise errors.ApiError(response.status_code, response.reason, payload) +120 if return_value and not response.content: +121 raise errors.ResponseEmptyApiError( +122 response.status_code, response.reason, "" +123 ) +124 +125 logger.debug(f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S} {method} {path}") +126 return response.content +127 +128 def _http( # type: ignore[return-value] # noqa: C901 +129 self, +130 cmd: str, +131 path: str, +132 auth: bool, +133 send: Optional[Union[str, bytes]], +134 return_value: bool = True, +135 params: Optional[dict] = None, +136 ) -> bytes: +137 for i in it.count(1): +138 try: +139 return self._http_request( +140 cmd, path, auth, send, return_value=return_value, params=params +141 ) +142 except errors.ApiError as e: +143 if e.status >= 500: +144 if i == self.MAX_RETRY_LIMIT: +145 raise +146 if i != 1: +147 self._sleep() +148 self._session = self._get_http_session() +149 else: +150 logger.debug("ApiError Exception occured") +151 raise +152 except errors.UsernamePasswordMissingError: +153 raise +154 except Exception as e: +155 logger.exception("General exception occured") +156 if i == self.MAX_RETRY_LIMIT: +157 if isinstance(e, errors.OsmApiError): +158 raise +159 raise errors.MaximumRetryLimitReachedError( +160 f"Give up after {i} retries" +161 ) from e +162 if i != 1: +163 self._sleep() +164 self._session = self._get_http_session() +165 +166 def _get_http_session(self) -> requests.Session: +167 """ +168 Creates a requests session for connection pooling. +169 """ +170 if self._http_session: +171 session = self._http_session +172 else: +173 session = requests.Session() +174 +175 session.auth = self._auth +176 session.headers.update({"user-agent": self._created_by}) +177 return session +178 +179 def _sleep(self) -> None: +180 time.sleep(5) +181 +182 def _get(self, path: str, params: Optional[dict] = None) -> bytes: +183 return self._http("GET", path, False, None, params=params) +184 +185 def _put( +186 self, path: str, data: Optional[Union[str, bytes]], return_value: bool = True +187 ) -> bytes: +188 return self._http("PUT", path, True, data, return_value=return_value) +189 +190 def _post( +191 self, +192 path: str, +193 data: Optional[Union[str, bytes]], +194 optionalAuth: bool = False, +195 forceAuth: bool = False, +196 params: Optional[dict] = None, +197 ) -> bytes: +198 # the Notes API allows certain POSTs by non-authenticated users +199 auth = optionalAuth and self._auth +200 if forceAuth: +201 auth = True +202 return self._http("POST", path, bool(auth), data, params=params) +203 +204 def _delete(self, path: str, data: Optional[Union[str, bytes]]) -> bytes: +205 return self._http("DELETE", path, True, data) +
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 - if not auth and session.auth: - self._auth = session.auth - except AttributeError: - pass + - self._http_session = session - self._session = self._get_http_session() -
22 def __init__( +23 self, +24 base_url: str, +25 created_by: str, +26 auth: Optional[Tuple[str, str]] = None, +27 session: Optional[requests.Session] = None, +28 timeout: int = 30, +29 ) -> None: +30 self._api = base_url +31 self._created_by = created_by +32 self._timeout = timeout +33 +34 try: +35 self._auth: Optional[Any] = auth +36 if not auth and session.auth: # type: ignore[union-attr] +37 self._auth = session.auth # type: ignore[union-attr] +38 except AttributeError: +39 pass +40 +41 self._http_session = session +42 self._session = self._get_http_session() +
Maximum retries if a call to the remote API fails (default: 5)
def close(self): - if self._session: - self._session.close() -
Node operations for the OpenStreetMap API.
+1""" + 2Node operations for the OpenStreetMap API. + 3""" + 4 + 5from typing import Any, Optional, TYPE_CHECKING, cast + 6from xml.dom.minidom import Element + 7 + 8from . import dom + 9 + 10if TYPE_CHECKING: + 11 from .OsmApi import OsmApi + 12 + 13 + 14class NodeMixin: + 15 """Mixin providing node-related operations with pythonic method names.""" + 16 + 17 def node_get( + 18 self: "OsmApi", node_id: int, node_version: int = -1 + 19 ) -> dict[str, Any]: + 20 """ + 21 Returns node with `node_id` as a dict: + 22 + 23 #!python + 24 { + 25 'id': id of node, + 26 'lat': latitude of node, + 27 'lon': longitude of node, + 28 'tag': {}, + 29 'changeset': id of changeset of last change, + 30 'version': version number of node, + 31 'user': username of user that made the last change, + 32 'uid': id of user that made the last change, + 33 'timestamp': timestamp of last change, + 34 'visible': True|False + 35 } + 36 + 37 If `node_version` is supplied, this specific version is returned, + 38 otherwise the latest version is returned. + 39 + 40 If the requested element has been deleted, + 41 `OsmApi.ElementDeletedApiError` is raised. + 42 + 43 If the requested element can not be found, + 44 `OsmApi.ElementNotFoundApiError` is raised. + 45 """ + 46 uri = f"/api/0.6/node/{node_id}" + 47 if node_version != -1: + 48 uri += f"/{node_version}" + 49 data = self._session._get(uri) + 50 node_element = cast( + 51 Element, dom.OsmResponseToDom(data, tag="node", single=True) + 52 ) + 53 return dom.dom_parse_node(node_element) + 54 + 55 def node_create( + 56 self: "OsmApi", node_data: dict[str, Any] + 57 ) -> Optional[dict[str, Any]]: + 58 """ + 59 Creates a node based on the supplied `node_data` dict: + 60 + 61 #!python + 62 { + 63 'lat': latitude of node, + 64 'lon': longitude of node, + 65 'tag': {}, + 66 } + 67 + 68 Returns updated `node_data` (without timestamp): + 69 + 70 #!python + 71 { + 72 'id': id of node, + 73 'lat': latitude of node, + 74 'lon': longitude of node, + 75 'tag': dict of tags, + 76 'changeset': id of changeset of last change, + 77 'version': version number of node, + 78 'user': username of last change, + 79 'uid': id of user of last change, + 80 'visible': True|False + 81 } + 82 + 83 If no authentication information are provided, + 84 `OsmApi.UsernamePasswordMissingError` is raised. + 85 + 86 If there is no open changeset, + 87 `OsmApi.NoChangesetOpenError` is raised. + 88 + 89 If the supplied information contain an existing node, + 90 `OsmApi.OsmTypeAlreadyExistsError` is raised. + 91 + 92 If the changeset is already closed, + 93 `OsmApi.ChangesetClosedApiError` is raised. + 94 """ + 95 return self._do("create", "node", node_data) + 96 + 97 def node_update( + 98 self: "OsmApi", node_data: dict[str, Any] + 99 ) -> Optional[dict[str, Any]]: +100 """ +101 Updates node with the supplied `node_data` dict: +102 +103 #!python +104 { +105 'id': id of node, +106 'lat': latitude of node, +107 'lon': longitude of node, +108 'tag': {}, +109 'version': version number of node, +110 } +111 +112 Returns updated `node_data` (without timestamp): +113 +114 #!python +115 { +116 'id': id of node, +117 'lat': latitude of node, +118 'lon': longitude of node, +119 'tag': dict of tags, +120 'changeset': id of changeset of last change, +121 'version': version number of node, +122 'user': username of last change, +123 'uid': id of user of last change, +124 'visible': True|False +125 } +126 +127 If no authentication information are provided, +128 `OsmApi.UsernamePasswordMissingError` is raised. +129 +130 If there is no open changeset, +131 `OsmApi.NoChangesetOpenError` is raised. +132 +133 If there is already an open changeset, +134 `OsmApi.ChangesetAlreadyOpenError` is raised. +135 +136 If the changeset is already closed, +137 `OsmApi.ChangesetClosedApiError` is raised. +138 """ +139 return self._do("modify", "node", node_data) +140 +141 def node_delete( +142 self: "OsmApi", node_data: dict[str, Any] +143 ) -> Optional[dict[str, Any]]: +144 """ +145 Delete node with `node_data`: +146 +147 #!python +148 { +149 'id': id of node, +150 'lat': latitude of node, +151 'lon': longitude of node, +152 'tag': dict of tags, +153 'version': version number of node, +154 } +155 +156 Returns updated `node_data` (without timestamp): +157 +158 #!python +159 { +160 'id': id of node, +161 'lat': latitude of node, +162 'lon': longitude of node, +163 'tag': dict of tags, +164 'changeset': id of changeset of last change, +165 'version': version number of node, +166 'user': username of last change, +167 'uid': id of user of last change, +168 'visible': True|False +169 } +170 +171 If no authentication information are provided, +172 `OsmApi.UsernamePasswordMissingError` is raised. +173 +174 If there is no open changeset, +175 `OsmApi.NoChangesetOpenError` is raised. +176 +177 If the changeset is already closed, +178 `OsmApi.ChangesetClosedApiError` is raised. +179 """ +180 return self._do("delete", "node", node_data) +181 +182 def node_history(self: "OsmApi", node_id: int) -> dict[int, dict[str, Any]]: +183 """ +184 Returns dict with version as key: +185 +186 #!python +187 { +188 1: dict of node version 1, +189 2: dict of node version 2, +190 ... +191 } +192 +193 If the requested element can not be found, +194 `OsmApi.ElementNotFoundApiError` is raised. +195 """ +196 uri = f"/api/0.6/node/{node_id}/history" +197 data = self._session._get(uri) +198 node_list = cast(list[Element], dom.OsmResponseToDom(data, tag="node")) +199 result = {} +200 for node in node_list: +201 node_data = dom.dom_parse_node(node) +202 result[node_data["version"]] = node_data +203 return result +204 +205 def node_ways(self: "OsmApi", node_id: int) -> list[dict[str, Any]]: +206 """ +207 Returns list of dicts of ways that use the node with `node_id`: +208 +209 #!python +210 [ +211 { +212 'id': id of way, +213 'nd': list of node ids, +214 'tag': dict of tags, +215 'changeset': id of changeset of last change, +216 'version': version number of way, +217 'user': username of user that made the last change, +218 'uid': id of user that made the last change, +219 'timestamp': timestamp of last change, +220 'visible': True|False +221 }, +222 ... +223 ] +224 +225 If the requested element can not be found, +226 `OsmApi.ElementNotFoundApiError` is raised. +227 """ +228 uri = f"/api/0.6/node/{node_id}/ways" +229 data = self._session._get(uri) +230 way_list = cast( +231 list[Element], dom.OsmResponseToDom(data, tag="way", allow_empty=True) +232 ) +233 return [dom.dom_parse_way(way) for way in way_list] +234 +235 def node_relations(self: "OsmApi", node_id: int) -> list[dict[str, Any]]: +236 """ +237 Returns list of dicts of relations that use the node with `node_id`: +238 +239 #!python +240 [ +241 { +242 'id': id of relation, +243 'member': [ +244 { +245 'ref': reference id, +246 'role': role, +247 'type': node|way|relation +248 }, +249 ... +250 ], +251 'tag': dict of tags, +252 'changeset': id of changeset of last change, +253 'version': version number of relation, +254 'user': username of user that made the last change, +255 'uid': id of user that made the last change, +256 'timestamp': timestamp of last change, +257 'visible': True|False +258 }, +259 ... +260 ] +261 +262 If the requested element can not be found, +263 `OsmApi.ElementNotFoundApiError` is raised. +264 """ +265 uri = f"/api/0.6/node/{node_id}/relations" +266 data = self._session._get(uri) +267 relation_list = cast( +268 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +269 ) +270 return [dom.dom_parse_relation(rel) for rel in relation_list] +271 +272 def nodes_get(self: "OsmApi", node_id_list: list[int]) -> dict[int, dict[str, Any]]: +273 """ +274 Returns dict with id as key: +275 +276 #!python +277 { +278 node_id: dict of node, +279 ... +280 } +281 +282 If the requested element can not be found, +283 `OsmApi.ElementNotFoundApiError` is raised. +284 """ +285 nodes = ",".join([str(x) for x in node_id_list]) +286 uri = f"/api/0.6/nodes?nodes={nodes}" +287 data = self._session._get(uri) +288 node_list = cast(list[Element], dom.OsmResponseToDom(data, tag="node")) +289 result = {} +290 for node in node_list: +291 node_data = dom.dom_parse_node(node) +292 result[node_data["id"]] = node_data +293 return result +
15class NodeMixin: + 16 """Mixin providing node-related operations with pythonic method names.""" + 17 + 18 def node_get( + 19 self: "OsmApi", node_id: int, node_version: int = -1 + 20 ) -> dict[str, Any]: + 21 """ + 22 Returns node with `node_id` as a dict: + 23 + 24 #!python + 25 { + 26 'id': id of node, + 27 'lat': latitude of node, + 28 'lon': longitude of node, + 29 'tag': {}, + 30 'changeset': id of changeset of last change, + 31 'version': version number of node, + 32 'user': username of user that made the last change, + 33 'uid': id of user that made the last change, + 34 'timestamp': timestamp of last change, + 35 'visible': True|False + 36 } + 37 + 38 If `node_version` is supplied, this specific version is returned, + 39 otherwise the latest version is returned. + 40 + 41 If the requested element has been deleted, + 42 `OsmApi.ElementDeletedApiError` is raised. + 43 + 44 If the requested element can not be found, + 45 `OsmApi.ElementNotFoundApiError` is raised. + 46 """ + 47 uri = f"/api/0.6/node/{node_id}" + 48 if node_version != -1: + 49 uri += f"/{node_version}" + 50 data = self._session._get(uri) + 51 node_element = cast( + 52 Element, dom.OsmResponseToDom(data, tag="node", single=True) + 53 ) + 54 return dom.dom_parse_node(node_element) + 55 + 56 def node_create( + 57 self: "OsmApi", node_data: dict[str, Any] + 58 ) -> Optional[dict[str, Any]]: + 59 """ + 60 Creates a node based on the supplied `node_data` dict: + 61 + 62 #!python + 63 { + 64 'lat': latitude of node, + 65 'lon': longitude of node, + 66 'tag': {}, + 67 } + 68 + 69 Returns updated `node_data` (without timestamp): + 70 + 71 #!python + 72 { + 73 'id': id of node, + 74 'lat': latitude of node, + 75 'lon': longitude of node, + 76 'tag': dict of tags, + 77 'changeset': id of changeset of last change, + 78 'version': version number of node, + 79 'user': username of last change, + 80 'uid': id of user of last change, + 81 'visible': True|False + 82 } + 83 + 84 If no authentication information are provided, + 85 `OsmApi.UsernamePasswordMissingError` is raised. + 86 + 87 If there is no open changeset, + 88 `OsmApi.NoChangesetOpenError` is raised. + 89 + 90 If the supplied information contain an existing node, + 91 `OsmApi.OsmTypeAlreadyExistsError` is raised. + 92 + 93 If the changeset is already closed, + 94 `OsmApi.ChangesetClosedApiError` is raised. + 95 """ + 96 return self._do("create", "node", node_data) + 97 + 98 def node_update( + 99 self: "OsmApi", node_data: dict[str, Any] +100 ) -> Optional[dict[str, Any]]: +101 """ +102 Updates node with the supplied `node_data` dict: +103 +104 #!python +105 { +106 'id': id of node, +107 'lat': latitude of node, +108 'lon': longitude of node, +109 'tag': {}, +110 'version': version number of node, +111 } +112 +113 Returns updated `node_data` (without timestamp): +114 +115 #!python +116 { +117 'id': id of node, +118 'lat': latitude of node, +119 'lon': longitude of node, +120 'tag': dict of tags, +121 'changeset': id of changeset of last change, +122 'version': version number of node, +123 'user': username of last change, +124 'uid': id of user of last change, +125 'visible': True|False +126 } +127 +128 If no authentication information are provided, +129 `OsmApi.UsernamePasswordMissingError` is raised. +130 +131 If there is no open changeset, +132 `OsmApi.NoChangesetOpenError` is raised. +133 +134 If there is already an open changeset, +135 `OsmApi.ChangesetAlreadyOpenError` is raised. +136 +137 If the changeset is already closed, +138 `OsmApi.ChangesetClosedApiError` is raised. +139 """ +140 return self._do("modify", "node", node_data) +141 +142 def node_delete( +143 self: "OsmApi", node_data: dict[str, Any] +144 ) -> Optional[dict[str, Any]]: +145 """ +146 Delete node with `node_data`: +147 +148 #!python +149 { +150 'id': id of node, +151 'lat': latitude of node, +152 'lon': longitude of node, +153 'tag': dict of tags, +154 'version': version number of node, +155 } +156 +157 Returns updated `node_data` (without timestamp): +158 +159 #!python +160 { +161 'id': id of node, +162 'lat': latitude of node, +163 'lon': longitude of node, +164 'tag': dict of tags, +165 'changeset': id of changeset of last change, +166 'version': version number of node, +167 'user': username of last change, +168 'uid': id of user of last change, +169 'visible': True|False +170 } +171 +172 If no authentication information are provided, +173 `OsmApi.UsernamePasswordMissingError` is raised. +174 +175 If there is no open changeset, +176 `OsmApi.NoChangesetOpenError` is raised. +177 +178 If the changeset is already closed, +179 `OsmApi.ChangesetClosedApiError` is raised. +180 """ +181 return self._do("delete", "node", node_data) +182 +183 def node_history(self: "OsmApi", node_id: int) -> dict[int, dict[str, Any]]: +184 """ +185 Returns dict with version as key: +186 +187 #!python +188 { +189 1: dict of node version 1, +190 2: dict of node version 2, +191 ... +192 } +193 +194 If the requested element can not be found, +195 `OsmApi.ElementNotFoundApiError` is raised. +196 """ +197 uri = f"/api/0.6/node/{node_id}/history" +198 data = self._session._get(uri) +199 node_list = cast(list[Element], dom.OsmResponseToDom(data, tag="node")) +200 result = {} +201 for node in node_list: +202 node_data = dom.dom_parse_node(node) +203 result[node_data["version"]] = node_data +204 return result +205 +206 def node_ways(self: "OsmApi", node_id: int) -> list[dict[str, Any]]: +207 """ +208 Returns list of dicts of ways that use the node with `node_id`: +209 +210 #!python +211 [ +212 { +213 'id': id of way, +214 'nd': list of node ids, +215 'tag': dict of tags, +216 'changeset': id of changeset of last change, +217 'version': version number of way, +218 'user': username of user that made the last change, +219 'uid': id of user that made the last change, +220 'timestamp': timestamp of last change, +221 'visible': True|False +222 }, +223 ... +224 ] +225 +226 If the requested element can not be found, +227 `OsmApi.ElementNotFoundApiError` is raised. +228 """ +229 uri = f"/api/0.6/node/{node_id}/ways" +230 data = self._session._get(uri) +231 way_list = cast( +232 list[Element], dom.OsmResponseToDom(data, tag="way", allow_empty=True) +233 ) +234 return [dom.dom_parse_way(way) for way in way_list] +235 +236 def node_relations(self: "OsmApi", node_id: int) -> list[dict[str, Any]]: +237 """ +238 Returns list of dicts of relations that use the node with `node_id`: +239 +240 #!python +241 [ +242 { +243 'id': id of relation, +244 'member': [ +245 { +246 'ref': reference id, +247 'role': role, +248 'type': node|way|relation +249 }, +250 ... +251 ], +252 'tag': dict of tags, +253 'changeset': id of changeset of last change, +254 'version': version number of relation, +255 'user': username of user that made the last change, +256 'uid': id of user that made the last change, +257 'timestamp': timestamp of last change, +258 'visible': True|False +259 }, +260 ... +261 ] +262 +263 If the requested element can not be found, +264 `OsmApi.ElementNotFoundApiError` is raised. +265 """ +266 uri = f"/api/0.6/node/{node_id}/relations" +267 data = self._session._get(uri) +268 relation_list = cast( +269 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +270 ) +271 return [dom.dom_parse_relation(rel) for rel in relation_list] +272 +273 def nodes_get(self: "OsmApi", node_id_list: list[int]) -> dict[int, dict[str, Any]]: +274 """ +275 Returns dict with id as key: +276 +277 #!python +278 { +279 node_id: dict of node, +280 ... +281 } +282 +283 If the requested element can not be found, +284 `OsmApi.ElementNotFoundApiError` is raised. +285 """ +286 nodes = ",".join([str(x) for x in node_id_list]) +287 uri = f"/api/0.6/nodes?nodes={nodes}" +288 data = self._session._get(uri) +289 node_list = cast(list[Element], dom.OsmResponseToDom(data, tag="node")) +290 result = {} +291 for node in node_list: +292 node_data = dom.dom_parse_node(node) +293 result[node_data["id"]] = node_data +294 return result +
Mixin providing node-related operations with pythonic method names.
+18 def node_get( +19 self: "OsmApi", node_id: int, node_version: int = -1 +20 ) -> dict[str, Any]: +21 """ +22 Returns node with `node_id` as a dict: +23 +24 #!python +25 { +26 'id': id of node, +27 'lat': latitude of node, +28 'lon': longitude of node, +29 'tag': {}, +30 'changeset': id of changeset of last change, +31 'version': version number of node, +32 'user': username of user that made the last change, +33 'uid': id of user that made the last change, +34 'timestamp': timestamp of last change, +35 'visible': True|False +36 } +37 +38 If `node_version` is supplied, this specific version is returned, +39 otherwise the latest version is returned. +40 +41 If the requested element has been deleted, +42 `OsmApi.ElementDeletedApiError` is raised. +43 +44 If the requested element can not be found, +45 `OsmApi.ElementNotFoundApiError` is raised. +46 """ +47 uri = f"/api/0.6/node/{node_id}" +48 if node_version != -1: +49 uri += f"/{node_version}" +50 data = self._session._get(uri) +51 node_element = cast( +52 Element, dom.OsmResponseToDom(data, tag="node", single=True) +53 ) +54 return dom.dom_parse_node(node_element) +
Returns node with node_id as a dict:
#!python
+{
+ 'id': id of node,
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': {},
+ 'changeset': id of changeset of last change,
+ 'version': version number of node,
+ 'user': username of user that made the last change,
+ 'uid': id of user that made the last change,
+ 'timestamp': timestamp of last change,
+ 'visible': True|False
+}
+
+
+If node_version is supplied, this specific version is returned,
+otherwise the latest version is returned.
If the requested element has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
56 def node_create( +57 self: "OsmApi", node_data: dict[str, Any] +58 ) -> Optional[dict[str, Any]]: +59 """ +60 Creates a node based on the supplied `node_data` dict: +61 +62 #!python +63 { +64 'lat': latitude of node, +65 'lon': longitude of node, +66 'tag': {}, +67 } +68 +69 Returns updated `node_data` (without timestamp): +70 +71 #!python +72 { +73 'id': id of node, +74 'lat': latitude of node, +75 'lon': longitude of node, +76 'tag': dict of tags, +77 'changeset': id of changeset of last change, +78 'version': version number of node, +79 'user': username of last change, +80 'uid': id of user of last change, +81 'visible': True|False +82 } +83 +84 If no authentication information are provided, +85 `OsmApi.UsernamePasswordMissingError` is raised. +86 +87 If there is no open changeset, +88 `OsmApi.NoChangesetOpenError` is raised. +89 +90 If the supplied information contain an existing node, +91 `OsmApi.OsmTypeAlreadyExistsError` is raised. +92 +93 If the changeset is already closed, +94 `OsmApi.ChangesetClosedApiError` is raised. +95 """ +96 return self._do("create", "node", node_data) +
Creates a node based on the supplied node_data dict:
#!python
+{
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': {},
+}
+
+
+Returns updated node_data (without timestamp):
#!python
+{
+ 'id': id of node,
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of node,
+ 'user': username of last change,
+ 'uid': id of user of last change,
+ 'visible': True|False
+}
+
+
+If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the supplied information contain an existing node,
+OsmApi.OsmTypeAlreadyExistsError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
98 def node_update( + 99 self: "OsmApi", node_data: dict[str, Any] +100 ) -> Optional[dict[str, Any]]: +101 """ +102 Updates node with the supplied `node_data` dict: +103 +104 #!python +105 { +106 'id': id of node, +107 'lat': latitude of node, +108 'lon': longitude of node, +109 'tag': {}, +110 'version': version number of node, +111 } +112 +113 Returns updated `node_data` (without timestamp): +114 +115 #!python +116 { +117 'id': id of node, +118 'lat': latitude of node, +119 'lon': longitude of node, +120 'tag': dict of tags, +121 'changeset': id of changeset of last change, +122 'version': version number of node, +123 'user': username of last change, +124 'uid': id of user of last change, +125 'visible': True|False +126 } +127 +128 If no authentication information are provided, +129 `OsmApi.UsernamePasswordMissingError` is raised. +130 +131 If there is no open changeset, +132 `OsmApi.NoChangesetOpenError` is raised. +133 +134 If there is already an open changeset, +135 `OsmApi.ChangesetAlreadyOpenError` is raised. +136 +137 If the changeset is already closed, +138 `OsmApi.ChangesetClosedApiError` is raised. +139 """ +140 return self._do("modify", "node", node_data) +
Updates node with the supplied node_data dict:
#!python
+{
+ 'id': id of node,
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': {},
+ 'version': version number of node,
+}
+
+
+Returns updated node_data (without timestamp):
#!python
+{
+ 'id': id of node,
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of node,
+ 'user': username of last change,
+ 'uid': id of user of last change,
+ 'visible': True|False
+}
+
+
+If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
+OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
142 def node_delete( +143 self: "OsmApi", node_data: dict[str, Any] +144 ) -> Optional[dict[str, Any]]: +145 """ +146 Delete node with `node_data`: +147 +148 #!python +149 { +150 'id': id of node, +151 'lat': latitude of node, +152 'lon': longitude of node, +153 'tag': dict of tags, +154 'version': version number of node, +155 } +156 +157 Returns updated `node_data` (without timestamp): +158 +159 #!python +160 { +161 'id': id of node, +162 'lat': latitude of node, +163 'lon': longitude of node, +164 'tag': dict of tags, +165 'changeset': id of changeset of last change, +166 'version': version number of node, +167 'user': username of last change, +168 'uid': id of user of last change, +169 'visible': True|False +170 } +171 +172 If no authentication information are provided, +173 `OsmApi.UsernamePasswordMissingError` is raised. +174 +175 If there is no open changeset, +176 `OsmApi.NoChangesetOpenError` is raised. +177 +178 If the changeset is already closed, +179 `OsmApi.ChangesetClosedApiError` is raised. +180 """ +181 return self._do("delete", "node", node_data) +
Delete node with node_data:
#!python
+{
+ 'id': id of node,
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': dict of tags,
+ 'version': version number of node,
+}
+
+
+Returns updated node_data (without timestamp):
#!python
+{
+ 'id': id of node,
+ 'lat': latitude of node,
+ 'lon': longitude of node,
+ 'tag': dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of node,
+ 'user': username of last change,
+ 'uid': id of user of last change,
+ 'visible': True|False
+}
+
+
+If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
183 def node_history(self: "OsmApi", node_id: int) -> dict[int, dict[str, Any]]: +184 """ +185 Returns dict with version as key: +186 +187 #!python +188 { +189 1: dict of node version 1, +190 2: dict of node version 2, +191 ... +192 } +193 +194 If the requested element can not be found, +195 `OsmApi.ElementNotFoundApiError` is raised. +196 """ +197 uri = f"/api/0.6/node/{node_id}/history" +198 data = self._session._get(uri) +199 node_list = cast(list[Element], dom.OsmResponseToDom(data, tag="node")) +200 result = {} +201 for node in node_list: +202 node_data = dom.dom_parse_node(node) +203 result[node_data["version"]] = node_data +204 return result +
Returns dict with version as key:
+ +#!python
+{
+ 1: dict of node version 1,
+ 2: dict of node version 2,
+ ...
+}
+
+
+If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
206 def node_ways(self: "OsmApi", node_id: int) -> list[dict[str, Any]]: +207 """ +208 Returns list of dicts of ways that use the node with `node_id`: +209 +210 #!python +211 [ +212 { +213 'id': id of way, +214 'nd': list of node ids, +215 'tag': dict of tags, +216 'changeset': id of changeset of last change, +217 'version': version number of way, +218 'user': username of user that made the last change, +219 'uid': id of user that made the last change, +220 'timestamp': timestamp of last change, +221 'visible': True|False +222 }, +223 ... +224 ] +225 +226 If the requested element can not be found, +227 `OsmApi.ElementNotFoundApiError` is raised. +228 """ +229 uri = f"/api/0.6/node/{node_id}/ways" +230 data = self._session._get(uri) +231 way_list = cast( +232 list[Element], dom.OsmResponseToDom(data, tag="way", allow_empty=True) +233 ) +234 return [dom.dom_parse_way(way) for way in way_list] +
Returns list of dicts of ways that use the node with node_id:
#!python
+[
+ {
+ 'id': id of way,
+ 'nd': list of node ids,
+ 'tag': dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of way,
+ 'user': username of user that made the last change,
+ 'uid': id of user that made the last change,
+ 'timestamp': timestamp of last change,
+ 'visible': True|False
+ },
+ ...
+]
+
+
+If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
236 def node_relations(self: "OsmApi", node_id: int) -> list[dict[str, Any]]: +237 """ +238 Returns list of dicts of relations that use the node with `node_id`: +239 +240 #!python +241 [ +242 { +243 'id': id of relation, +244 'member': [ +245 { +246 'ref': reference id, +247 'role': role, +248 'type': node|way|relation +249 }, +250 ... +251 ], +252 'tag': dict of tags, +253 'changeset': id of changeset of last change, +254 'version': version number of relation, +255 'user': username of user that made the last change, +256 'uid': id of user that made the last change, +257 'timestamp': timestamp of last change, +258 'visible': True|False +259 }, +260 ... +261 ] +262 +263 If the requested element can not be found, +264 `OsmApi.ElementNotFoundApiError` is raised. +265 """ +266 uri = f"/api/0.6/node/{node_id}/relations" +267 data = self._session._get(uri) +268 relation_list = cast( +269 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +270 ) +271 return [dom.dom_parse_relation(rel) for rel in relation_list] +
Returns list of dicts of relations that use the node with node_id:
#!python
+[
+ {
+ 'id': id of relation,
+ 'member': [
+ {
+ 'ref': reference id,
+ 'role': role,
+ 'type': node|way|relation
+ },
+ ...
+ ],
+ 'tag': dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of relation,
+ 'user': username of user that made the last change,
+ 'uid': id of user that made the last change,
+ 'timestamp': timestamp of last change,
+ 'visible': True|False
+ },
+ ...
+]
+
+
+If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
273 def nodes_get(self: "OsmApi", node_id_list: list[int]) -> dict[int, dict[str, Any]]: +274 """ +275 Returns dict with id as key: +276 +277 #!python +278 { +279 node_id: dict of node, +280 ... +281 } +282 +283 If the requested element can not be found, +284 `OsmApi.ElementNotFoundApiError` is raised. +285 """ +286 nodes = ",".join([str(x) for x in node_id_list]) +287 uri = f"/api/0.6/nodes?nodes={nodes}" +288 data = self._session._get(uri) +289 node_list = cast(list[Element], dom.OsmResponseToDom(data, tag="node")) +290 result = {} +291 for node in node_list: +292 node_data = dom.dom_parse_node(node) +293 result[node_data["id"]] = node_data +294 return result +
Returns dict with id as key:
+ +#!python
+{
+ node_id: dict of node,
+ ...
+}
+
+
+If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
Note operations for the OpenStreetMap API.
+1""" + 2Note operations for the OpenStreetMap API. + 3""" + 4 + 5from typing import Any, Optional, TYPE_CHECKING, cast + 6from xml.dom.minidom import Element + 7 + 8from . import dom, errors, parser + 9 + 10if TYPE_CHECKING: + 11 from .OsmApi import OsmApi + 12 + 13 + 14class NoteMixin: + 15 """Mixin providing note-related operations with pythonic method names.""" + 16 + 17 def notes_get( + 18 self: "OsmApi", + 19 min_lon: float, + 20 min_lat: float, + 21 max_lon: float, + 22 max_lat: float, + 23 limit: int = 100, + 24 closed: int = 7, + 25 ) -> list[dict[str, Any]]: + 26 """ + 27 Returns a list of dicts of notes in the specified bounding box. + 28 + 29 The limit parameter defines how many results should be returned. + 30 + 31 closed specifies the number of days a bug needs to be closed + 32 to no longer be returned. + 33 The value 0 means only open bugs are returned, + 34 -1 means all bugs are returned. + 35 + 36 All parameters are optional. + 37 """ + 38 path = "/api/0.6/notes" + 39 params = { + 40 "bbox": f"{min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}", + 41 "limit": limit, + 42 "closed": closed, + 43 } + 44 data = self._session._get(path, params=params) + 45 return parser.parse_notes(data) + 46 + 47 def note_get(self: "OsmApi", note_id: int) -> dict[str, Any]: + 48 """ + 49 Returns a note as dict. + 50 + 51 `note_id` is the unique identifier of the note. + 52 """ + 53 uri = f"/api/0.6/notes/{note_id}" + 54 data = self._session._get(uri) + 55 note_element = cast( + 56 Element, dom.OsmResponseToDom(data, tag="note", single=True) + 57 ) + 58 return dom.dom_parse_note(note_element) + 59 + 60 def note_create(self: "OsmApi", note_data: dict[str, Any]) -> dict[str, Any]: + 61 """ + 62 Creates a note based on the supplied `note_data` dict: + 63 + 64 #!python + 65 { + 66 'lat': latitude of note, + 67 'lon': longitude of note, + 68 'text': text of the note, + 69 } + 70 + 71 Returns updated note data. + 72 """ + 73 uri = "/api/0.6/notes" + 74 return self._note_action(uri, params=note_data) + 75 + 76 def note_comment(self: "OsmApi", note_id: int, comment: str) -> dict[str, Any]: + 77 """ + 78 Adds a new comment to a note. + 79 + 80 Returns the updated note. + 81 """ + 82 path = f"/api/0.6/notes/{note_id}/comment" + 83 return self._note_action(path, comment) + 84 + 85 def note_close( + 86 self: "OsmApi", note_id: int, comment: Optional[str] = None + 87 ) -> dict[str, Any]: + 88 """ + 89 Closes a note. + 90 + 91 Returns the updated note. + 92 + 93 If no authentication information are provided, + 94 `OsmApi.UsernamePasswordMissingError` is raised. + 95 """ + 96 path = f"/api/0.6/notes/{note_id}/close" + 97 return self._note_action(path, comment, optional_auth=False) + 98 + 99 def note_reopen( +100 self: "OsmApi", note_id: int, comment: Optional[str] = None +101 ) -> dict[str, Any]: +102 """ +103 Reopens a note. +104 +105 Returns the updated note. +106 +107 If no authentication information are provided, +108 `OsmApi.UsernamePasswordMissingError` is raised. +109 +110 If the requested element has been deleted, +111 `OsmApi.ElementDeletedApiError` is raised. +112 +113 If the requested element can not be found, +114 `OsmApi.ElementNotFoundApiError` is raised. +115 """ +116 path = f"/api/0.6/notes/{note_id}/reopen" +117 return self._note_action(path, comment, optional_auth=False) +118 +119 def notes_search( +120 self: "OsmApi", query: str, limit: int = 100, closed: int = 7 +121 ) -> list[dict[str, Any]]: +122 """ +123 Returns a list of dicts of notes that match the given search query. +124 +125 The limit parameter defines how many results should be returned. +126 +127 closed specifies the number of days a bug needs to be closed +128 to no longer be returned. +129 The value 0 means only open bugs are returned, +130 -1 means all bugs are returned. +131 """ +132 uri = "/api/0.6/notes/search" +133 params: dict[str, Any] = { +134 "q": query, +135 "limit": limit, +136 "closed": closed, +137 } +138 data = self._session._get(uri, params=params) +139 return parser.parse_notes(data) +140 +141 def _note_action( +142 self: "OsmApi", +143 path: str, +144 comment: Optional[str] = None, +145 optional_auth: bool = True, +146 params: Optional[dict[str, Any]] = None, +147 ) -> dict[str, Any]: +148 """ +149 Performs an action on a Note with a comment +150 +151 Return the updated note +152 """ +153 uri = path +154 final_params = params.copy() if params else {} +155 if comment is not None: +156 final_params["text"] = comment +157 try: +158 result = self._session._post( +159 uri, +160 None, +161 optionalAuth=optional_auth, +162 params=final_params if final_params else None, +163 ) +164 except errors.ApiError as e: +165 if e.status == 409: +166 raise errors.NoteAlreadyClosedApiError( +167 e.status, e.reason, e.payload +168 ) from e +169 else: +170 raise +171 +172 # parse the result +173 note_element = cast( +174 Element, dom.OsmResponseToDom(result, tag="note", single=True) +175 ) +176 return dom.dom_parse_note(note_element) +
15class NoteMixin: + 16 """Mixin providing note-related operations with pythonic method names.""" + 17 + 18 def notes_get( + 19 self: "OsmApi", + 20 min_lon: float, + 21 min_lat: float, + 22 max_lon: float, + 23 max_lat: float, + 24 limit: int = 100, + 25 closed: int = 7, + 26 ) -> list[dict[str, Any]]: + 27 """ + 28 Returns a list of dicts of notes in the specified bounding box. + 29 + 30 The limit parameter defines how many results should be returned. + 31 + 32 closed specifies the number of days a bug needs to be closed + 33 to no longer be returned. + 34 The value 0 means only open bugs are returned, + 35 -1 means all bugs are returned. + 36 + 37 All parameters are optional. + 38 """ + 39 path = "/api/0.6/notes" + 40 params = { + 41 "bbox": f"{min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}", + 42 "limit": limit, + 43 "closed": closed, + 44 } + 45 data = self._session._get(path, params=params) + 46 return parser.parse_notes(data) + 47 + 48 def note_get(self: "OsmApi", note_id: int) -> dict[str, Any]: + 49 """ + 50 Returns a note as dict. + 51 + 52 `note_id` is the unique identifier of the note. + 53 """ + 54 uri = f"/api/0.6/notes/{note_id}" + 55 data = self._session._get(uri) + 56 note_element = cast( + 57 Element, dom.OsmResponseToDom(data, tag="note", single=True) + 58 ) + 59 return dom.dom_parse_note(note_element) + 60 + 61 def note_create(self: "OsmApi", note_data: dict[str, Any]) -> dict[str, Any]: + 62 """ + 63 Creates a note based on the supplied `note_data` dict: + 64 + 65 #!python + 66 { + 67 'lat': latitude of note, + 68 'lon': longitude of note, + 69 'text': text of the note, + 70 } + 71 + 72 Returns updated note data. + 73 """ + 74 uri = "/api/0.6/notes" + 75 return self._note_action(uri, params=note_data) + 76 + 77 def note_comment(self: "OsmApi", note_id: int, comment: str) -> dict[str, Any]: + 78 """ + 79 Adds a new comment to a note. + 80 + 81 Returns the updated note. + 82 """ + 83 path = f"/api/0.6/notes/{note_id}/comment" + 84 return self._note_action(path, comment) + 85 + 86 def note_close( + 87 self: "OsmApi", note_id: int, comment: Optional[str] = None + 88 ) -> dict[str, Any]: + 89 """ + 90 Closes a note. + 91 + 92 Returns the updated note. + 93 + 94 If no authentication information are provided, + 95 `OsmApi.UsernamePasswordMissingError` is raised. + 96 """ + 97 path = f"/api/0.6/notes/{note_id}/close" + 98 return self._note_action(path, comment, optional_auth=False) + 99 +100 def note_reopen( +101 self: "OsmApi", note_id: int, comment: Optional[str] = None +102 ) -> dict[str, Any]: +103 """ +104 Reopens a note. +105 +106 Returns the updated note. +107 +108 If no authentication information are provided, +109 `OsmApi.UsernamePasswordMissingError` is raised. +110 +111 If the requested element has been deleted, +112 `OsmApi.ElementDeletedApiError` is raised. +113 +114 If the requested element can not be found, +115 `OsmApi.ElementNotFoundApiError` is raised. +116 """ +117 path = f"/api/0.6/notes/{note_id}/reopen" +118 return self._note_action(path, comment, optional_auth=False) +119 +120 def notes_search( +121 self: "OsmApi", query: str, limit: int = 100, closed: int = 7 +122 ) -> list[dict[str, Any]]: +123 """ +124 Returns a list of dicts of notes that match the given search query. +125 +126 The limit parameter defines how many results should be returned. +127 +128 closed specifies the number of days a bug needs to be closed +129 to no longer be returned. +130 The value 0 means only open bugs are returned, +131 -1 means all bugs are returned. +132 """ +133 uri = "/api/0.6/notes/search" +134 params: dict[str, Any] = { +135 "q": query, +136 "limit": limit, +137 "closed": closed, +138 } +139 data = self._session._get(uri, params=params) +140 return parser.parse_notes(data) +141 +142 def _note_action( +143 self: "OsmApi", +144 path: str, +145 comment: Optional[str] = None, +146 optional_auth: bool = True, +147 params: Optional[dict[str, Any]] = None, +148 ) -> dict[str, Any]: +149 """ +150 Performs an action on a Note with a comment +151 +152 Return the updated note +153 """ +154 uri = path +155 final_params = params.copy() if params else {} +156 if comment is not None: +157 final_params["text"] = comment +158 try: +159 result = self._session._post( +160 uri, +161 None, +162 optionalAuth=optional_auth, +163 params=final_params if final_params else None, +164 ) +165 except errors.ApiError as e: +166 if e.status == 409: +167 raise errors.NoteAlreadyClosedApiError( +168 e.status, e.reason, e.payload +169 ) from e +170 else: +171 raise +172 +173 # parse the result +174 note_element = cast( +175 Element, dom.OsmResponseToDom(result, tag="note", single=True) +176 ) +177 return dom.dom_parse_note(note_element) +
Mixin providing note-related operations with pythonic method names.
+18 def notes_get( +19 self: "OsmApi", +20 min_lon: float, +21 min_lat: float, +22 max_lon: float, +23 max_lat: float, +24 limit: int = 100, +25 closed: int = 7, +26 ) -> list[dict[str, Any]]: +27 """ +28 Returns a list of dicts of notes in the specified bounding box. +29 +30 The limit parameter defines how many results should be returned. +31 +32 closed specifies the number of days a bug needs to be closed +33 to no longer be returned. +34 The value 0 means only open bugs are returned, +35 -1 means all bugs are returned. +36 +37 All parameters are optional. +38 """ +39 path = "/api/0.6/notes" +40 params = { +41 "bbox": f"{min_lon:f},{min_lat:f},{max_lon:f},{max_lat:f}", +42 "limit": limit, +43 "closed": closed, +44 } +45 data = self._session._get(path, params=params) +46 return parser.parse_notes(data) +
Returns a list of dicts of notes in the specified bounding box.
+ +The limit parameter defines how many results should be returned.
+ +closed specifies the number of days a bug needs to be closed +to no longer be returned. +The value 0 means only open bugs are returned, +-1 means all bugs are returned.
+ +All parameters are optional.
+48 def note_get(self: "OsmApi", note_id: int) -> dict[str, Any]: +49 """ +50 Returns a note as dict. +51 +52 `note_id` is the unique identifier of the note. +53 """ +54 uri = f"/api/0.6/notes/{note_id}" +55 data = self._session._get(uri) +56 note_element = cast( +57 Element, dom.OsmResponseToDom(data, tag="note", single=True) +58 ) +59 return dom.dom_parse_note(note_element) +
Returns a note as dict.
+ +note_id is the unique identifier of the note.
61 def note_create(self: "OsmApi", note_data: dict[str, Any]) -> dict[str, Any]: +62 """ +63 Creates a note based on the supplied `note_data` dict: +64 +65 #!python +66 { +67 'lat': latitude of note, +68 'lon': longitude of note, +69 'text': text of the note, +70 } +71 +72 Returns updated note data. +73 """ +74 uri = "/api/0.6/notes" +75 return self._note_action(uri, params=note_data) +
Creates a note based on the supplied note_data dict:
#!python
+{
+ 'lat': latitude of note,
+ 'lon': longitude of note,
+ 'text': text of the note,
+}
+
+
+Returns updated note data.
+77 def note_comment(self: "OsmApi", note_id: int, comment: str) -> dict[str, Any]: +78 """ +79 Adds a new comment to a note. +80 +81 Returns the updated note. +82 """ +83 path = f"/api/0.6/notes/{note_id}/comment" +84 return self._note_action(path, comment) +
Adds a new comment to a note.
+ +Returns the updated note.
+86 def note_close( +87 self: "OsmApi", note_id: int, comment: Optional[str] = None +88 ) -> dict[str, Any]: +89 """ +90 Closes a note. +91 +92 Returns the updated note. +93 +94 If no authentication information are provided, +95 `OsmApi.UsernamePasswordMissingError` is raised. +96 """ +97 path = f"/api/0.6/notes/{note_id}/close" +98 return self._note_action(path, comment, optional_auth=False) +
Closes a note.
+ +Returns the updated note.
+ +If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
100 def note_reopen( +101 self: "OsmApi", note_id: int, comment: Optional[str] = None +102 ) -> dict[str, Any]: +103 """ +104 Reopens a note. +105 +106 Returns the updated note. +107 +108 If no authentication information are provided, +109 `OsmApi.UsernamePasswordMissingError` is raised. +110 +111 If the requested element has been deleted, +112 `OsmApi.ElementDeletedApiError` is raised. +113 +114 If the requested element can not be found, +115 `OsmApi.ElementNotFoundApiError` is raised. +116 """ +117 path = f"/api/0.6/notes/{note_id}/reopen" +118 return self._note_action(path, comment, optional_auth=False) +
Reopens a note.
+ +Returns the updated note.
+ +If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If the requested element has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
120 def notes_search( +121 self: "OsmApi", query: str, limit: int = 100, closed: int = 7 +122 ) -> list[dict[str, Any]]: +123 """ +124 Returns a list of dicts of notes that match the given search query. +125 +126 The limit parameter defines how many results should be returned. +127 +128 closed specifies the number of days a bug needs to be closed +129 to no longer be returned. +130 The value 0 means only open bugs are returned, +131 -1 means all bugs are returned. +132 """ +133 uri = "/api/0.6/notes/search" +134 params: dict[str, Any] = { +135 "q": query, +136 "limit": limit, +137 "closed": closed, +138 } +139 data = self._session._get(uri, params=params) +140 return parser.parse_notes(data) +
Returns a list of dicts of notes that match the given search query.
+ +The limit parameter defines how many results should be returned.
+ +closed specifies the number of days a bug needs to be closed +to no longer be returned. +The value 0 means only open bugs are returned, +-1 means all bugs are returned.
+import xml.dom.minidom -import xml.parsers.expat - -from . import errors -from . import dom - - -def ParseOsm(data): - """ - Parse osm data. - - Returns list of dict: - - #!python - { - type: node|way|relation, - data: {} - } - """ - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("osm")[0] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - result = [] - for elem in data.childNodes: - if elem.nodeName == "node": - result.append({"type": elem.nodeName, "data": dom.DomParseNode(elem)}) - elif elem.nodeName == "way": - result.append({"type": elem.nodeName, "data": dom.DomParseWay(elem)}) - elif elem.nodeName == "relation": - result.append({"type": elem.nodeName, "data": dom.DomParseRelation(elem)}) - return result - - -def ParseOsc(data): - """ - Parse osc data. - - Returns list of dict: - - #!python - { - type: node|way|relation, - action: create|delete|modify, - data: {} - } - """ - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("osmChange")[0] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - result = [] - for action in data.childNodes: - if action.nodeName == "#text": - continue - for elem in action.childNodes: - if elem.nodeName == "node": - result.append( - { - "action": action.nodeName, - "type": elem.nodeName, - "data": dom.DomParseNode(elem), - } - ) - elif elem.nodeName == "way": - result.append( - { - "action": action.nodeName, - "type": elem.nodeName, - "data": dom.DomParseWay(elem), - } - ) - elif elem.nodeName == "relation": - result.append( - { - "action": action.nodeName, - "type": elem.nodeName, - "data": dom.DomParseRelation(elem), - } - ) - return result - - -def ParseNotes(data): - """ - Parse notes data. - - Returns a list of dict: - - #!python - [ - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - }, - { ... } - ] - """ - noteElements = dom.OsmResponseToDom(data, tag="note", allow_empty=True) - result = [] - for noteElement in noteElements: - note = dom.DomParseNote(noteElement) - result.append(note) - return result -
1import xml.dom.minidom + 2import xml.parsers.expat + 3from typing import Any, cast + 4from xml.dom.minidom import Element + 5 + 6from . import errors + 7from . import dom + 8 + 9 + 10def parse_osm(data: bytes) -> list[dict[str, Any]]: + 11 """ + 12 Parse osm data. + 13 + 14 Returns list of dict: + 15 + 16 #!python + 17 { + 18 type: node|way|relation, + 19 data: {} + 20 } + 21 """ + 22 try: + 23 data_parsed = xml.dom.minidom.parseString(data) + 24 data_parsed = data_parsed.getElementsByTagName("osm")[0] # type: ignore[assignment] # noqa: E501 + 25 except (xml.parsers.expat.ExpatError, IndexError) as e: + 26 raise errors.XmlResponseInvalidError( + 27 f"The XML response from the OSM API is invalid: {e!r}" + 28 ) from e + 29 + 30 result: list[dict[str, Any]] = [] + 31 for elem in data_parsed.childNodes: + 32 if elem.nodeName == "node": + 33 result.append({"type": elem.nodeName, "data": dom.dom_parse_node(elem)}) # type: ignore[arg-type] # noqa: E501 + 34 elif elem.nodeName == "way": + 35 result.append({"type": elem.nodeName, "data": dom.dom_parse_way(elem)}) # type: ignore[arg-type] # noqa: E501 + 36 elif elem.nodeName == "relation": + 37 result.append({"type": elem.nodeName, "data": dom.dom_parse_relation(elem)}) # type: ignore[arg-type] # noqa: E501 + 38 return result + 39 + 40 + 41def parse_osc(data: bytes) -> list[dict[str, Any]]: + 42 """ + 43 Parse osc data. + 44 + 45 Returns list of dict: + 46 + 47 #!python + 48 { + 49 type: node|way|relation, + 50 action: create|delete|modify, + 51 data: {} + 52 } + 53 """ + 54 try: + 55 data_parsed = xml.dom.minidom.parseString(data) + 56 data_parsed = data_parsed.getElementsByTagName("osmChange")[0] # type: ignore[assignment] # noqa: E501 + 57 except (xml.parsers.expat.ExpatError, IndexError) as e: + 58 raise errors.XmlResponseInvalidError( + 59 f"The XML response from the OSM API is invalid: {e!r}" + 60 ) from e + 61 + 62 result: list[dict[str, Any]] = [] + 63 for action in data_parsed.childNodes: + 64 if action.nodeName == "#text": + 65 continue + 66 for elem in action.childNodes: + 67 if elem.nodeName == "node": + 68 result.append( + 69 { + 70 "action": action.nodeName, + 71 "type": elem.nodeName, + 72 "data": dom.dom_parse_node(elem), # type: ignore[arg-type] + 73 } + 74 ) + 75 elif elem.nodeName == "way": + 76 result.append( + 77 { + 78 "action": action.nodeName, + 79 "type": elem.nodeName, + 80 "data": dom.dom_parse_way(elem), # type: ignore[arg-type] + 81 } + 82 ) + 83 elif elem.nodeName == "relation": + 84 result.append( + 85 { + 86 "action": action.nodeName, + 87 "type": elem.nodeName, + 88 "data": dom.dom_parse_relation(elem), # type: ignore[arg-type] + 89 } + 90 ) + 91 return result + 92 + 93 + 94def parse_notes(data: bytes) -> list[dict[str, Any]]: + 95 """ + 96 Parse notes data. + 97 + 98 Returns a list of dict: + 99 +100 #!python +101 [ +102 { +103 'id': integer, +104 'action': opened|commented|closed, +105 'status': open|closed +106 'date_created': creation date +107 'date_closed': closing data|None +108 'uid': User ID|None +109 'user': User name|None +110 'comments': {} +111 }, +112 { ... } +113 ] +114 """ +115 noteElements = cast( +116 list[Element], dom.OsmResponseToDom(data, tag="note", allow_empty=True) +117 ) +118 result: list[dict[str, Any]] = [] +119 for noteElement in noteElements: +120 note = dom.dom_parse_note(noteElement) +121 result.append(note) +122 return result +
11def parse_osm(data: bytes) -> list[dict[str, Any]]: +12 """ +13 Parse osm data. +14 +15 Returns list of dict: +16 +17 #!python +18 { +19 type: node|way|relation, +20 data: {} +21 } +22 """ +23 try: +24 data_parsed = xml.dom.minidom.parseString(data) +25 data_parsed = data_parsed.getElementsByTagName("osm")[0] # type: ignore[assignment] # noqa: E501 +26 except (xml.parsers.expat.ExpatError, IndexError) as e: +27 raise errors.XmlResponseInvalidError( +28 f"The XML response from the OSM API is invalid: {e!r}" +29 ) from e +30 +31 result: list[dict[str, Any]] = [] +32 for elem in data_parsed.childNodes: +33 if elem.nodeName == "node": +34 result.append({"type": elem.nodeName, "data": dom.dom_parse_node(elem)}) # type: ignore[arg-type] # noqa: E501 +35 elif elem.nodeName == "way": +36 result.append({"type": elem.nodeName, "data": dom.dom_parse_way(elem)}) # type: ignore[arg-type] # noqa: E501 +37 elif elem.nodeName == "relation": +38 result.append({"type": elem.nodeName, "data": dom.dom_parse_relation(elem)}) # type: ignore[arg-type] # noqa: E501 +39 return result +
def ParseOsm(data): - """ - Parse osm data. - - Returns list of dict: - - #!python - { - type: node|way|relation, - data: {} - } - """ - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("osm")[0] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - result = [] - for elem in data.childNodes: - if elem.nodeName == "node": - result.append({"type": elem.nodeName, "data": dom.DomParseNode(elem)}) - elif elem.nodeName == "way": - result.append({"type": elem.nodeName, "data": dom.DomParseWay(elem)}) - elif elem.nodeName == "relation": - result.append({"type": elem.nodeName, "data": dom.DomParseRelation(elem)}) - return result -
Parse osm data.
@@ -238,70 +243,70 @@42def parse_osc(data: bytes) -> list[dict[str, Any]]: +43 """ +44 Parse osc data. +45 +46 Returns list of dict: +47 +48 #!python +49 { +50 type: node|way|relation, +51 action: create|delete|modify, +52 data: {} +53 } +54 """ +55 try: +56 data_parsed = xml.dom.minidom.parseString(data) +57 data_parsed = data_parsed.getElementsByTagName("osmChange")[0] # type: ignore[assignment] # noqa: E501 +58 except (xml.parsers.expat.ExpatError, IndexError) as e: +59 raise errors.XmlResponseInvalidError( +60 f"The XML response from the OSM API is invalid: {e!r}" +61 ) from e +62 +63 result: list[dict[str, Any]] = [] +64 for action in data_parsed.childNodes: +65 if action.nodeName == "#text": +66 continue +67 for elem in action.childNodes: +68 if elem.nodeName == "node": +69 result.append( +70 { +71 "action": action.nodeName, +72 "type": elem.nodeName, +73 "data": dom.dom_parse_node(elem), # type: ignore[arg-type] +74 } +75 ) +76 elif elem.nodeName == "way": +77 result.append( +78 { +79 "action": action.nodeName, +80 "type": elem.nodeName, +81 "data": dom.dom_parse_way(elem), # type: ignore[arg-type] +82 } +83 ) +84 elif elem.nodeName == "relation": +85 result.append( +86 { +87 "action": action.nodeName, +88 "type": elem.nodeName, +89 "data": dom.dom_parse_relation(elem), # type: ignore[arg-type] +90 } +91 ) +92 return result +
def ParseOsc(data): - """ - Parse osc data. - - Returns list of dict: - - #!python - { - type: node|way|relation, - action: create|delete|modify, - data: {} - } - """ - try: - data = xml.dom.minidom.parseString(data) - data = data.getElementsByTagName("osmChange")[0] - except (xml.parsers.expat.ExpatError, IndexError) as e: - raise errors.XmlResponseInvalidError( - f"The XML response from the OSM API is invalid: {e!r}" - ) from e - - result = [] - for action in data.childNodes: - if action.nodeName == "#text": - continue - for elem in action.childNodes: - if elem.nodeName == "node": - result.append( - { - "action": action.nodeName, - "type": elem.nodeName, - "data": dom.DomParseNode(elem), - } - ) - elif elem.nodeName == "way": - result.append( - { - "action": action.nodeName, - "type": elem.nodeName, - "data": dom.DomParseWay(elem), - } - ) - elif elem.nodeName == "relation": - result.append( - { - "action": action.nodeName, - "type": elem.nodeName, - "data": dom.DomParseRelation(elem), - } - ) - return result -
Parse osc data.
@@ -318,46 +323,48 @@95def parse_notes(data: bytes) -> list[dict[str, Any]]: + 96 """ + 97 Parse notes data. + 98 + 99 Returns a list of dict: +100 +101 #!python +102 [ +103 { +104 'id': integer, +105 'action': opened|commented|closed, +106 'status': open|closed +107 'date_created': creation date +108 'date_closed': closing data|None +109 'uid': User ID|None +110 'user': User name|None +111 'comments': {} +112 }, +113 { ... } +114 ] +115 """ +116 noteElements = cast( +117 list[Element], dom.OsmResponseToDom(data, tag="note", allow_empty=True) +118 ) +119 result: list[dict[str, Any]] = [] +120 for noteElement in noteElements: +121 note = dom.dom_parse_note(noteElement) +122 result.append(note) +123 return result +
def ParseNotes(data): - """ - Parse notes data. - - Returns a list of dict: - - #!python - [ - { - 'id': integer, - 'action': opened|commented|closed, - 'status': open|closed - 'date_created': creation date - 'date_closed': closing data|None - 'uid': User ID|None - 'user': User name|None - 'comments': {} - }, - { ... } - ] - """ - noteElements = dom.OsmResponseToDom(data, tag="note", allow_empty=True) - result = [] - for noteElement in noteElements: - note = dom.DomParseNote(noteElement) - result.append(note) - return result -
Parse notes data.
@@ -483,12 +490,26 @@Relation operations for the OpenStreetMap API.
+ +This module provides pythonic (snake_case) methods for working with OSM relations.
+1""" + 2Relation operations for the OpenStreetMap API. + 3 + 4This module provides pythonic (snake_case) methods for working with OSM relations. + 5""" + 6 + 7from typing import Any, Optional, TYPE_CHECKING, cast + 8from xml.dom.minidom import Element + 9 + 10from . import dom, parser + 11 + 12if TYPE_CHECKING: + 13 from .OsmApi import OsmApi + 14 + 15 + 16class RelationMixin: + 17 """Mixin providing relation-related operations with pythonic method names.""" + 18 + 19 def relation_get( + 20 self: "OsmApi", relation_id: int, relation_version: int = -1 + 21 ) -> dict[str, Any]: + 22 """ + 23 Returns relation with `relation_id` as a dict. + 24 + 25 If `relation_version` is supplied, this specific version is returned, + 26 otherwise the latest version is returned. + 27 + 28 If the requested element has been deleted, + 29 `OsmApi.ElementDeletedApiError` is raised. + 30 + 31 If the requested element can not be found, + 32 `OsmApi.ElementNotFoundApiError` is raised. + 33 """ + 34 uri = f"/api/0.6/relation/{relation_id}" + 35 if relation_version != -1: + 36 uri += f"/{relation_version}" + 37 data = self._session._get(uri) + 38 relation = cast( + 39 Element, dom.OsmResponseToDom(data, tag="relation", single=True) + 40 ) + 41 return dom.dom_parse_relation(relation) + 42 + 43 def relation_create( + 44 self: "OsmApi", relation_data: dict[str, Any] + 45 ) -> Optional[dict[str, Any]]: + 46 """ + 47 Creates a relation based on the supplied `relation_data` dict. + 48 + 49 If no authentication information are provided, + 50 `OsmApi.UsernamePasswordMissingError` is raised. + 51 + 52 If the supplied information contain an existing relation, + 53 `OsmApi.OsmTypeAlreadyExistsError` is raised. + 54 + 55 If there is no open changeset, + 56 `OsmApi.NoChangesetOpenError` is raised. + 57 + 58 If the changeset is already closed, + 59 `OsmApi.ChangesetClosedApiError` is raised. + 60 """ + 61 return self._do("create", "relation", relation_data) + 62 + 63 def relation_update( + 64 self: "OsmApi", relation_data: dict[str, Any] + 65 ) -> Optional[dict[str, Any]]: + 66 """ + 67 Updates relation with the supplied `relation_data` dict. + 68 + 69 If no authentication information are provided, + 70 `OsmApi.UsernamePasswordMissingError` is raised. + 71 + 72 If there is no open changeset, + 73 `OsmApi.NoChangesetOpenError` is raised. + 74 + 75 If the changeset is already closed, + 76 `OsmApi.ChangesetClosedApiError` is raised. + 77 """ + 78 return self._do("modify", "relation", relation_data) + 79 + 80 def relation_delete( + 81 self: "OsmApi", relation_data: dict[str, Any] + 82 ) -> Optional[dict[str, Any]]: + 83 """ + 84 Delete relation with `relation_data`. + 85 + 86 If no authentication information are provided, + 87 `OsmApi.UsernamePasswordMissingError` is raised. + 88 + 89 If there is no open changeset, + 90 `OsmApi.NoChangesetOpenError` is raised. + 91 + 92 If the changeset is already closed, + 93 `OsmApi.ChangesetClosedApiError` is raised. + 94 """ + 95 return self._do("delete", "relation", relation_data) + 96 + 97 def relation_history(self: "OsmApi", relation_id: int) -> dict[int, dict[str, Any]]: + 98 """ + 99 Returns dict with version as key. +100 +101 If the requested element can not be found, +102 `OsmApi.ElementNotFoundApiError` is raised. +103 """ +104 uri = f"/api/0.6/relation/{relation_id}/history" +105 data = self._session._get(uri) +106 relations = cast(list[Element], dom.OsmResponseToDom(data, tag="relation")) +107 result: dict[int, dict[str, Any]] = {} +108 for relation in relations: +109 relation_data = dom.dom_parse_relation(relation) +110 result[relation_data["version"]] = relation_data +111 return result +112 +113 def relation_relations(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +114 """ +115 Returns a list of dicts of relation data containing relation `relation_id`. +116 +117 If the requested element can not be found, +118 `OsmApi.ElementNotFoundApiError` is raised. +119 """ +120 uri = f"/api/0.6/relation/{relation_id}/relations" +121 data = self._session._get(uri) +122 relations = cast( +123 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +124 ) +125 result: list[dict[str, Any]] = [] +126 for relation in relations: +127 relation_data = dom.dom_parse_relation(relation) +128 result.append(relation_data) +129 return result +130 +131 def relation_full_recur(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +132 """ +133 Returns the full data (all levels) for relation `relation_id` as list of dicts. +134 +135 This function is useful for relations containing other relations. +136 +137 If you don't need all levels, use `relation_full` instead, +138 which return only 2 levels. +139 +140 If any relation (on any level) has been deleted, +141 `OsmApi.ElementDeletedApiError` is raised. +142 +143 If the requested element can not be found, +144 `OsmApi.ElementNotFoundApiError` is raised. +145 """ +146 data = [] +147 todo = [relation_id] +148 done = [] +149 while todo: +150 rid = todo.pop(0) +151 done.append(rid) +152 temp = self.relation_full(rid) +153 for item in temp: +154 if item["type"] != "relation": +155 continue +156 if item["data"]["id"] in done: +157 continue +158 todo.append(item["data"]["id"]) +159 data += temp +160 return data +161 +162 def relation_full(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +163 """ +164 Returns the full data (two levels) for relation `relation_id` as list of dicts. +165 +166 If you need all levels, use `relation_full_recur`. +167 +168 If the requested element has been deleted, +169 `OsmApi.ElementDeletedApiError` is raised. +170 +171 If the requested element can not be found, +172 `OsmApi.ElementNotFoundApiError` is raised. +173 """ +174 uri = f"/api/0.6/relation/{relation_id}/full" +175 data = self._session._get(uri) +176 return parser.parse_osm(data) +177 +178 def relations_get( +179 self: "OsmApi", relation_id_list: list[int] +180 ) -> dict[int, dict[str, Any]]: +181 """ +182 Returns dict with the id of the relation as a key +183 for each relation in `relation_id_list`. +184 +185 `relation_id_list` is a list containing unique identifiers +186 for multiple relations. +187 """ +188 relation_list = ",".join([str(x) for x in relation_id_list]) +189 uri = f"/api/0.6/relations?relations={relation_list}" +190 data = self._session._get(uri) +191 relations = cast(list[Element], dom.OsmResponseToDom(data, tag="relation")) +192 result: dict[int, dict[str, Any]] = {} +193 for relation in relations: +194 relation_data = dom.dom_parse_relation(relation) +195 result[relation_data["id"]] = relation_data +196 return result +
17class RelationMixin: + 18 """Mixin providing relation-related operations with pythonic method names.""" + 19 + 20 def relation_get( + 21 self: "OsmApi", relation_id: int, relation_version: int = -1 + 22 ) -> dict[str, Any]: + 23 """ + 24 Returns relation with `relation_id` as a dict. + 25 + 26 If `relation_version` is supplied, this specific version is returned, + 27 otherwise the latest version is returned. + 28 + 29 If the requested element has been deleted, + 30 `OsmApi.ElementDeletedApiError` is raised. + 31 + 32 If the requested element can not be found, + 33 `OsmApi.ElementNotFoundApiError` is raised. + 34 """ + 35 uri = f"/api/0.6/relation/{relation_id}" + 36 if relation_version != -1: + 37 uri += f"/{relation_version}" + 38 data = self._session._get(uri) + 39 relation = cast( + 40 Element, dom.OsmResponseToDom(data, tag="relation", single=True) + 41 ) + 42 return dom.dom_parse_relation(relation) + 43 + 44 def relation_create( + 45 self: "OsmApi", relation_data: dict[str, Any] + 46 ) -> Optional[dict[str, Any]]: + 47 """ + 48 Creates a relation based on the supplied `relation_data` dict. + 49 + 50 If no authentication information are provided, + 51 `OsmApi.UsernamePasswordMissingError` is raised. + 52 + 53 If the supplied information contain an existing relation, + 54 `OsmApi.OsmTypeAlreadyExistsError` is raised. + 55 + 56 If there is no open changeset, + 57 `OsmApi.NoChangesetOpenError` is raised. + 58 + 59 If the changeset is already closed, + 60 `OsmApi.ChangesetClosedApiError` is raised. + 61 """ + 62 return self._do("create", "relation", relation_data) + 63 + 64 def relation_update( + 65 self: "OsmApi", relation_data: dict[str, Any] + 66 ) -> Optional[dict[str, Any]]: + 67 """ + 68 Updates relation with the supplied `relation_data` dict. + 69 + 70 If no authentication information are provided, + 71 `OsmApi.UsernamePasswordMissingError` is raised. + 72 + 73 If there is no open changeset, + 74 `OsmApi.NoChangesetOpenError` is raised. + 75 + 76 If the changeset is already closed, + 77 `OsmApi.ChangesetClosedApiError` is raised. + 78 """ + 79 return self._do("modify", "relation", relation_data) + 80 + 81 def relation_delete( + 82 self: "OsmApi", relation_data: dict[str, Any] + 83 ) -> Optional[dict[str, Any]]: + 84 """ + 85 Delete relation with `relation_data`. + 86 + 87 If no authentication information are provided, + 88 `OsmApi.UsernamePasswordMissingError` is raised. + 89 + 90 If there is no open changeset, + 91 `OsmApi.NoChangesetOpenError` is raised. + 92 + 93 If the changeset is already closed, + 94 `OsmApi.ChangesetClosedApiError` is raised. + 95 """ + 96 return self._do("delete", "relation", relation_data) + 97 + 98 def relation_history(self: "OsmApi", relation_id: int) -> dict[int, dict[str, Any]]: + 99 """ +100 Returns dict with version as key. +101 +102 If the requested element can not be found, +103 `OsmApi.ElementNotFoundApiError` is raised. +104 """ +105 uri = f"/api/0.6/relation/{relation_id}/history" +106 data = self._session._get(uri) +107 relations = cast(list[Element], dom.OsmResponseToDom(data, tag="relation")) +108 result: dict[int, dict[str, Any]] = {} +109 for relation in relations: +110 relation_data = dom.dom_parse_relation(relation) +111 result[relation_data["version"]] = relation_data +112 return result +113 +114 def relation_relations(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +115 """ +116 Returns a list of dicts of relation data containing relation `relation_id`. +117 +118 If the requested element can not be found, +119 `OsmApi.ElementNotFoundApiError` is raised. +120 """ +121 uri = f"/api/0.6/relation/{relation_id}/relations" +122 data = self._session._get(uri) +123 relations = cast( +124 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +125 ) +126 result: list[dict[str, Any]] = [] +127 for relation in relations: +128 relation_data = dom.dom_parse_relation(relation) +129 result.append(relation_data) +130 return result +131 +132 def relation_full_recur(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +133 """ +134 Returns the full data (all levels) for relation `relation_id` as list of dicts. +135 +136 This function is useful for relations containing other relations. +137 +138 If you don't need all levels, use `relation_full` instead, +139 which return only 2 levels. +140 +141 If any relation (on any level) has been deleted, +142 `OsmApi.ElementDeletedApiError` is raised. +143 +144 If the requested element can not be found, +145 `OsmApi.ElementNotFoundApiError` is raised. +146 """ +147 data = [] +148 todo = [relation_id] +149 done = [] +150 while todo: +151 rid = todo.pop(0) +152 done.append(rid) +153 temp = self.relation_full(rid) +154 for item in temp: +155 if item["type"] != "relation": +156 continue +157 if item["data"]["id"] in done: +158 continue +159 todo.append(item["data"]["id"]) +160 data += temp +161 return data +162 +163 def relation_full(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +164 """ +165 Returns the full data (two levels) for relation `relation_id` as list of dicts. +166 +167 If you need all levels, use `relation_full_recur`. +168 +169 If the requested element has been deleted, +170 `OsmApi.ElementDeletedApiError` is raised. +171 +172 If the requested element can not be found, +173 `OsmApi.ElementNotFoundApiError` is raised. +174 """ +175 uri = f"/api/0.6/relation/{relation_id}/full" +176 data = self._session._get(uri) +177 return parser.parse_osm(data) +178 +179 def relations_get( +180 self: "OsmApi", relation_id_list: list[int] +181 ) -> dict[int, dict[str, Any]]: +182 """ +183 Returns dict with the id of the relation as a key +184 for each relation in `relation_id_list`. +185 +186 `relation_id_list` is a list containing unique identifiers +187 for multiple relations. +188 """ +189 relation_list = ",".join([str(x) for x in relation_id_list]) +190 uri = f"/api/0.6/relations?relations={relation_list}" +191 data = self._session._get(uri) +192 relations = cast(list[Element], dom.OsmResponseToDom(data, tag="relation")) +193 result: dict[int, dict[str, Any]] = {} +194 for relation in relations: +195 relation_data = dom.dom_parse_relation(relation) +196 result[relation_data["id"]] = relation_data +197 return result +
Mixin providing relation-related operations with pythonic method names.
+20 def relation_get( +21 self: "OsmApi", relation_id: int, relation_version: int = -1 +22 ) -> dict[str, Any]: +23 """ +24 Returns relation with `relation_id` as a dict. +25 +26 If `relation_version` is supplied, this specific version is returned, +27 otherwise the latest version is returned. +28 +29 If the requested element has been deleted, +30 `OsmApi.ElementDeletedApiError` is raised. +31 +32 If the requested element can not be found, +33 `OsmApi.ElementNotFoundApiError` is raised. +34 """ +35 uri = f"/api/0.6/relation/{relation_id}" +36 if relation_version != -1: +37 uri += f"/{relation_version}" +38 data = self._session._get(uri) +39 relation = cast( +40 Element, dom.OsmResponseToDom(data, tag="relation", single=True) +41 ) +42 return dom.dom_parse_relation(relation) +
Returns relation with relation_id as a dict.
If relation_version is supplied, this specific version is returned,
+otherwise the latest version is returned.
If the requested element has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
44 def relation_create( +45 self: "OsmApi", relation_data: dict[str, Any] +46 ) -> Optional[dict[str, Any]]: +47 """ +48 Creates a relation based on the supplied `relation_data` dict. +49 +50 If no authentication information are provided, +51 `OsmApi.UsernamePasswordMissingError` is raised. +52 +53 If the supplied information contain an existing relation, +54 `OsmApi.OsmTypeAlreadyExistsError` is raised. +55 +56 If there is no open changeset, +57 `OsmApi.NoChangesetOpenError` is raised. +58 +59 If the changeset is already closed, +60 `OsmApi.ChangesetClosedApiError` is raised. +61 """ +62 return self._do("create", "relation", relation_data) +
Creates a relation based on the supplied relation_data dict.
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing relation,
+OsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
64 def relation_update( +65 self: "OsmApi", relation_data: dict[str, Any] +66 ) -> Optional[dict[str, Any]]: +67 """ +68 Updates relation with the supplied `relation_data` dict. +69 +70 If no authentication information are provided, +71 `OsmApi.UsernamePasswordMissingError` is raised. +72 +73 If there is no open changeset, +74 `OsmApi.NoChangesetOpenError` is raised. +75 +76 If the changeset is already closed, +77 `OsmApi.ChangesetClosedApiError` is raised. +78 """ +79 return self._do("modify", "relation", relation_data) +
Updates relation with the supplied relation_data dict.
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
81 def relation_delete( +82 self: "OsmApi", relation_data: dict[str, Any] +83 ) -> Optional[dict[str, Any]]: +84 """ +85 Delete relation with `relation_data`. +86 +87 If no authentication information are provided, +88 `OsmApi.UsernamePasswordMissingError` is raised. +89 +90 If there is no open changeset, +91 `OsmApi.NoChangesetOpenError` is raised. +92 +93 If the changeset is already closed, +94 `OsmApi.ChangesetClosedApiError` is raised. +95 """ +96 return self._do("delete", "relation", relation_data) +
Delete relation with relation_data.
If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
98 def relation_history(self: "OsmApi", relation_id: int) -> dict[int, dict[str, Any]]: + 99 """ +100 Returns dict with version as key. +101 +102 If the requested element can not be found, +103 `OsmApi.ElementNotFoundApiError` is raised. +104 """ +105 uri = f"/api/0.6/relation/{relation_id}/history" +106 data = self._session._get(uri) +107 relations = cast(list[Element], dom.OsmResponseToDom(data, tag="relation")) +108 result: dict[int, dict[str, Any]] = {} +109 for relation in relations: +110 relation_data = dom.dom_parse_relation(relation) +111 result[relation_data["version"]] = relation_data +112 return result +
Returns dict with version as key.
+ +If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
114 def relation_relations(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +115 """ +116 Returns a list of dicts of relation data containing relation `relation_id`. +117 +118 If the requested element can not be found, +119 `OsmApi.ElementNotFoundApiError` is raised. +120 """ +121 uri = f"/api/0.6/relation/{relation_id}/relations" +122 data = self._session._get(uri) +123 relations = cast( +124 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +125 ) +126 result: list[dict[str, Any]] = [] +127 for relation in relations: +128 relation_data = dom.dom_parse_relation(relation) +129 result.append(relation_data) +130 return result +
Returns a list of dicts of relation data containing relation relation_id.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
132 def relation_full_recur(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +133 """ +134 Returns the full data (all levels) for relation `relation_id` as list of dicts. +135 +136 This function is useful for relations containing other relations. +137 +138 If you don't need all levels, use `relation_full` instead, +139 which return only 2 levels. +140 +141 If any relation (on any level) has been deleted, +142 `OsmApi.ElementDeletedApiError` is raised. +143 +144 If the requested element can not be found, +145 `OsmApi.ElementNotFoundApiError` is raised. +146 """ +147 data = [] +148 todo = [relation_id] +149 done = [] +150 while todo: +151 rid = todo.pop(0) +152 done.append(rid) +153 temp = self.relation_full(rid) +154 for item in temp: +155 if item["type"] != "relation": +156 continue +157 if item["data"]["id"] in done: +158 continue +159 todo.append(item["data"]["id"]) +160 data += temp +161 return data +
Returns the full data (all levels) for relation relation_id as list of dicts.
This function is useful for relations containing other relations.
+ +If you don't need all levels, use relation_full instead,
+which return only 2 levels.
If any relation (on any level) has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
163 def relation_full(self: "OsmApi", relation_id: int) -> list[dict[str, Any]]: +164 """ +165 Returns the full data (two levels) for relation `relation_id` as list of dicts. +166 +167 If you need all levels, use `relation_full_recur`. +168 +169 If the requested element has been deleted, +170 `OsmApi.ElementDeletedApiError` is raised. +171 +172 If the requested element can not be found, +173 `OsmApi.ElementNotFoundApiError` is raised. +174 """ +175 uri = f"/api/0.6/relation/{relation_id}/full" +176 data = self._session._get(uri) +177 return parser.parse_osm(data) +
Returns the full data (two levels) for relation relation_id as list of dicts.
If you need all levels, use relation_full_recur.
If the requested element has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
179 def relations_get( +180 self: "OsmApi", relation_id_list: list[int] +181 ) -> dict[int, dict[str, Any]]: +182 """ +183 Returns dict with the id of the relation as a key +184 for each relation in `relation_id_list`. +185 +186 `relation_id_list` is a list containing unique identifiers +187 for multiple relations. +188 """ +189 relation_list = ",".join([str(x) for x in relation_id_list]) +190 uri = f"/api/0.6/relations?relations={relation_list}" +191 data = self._session._get(uri) +192 relations = cast(list[Element], dom.OsmResponseToDom(data, tag="relation")) +193 result: dict[int, dict[str, Any]] = {} +194 for relation in relations: +195 relation_data = dom.dom_parse_relation(relation) +196 result[relation_data["id"]] = relation_data +197 return result +
Returns dict with the id of the relation as a key
+for each relation in relation_id_list.
relation_id_list is a list containing unique identifiers
+for multiple relations.
Way operations for the OpenStreetMap API.
+ +This module provides pythonic (snake_case) methods for working with OSM ways.
+1""" + 2Way operations for the OpenStreetMap API. + 3 + 4This module provides pythonic (snake_case) methods for working with OSM ways. + 5""" + 6 + 7from typing import Any, Optional, TYPE_CHECKING, cast + 8from xml.dom.minidom import Element + 9 + 10from . import dom, parser + 11 + 12if TYPE_CHECKING: + 13 from .OsmApi import OsmApi + 14 + 15 + 16class WayMixin: + 17 """Mixin providing way-related operations with pythonic method names.""" + 18 + 19 def way_get(self: "OsmApi", way_id: int, way_version: int = -1) -> dict[str, Any]: + 20 """ + 21 Returns way with `way_id` as a dict: + 22 + 23 #!python + 24 { + 25 'id': id of way, + 26 'tag': {} tags of this way, + 27 'nd': [] list of nodes belonging to this way + 28 'changeset': id of changeset of last change, + 29 'version': version number of way, + 30 'user': username of user that made the last change, + 31 'uid': id of user that made the last change, + 32 'timestamp': timestamp of last change, + 33 'visible': True|False + 34 } + 35 + 36 If `way_version` is supplied, this specific version is returned, + 37 otherwise the latest version is returned. + 38 + 39 If the requested element has been deleted, + 40 `OsmApi.ElementDeletedApiError` is raised. + 41 + 42 If the requested element can not be found, + 43 `OsmApi.ElementNotFoundApiError` is raised. + 44 """ + 45 uri = f"/api/0.6/way/{way_id}" + 46 if way_version != -1: + 47 uri += f"/{way_version}" + 48 data = self._session._get(uri) + 49 way = cast(Element, dom.OsmResponseToDom(data, tag="way", single=True)) + 50 return dom.dom_parse_way(way) + 51 + 52 def way_create( + 53 self: "OsmApi", way_data: dict[str, Any] + 54 ) -> Optional[dict[str, Any]]: + 55 """ + 56 Creates a way based on the supplied `way_data` dict: + 57 + 58 #!python + 59 { + 60 'nd': [] list of nodes, + 61 'tag': {} dict of tags, + 62 } + 63 + 64 Returns updated `way_data` (without timestamp): + 65 + 66 #!python + 67 { + 68 'id': id of node, + 69 'nd': [] list of nodes, + 70 'tag': {} dict of tags, + 71 'changeset': id of changeset of last change, + 72 'version': version number of way, + 73 'user': username of last change, + 74 'uid': id of user of last change, + 75 'visible': True|False + 76 } + 77 + 78 If no authentication information are provided, + 79 `OsmApi.UsernamePasswordMissingError` is raised. + 80 + 81 If the supplied information contain an existing node, + 82 `OsmApi.OsmTypeAlreadyExistsError` is raised. + 83 + 84 If there is no open changeset, + 85 `OsmApi.NoChangesetOpenError` is raised. + 86 + 87 If there is already an open changeset, + 88 `OsmApi.ChangesetAlreadyOpenError` is raised. + 89 + 90 If the changeset is already closed, + 91 `OsmApi.ChangesetClosedApiError` is raised. + 92 """ + 93 return self._do("create", "way", way_data) + 94 + 95 def way_update( + 96 self: "OsmApi", way_data: dict[str, Any] + 97 ) -> Optional[dict[str, Any]]: + 98 """ + 99 Updates way with the supplied `way_data` dict: +100 +101 #!python +102 { +103 'id': id of way, +104 'nd': [] list of nodes, +105 'tag': {}, +106 'version': version number of way, +107 } +108 +109 Returns updated `way_data` (without timestamp): +110 +111 #!python +112 { +113 'id': id of node, +114 'nd': [] list of nodes, +115 'tag': {} dict of tags, +116 'changeset': id of changeset of last change, +117 'version': version number of way, +118 'user': username of last change, +119 'uid': id of user of last change, +120 'visible': True|False +121 } +122 +123 If no authentication information are provided, +124 `OsmApi.UsernamePasswordMissingError` is raised. +125 +126 If there is no open changeset, +127 `OsmApi.NoChangesetOpenError` is raised. +128 +129 If there is already an open changeset, +130 `OsmApi.ChangesetAlreadyOpenError` is raised. +131 +132 If the changeset is already closed, +133 `OsmApi.ChangesetClosedApiError` is raised. +134 """ +135 return self._do("modify", "way", way_data) +136 +137 def way_delete( +138 self: "OsmApi", way_data: dict[str, Any] +139 ) -> Optional[dict[str, Any]]: +140 """ +141 Delete way with `way_data`: +142 +143 #!python +144 { +145 'id': id of way, +146 'nd': [] list of nodes, +147 'tag': dict of tags, +148 'version': version number of way, +149 } +150 +151 Returns updated `way_data` (without timestamp): +152 +153 #!python +154 { +155 'id': id of way, +156 'nd': [] list of nodes, +157 'tag': dict of tags, +158 'changeset': id of changeset of last change, +159 'version': version number of way, +160 'user': username of last change, +161 'uid': id of user of last change, +162 'visible': True|False +163 } +164 +165 If no authentication information are provided, +166 `OsmApi.UsernamePasswordMissingError` is raised. +167 +168 If there is no open changeset, +169 `OsmApi.NoChangesetOpenError` is raised. +170 +171 If the changeset is already closed, +172 `OsmApi.ChangesetClosedApiError` is raised. +173 """ +174 return self._do("delete", "way", way_data) +175 +176 def way_history(self: "OsmApi", way_id: int) -> dict[int, dict[str, Any]]: +177 """ +178 Returns dict with version as key: +179 +180 #!python +181 { +182 1: dict of way version 1, +183 2: dict of way version 2, +184 ... +185 } +186 +187 If the requested element can not be found, +188 `OsmApi.ElementNotFoundApiError` is raised. +189 """ +190 uri = f"/api/0.6/way/{way_id}/history" +191 data = self._session._get(uri) +192 ways = cast(list[Element], dom.OsmResponseToDom(data, tag="way")) +193 result: dict[int, dict[str, Any]] = {} +194 for way in ways: +195 way_data = dom.dom_parse_way(way) +196 result[way_data["version"]] = way_data +197 return result +198 +199 def way_relations(self: "OsmApi", way_id: int) -> list[dict[str, Any]]: +200 """ +201 Returns a list of dicts of relation data containing way `way_id`: +202 +203 #!python +204 [ +205 { +206 'id': id of Relation, +207 'member': [ +208 { +209 'ref': ID of referenced element, +210 'role': optional description of role in relation +211 'type': node|way|relation +212 }, +213 { +214 ... +215 } +216 ] +217 'tag': {} dict of tags, +218 'changeset': id of changeset of last change, +219 'version': version number of Way, +220 'user': username of user that made the last change, +221 'uid': id of user that made the last change, +222 'visible': True|False +223 }, +224 { +225 ... +226 }, +227 ] +228 +229 The `way_id` is a unique identifier for a way. +230 """ +231 uri = f"/api/0.6/way/{way_id}/relations" +232 data = self._session._get(uri) +233 relations = cast( +234 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +235 ) +236 result: list[dict[str, Any]] = [] +237 for relation in relations: +238 relation_data = dom.dom_parse_relation(relation) +239 result.append(relation_data) +240 return result +241 +242 def way_full(self: "OsmApi", way_id: int) -> list[dict[str, Any]]: +243 """ +244 Returns the full data for way `way_id` as list of dicts: +245 +246 #!python +247 [ +248 { +249 'type': node|way|relation, +250 'data': {} data dict for node|way|relation +251 }, +252 { ... } +253 ] +254 +255 The `way_id` is a unique identifier for a way. +256 +257 If the requested element has been deleted, +258 `OsmApi.ElementDeletedApiError` is raised. +259 +260 If the requested element can not be found, +261 `OsmApi.ElementNotFoundApiError` is raised. +262 """ +263 uri = f"/api/0.6/way/{way_id}/full" +264 data = self._session._get(uri) +265 return parser.parse_osm(data) +266 +267 def ways_get(self: "OsmApi", way_id_list: list[int]) -> dict[int, dict[str, Any]]: +268 """ +269 Returns dict with the id of the way as a key for +270 each way in `way_id_list`: +271 +272 #!python +273 { +274 '1234': dict of way data, +275 '5678': dict of way data, +276 ... +277 } +278 +279 `way_id_list` is a list containing unique identifiers for multiple ways. +280 """ +281 way_list = ",".join([str(x) for x in way_id_list]) +282 uri = f"/api/0.6/ways?ways={way_list}" +283 data = self._session._get(uri) +284 ways = cast(list[Element], dom.OsmResponseToDom(data, tag="way")) +285 result: dict[int, dict[str, Any]] = {} +286 for way in ways: +287 way_data = dom.dom_parse_way(way) +288 result[way_data["id"]] = way_data +289 return result +
17class WayMixin: + 18 """Mixin providing way-related operations with pythonic method names.""" + 19 + 20 def way_get(self: "OsmApi", way_id: int, way_version: int = -1) -> dict[str, Any]: + 21 """ + 22 Returns way with `way_id` as a dict: + 23 + 24 #!python + 25 { + 26 'id': id of way, + 27 'tag': {} tags of this way, + 28 'nd': [] list of nodes belonging to this way + 29 'changeset': id of changeset of last change, + 30 'version': version number of way, + 31 'user': username of user that made the last change, + 32 'uid': id of user that made the last change, + 33 'timestamp': timestamp of last change, + 34 'visible': True|False + 35 } + 36 + 37 If `way_version` is supplied, this specific version is returned, + 38 otherwise the latest version is returned. + 39 + 40 If the requested element has been deleted, + 41 `OsmApi.ElementDeletedApiError` is raised. + 42 + 43 If the requested element can not be found, + 44 `OsmApi.ElementNotFoundApiError` is raised. + 45 """ + 46 uri = f"/api/0.6/way/{way_id}" + 47 if way_version != -1: + 48 uri += f"/{way_version}" + 49 data = self._session._get(uri) + 50 way = cast(Element, dom.OsmResponseToDom(data, tag="way", single=True)) + 51 return dom.dom_parse_way(way) + 52 + 53 def way_create( + 54 self: "OsmApi", way_data: dict[str, Any] + 55 ) -> Optional[dict[str, Any]]: + 56 """ + 57 Creates a way based on the supplied `way_data` dict: + 58 + 59 #!python + 60 { + 61 'nd': [] list of nodes, + 62 'tag': {} dict of tags, + 63 } + 64 + 65 Returns updated `way_data` (without timestamp): + 66 + 67 #!python + 68 { + 69 'id': id of node, + 70 'nd': [] list of nodes, + 71 'tag': {} dict of tags, + 72 'changeset': id of changeset of last change, + 73 'version': version number of way, + 74 'user': username of last change, + 75 'uid': id of user of last change, + 76 'visible': True|False + 77 } + 78 + 79 If no authentication information are provided, + 80 `OsmApi.UsernamePasswordMissingError` is raised. + 81 + 82 If the supplied information contain an existing node, + 83 `OsmApi.OsmTypeAlreadyExistsError` is raised. + 84 + 85 If there is no open changeset, + 86 `OsmApi.NoChangesetOpenError` is raised. + 87 + 88 If there is already an open changeset, + 89 `OsmApi.ChangesetAlreadyOpenError` is raised. + 90 + 91 If the changeset is already closed, + 92 `OsmApi.ChangesetClosedApiError` is raised. + 93 """ + 94 return self._do("create", "way", way_data) + 95 + 96 def way_update( + 97 self: "OsmApi", way_data: dict[str, Any] + 98 ) -> Optional[dict[str, Any]]: + 99 """ +100 Updates way with the supplied `way_data` dict: +101 +102 #!python +103 { +104 'id': id of way, +105 'nd': [] list of nodes, +106 'tag': {}, +107 'version': version number of way, +108 } +109 +110 Returns updated `way_data` (without timestamp): +111 +112 #!python +113 { +114 'id': id of node, +115 'nd': [] list of nodes, +116 'tag': {} dict of tags, +117 'changeset': id of changeset of last change, +118 'version': version number of way, +119 'user': username of last change, +120 'uid': id of user of last change, +121 'visible': True|False +122 } +123 +124 If no authentication information are provided, +125 `OsmApi.UsernamePasswordMissingError` is raised. +126 +127 If there is no open changeset, +128 `OsmApi.NoChangesetOpenError` is raised. +129 +130 If there is already an open changeset, +131 `OsmApi.ChangesetAlreadyOpenError` is raised. +132 +133 If the changeset is already closed, +134 `OsmApi.ChangesetClosedApiError` is raised. +135 """ +136 return self._do("modify", "way", way_data) +137 +138 def way_delete( +139 self: "OsmApi", way_data: dict[str, Any] +140 ) -> Optional[dict[str, Any]]: +141 """ +142 Delete way with `way_data`: +143 +144 #!python +145 { +146 'id': id of way, +147 'nd': [] list of nodes, +148 'tag': dict of tags, +149 'version': version number of way, +150 } +151 +152 Returns updated `way_data` (without timestamp): +153 +154 #!python +155 { +156 'id': id of way, +157 'nd': [] list of nodes, +158 'tag': dict of tags, +159 'changeset': id of changeset of last change, +160 'version': version number of way, +161 'user': username of last change, +162 'uid': id of user of last change, +163 'visible': True|False +164 } +165 +166 If no authentication information are provided, +167 `OsmApi.UsernamePasswordMissingError` is raised. +168 +169 If there is no open changeset, +170 `OsmApi.NoChangesetOpenError` is raised. +171 +172 If the changeset is already closed, +173 `OsmApi.ChangesetClosedApiError` is raised. +174 """ +175 return self._do("delete", "way", way_data) +176 +177 def way_history(self: "OsmApi", way_id: int) -> dict[int, dict[str, Any]]: +178 """ +179 Returns dict with version as key: +180 +181 #!python +182 { +183 1: dict of way version 1, +184 2: dict of way version 2, +185 ... +186 } +187 +188 If the requested element can not be found, +189 `OsmApi.ElementNotFoundApiError` is raised. +190 """ +191 uri = f"/api/0.6/way/{way_id}/history" +192 data = self._session._get(uri) +193 ways = cast(list[Element], dom.OsmResponseToDom(data, tag="way")) +194 result: dict[int, dict[str, Any]] = {} +195 for way in ways: +196 way_data = dom.dom_parse_way(way) +197 result[way_data["version"]] = way_data +198 return result +199 +200 def way_relations(self: "OsmApi", way_id: int) -> list[dict[str, Any]]: +201 """ +202 Returns a list of dicts of relation data containing way `way_id`: +203 +204 #!python +205 [ +206 { +207 'id': id of Relation, +208 'member': [ +209 { +210 'ref': ID of referenced element, +211 'role': optional description of role in relation +212 'type': node|way|relation +213 }, +214 { +215 ... +216 } +217 ] +218 'tag': {} dict of tags, +219 'changeset': id of changeset of last change, +220 'version': version number of Way, +221 'user': username of user that made the last change, +222 'uid': id of user that made the last change, +223 'visible': True|False +224 }, +225 { +226 ... +227 }, +228 ] +229 +230 The `way_id` is a unique identifier for a way. +231 """ +232 uri = f"/api/0.6/way/{way_id}/relations" +233 data = self._session._get(uri) +234 relations = cast( +235 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +236 ) +237 result: list[dict[str, Any]] = [] +238 for relation in relations: +239 relation_data = dom.dom_parse_relation(relation) +240 result.append(relation_data) +241 return result +242 +243 def way_full(self: "OsmApi", way_id: int) -> list[dict[str, Any]]: +244 """ +245 Returns the full data for way `way_id` as list of dicts: +246 +247 #!python +248 [ +249 { +250 'type': node|way|relation, +251 'data': {} data dict for node|way|relation +252 }, +253 { ... } +254 ] +255 +256 The `way_id` is a unique identifier for a way. +257 +258 If the requested element has been deleted, +259 `OsmApi.ElementDeletedApiError` is raised. +260 +261 If the requested element can not be found, +262 `OsmApi.ElementNotFoundApiError` is raised. +263 """ +264 uri = f"/api/0.6/way/{way_id}/full" +265 data = self._session._get(uri) +266 return parser.parse_osm(data) +267 +268 def ways_get(self: "OsmApi", way_id_list: list[int]) -> dict[int, dict[str, Any]]: +269 """ +270 Returns dict with the id of the way as a key for +271 each way in `way_id_list`: +272 +273 #!python +274 { +275 '1234': dict of way data, +276 '5678': dict of way data, +277 ... +278 } +279 +280 `way_id_list` is a list containing unique identifiers for multiple ways. +281 """ +282 way_list = ",".join([str(x) for x in way_id_list]) +283 uri = f"/api/0.6/ways?ways={way_list}" +284 data = self._session._get(uri) +285 ways = cast(list[Element], dom.OsmResponseToDom(data, tag="way")) +286 result: dict[int, dict[str, Any]] = {} +287 for way in ways: +288 way_data = dom.dom_parse_way(way) +289 result[way_data["id"]] = way_data +290 return result +
Mixin providing way-related operations with pythonic method names.
+20 def way_get(self: "OsmApi", way_id: int, way_version: int = -1) -> dict[str, Any]: +21 """ +22 Returns way with `way_id` as a dict: +23 +24 #!python +25 { +26 'id': id of way, +27 'tag': {} tags of this way, +28 'nd': [] list of nodes belonging to this way +29 'changeset': id of changeset of last change, +30 'version': version number of way, +31 'user': username of user that made the last change, +32 'uid': id of user that made the last change, +33 'timestamp': timestamp of last change, +34 'visible': True|False +35 } +36 +37 If `way_version` is supplied, this specific version is returned, +38 otherwise the latest version is returned. +39 +40 If the requested element has been deleted, +41 `OsmApi.ElementDeletedApiError` is raised. +42 +43 If the requested element can not be found, +44 `OsmApi.ElementNotFoundApiError` is raised. +45 """ +46 uri = f"/api/0.6/way/{way_id}" +47 if way_version != -1: +48 uri += f"/{way_version}" +49 data = self._session._get(uri) +50 way = cast(Element, dom.OsmResponseToDom(data, tag="way", single=True)) +51 return dom.dom_parse_way(way) +
Returns way with way_id as a dict:
#!python
+{
+ 'id': id of way,
+ 'tag': {} tags of this way,
+ 'nd': [] list of nodes belonging to this way
+ 'changeset': id of changeset of last change,
+ 'version': version number of way,
+ 'user': username of user that made the last change,
+ 'uid': id of user that made the last change,
+ 'timestamp': timestamp of last change,
+ 'visible': True|False
+}
+
+
+If way_version is supplied, this specific version is returned,
+otherwise the latest version is returned.
If the requested element has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
53 def way_create( +54 self: "OsmApi", way_data: dict[str, Any] +55 ) -> Optional[dict[str, Any]]: +56 """ +57 Creates a way based on the supplied `way_data` dict: +58 +59 #!python +60 { +61 'nd': [] list of nodes, +62 'tag': {} dict of tags, +63 } +64 +65 Returns updated `way_data` (without timestamp): +66 +67 #!python +68 { +69 'id': id of node, +70 'nd': [] list of nodes, +71 'tag': {} dict of tags, +72 'changeset': id of changeset of last change, +73 'version': version number of way, +74 'user': username of last change, +75 'uid': id of user of last change, +76 'visible': True|False +77 } +78 +79 If no authentication information are provided, +80 `OsmApi.UsernamePasswordMissingError` is raised. +81 +82 If the supplied information contain an existing node, +83 `OsmApi.OsmTypeAlreadyExistsError` is raised. +84 +85 If there is no open changeset, +86 `OsmApi.NoChangesetOpenError` is raised. +87 +88 If there is already an open changeset, +89 `OsmApi.ChangesetAlreadyOpenError` is raised. +90 +91 If the changeset is already closed, +92 `OsmApi.ChangesetClosedApiError` is raised. +93 """ +94 return self._do("create", "way", way_data) +
Creates a way based on the supplied way_data dict:
#!python
+{
+ 'nd': [] list of nodes,
+ 'tag': {} dict of tags,
+}
+
+
+Returns updated way_data (without timestamp):
#!python
+{
+ 'id': id of node,
+ 'nd': [] list of nodes,
+ 'tag': {} dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of way,
+ 'user': username of last change,
+ 'uid': id of user of last change,
+ 'visible': True|False
+}
+
+
+If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing node,
+OsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
+OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
96 def way_update( + 97 self: "OsmApi", way_data: dict[str, Any] + 98 ) -> Optional[dict[str, Any]]: + 99 """ +100 Updates way with the supplied `way_data` dict: +101 +102 #!python +103 { +104 'id': id of way, +105 'nd': [] list of nodes, +106 'tag': {}, +107 'version': version number of way, +108 } +109 +110 Returns updated `way_data` (without timestamp): +111 +112 #!python +113 { +114 'id': id of node, +115 'nd': [] list of nodes, +116 'tag': {} dict of tags, +117 'changeset': id of changeset of last change, +118 'version': version number of way, +119 'user': username of last change, +120 'uid': id of user of last change, +121 'visible': True|False +122 } +123 +124 If no authentication information are provided, +125 `OsmApi.UsernamePasswordMissingError` is raised. +126 +127 If there is no open changeset, +128 `OsmApi.NoChangesetOpenError` is raised. +129 +130 If there is already an open changeset, +131 `OsmApi.ChangesetAlreadyOpenError` is raised. +132 +133 If the changeset is already closed, +134 `OsmApi.ChangesetClosedApiError` is raised. +135 """ +136 return self._do("modify", "way", way_data) +
Updates way with the supplied way_data dict:
#!python
+{
+ 'id': id of way,
+ 'nd': [] list of nodes,
+ 'tag': {},
+ 'version': version number of way,
+}
+
+
+Returns updated way_data (without timestamp):
#!python
+{
+ 'id': id of node,
+ 'nd': [] list of nodes,
+ 'tag': {} dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of way,
+ 'user': username of last change,
+ 'uid': id of user of last change,
+ 'visible': True|False
+}
+
+
+If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,
+OsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
138 def way_delete( +139 self: "OsmApi", way_data: dict[str, Any] +140 ) -> Optional[dict[str, Any]]: +141 """ +142 Delete way with `way_data`: +143 +144 #!python +145 { +146 'id': id of way, +147 'nd': [] list of nodes, +148 'tag': dict of tags, +149 'version': version number of way, +150 } +151 +152 Returns updated `way_data` (without timestamp): +153 +154 #!python +155 { +156 'id': id of way, +157 'nd': [] list of nodes, +158 'tag': dict of tags, +159 'changeset': id of changeset of last change, +160 'version': version number of way, +161 'user': username of last change, +162 'uid': id of user of last change, +163 'visible': True|False +164 } +165 +166 If no authentication information are provided, +167 `OsmApi.UsernamePasswordMissingError` is raised. +168 +169 If there is no open changeset, +170 `OsmApi.NoChangesetOpenError` is raised. +171 +172 If the changeset is already closed, +173 `OsmApi.ChangesetClosedApiError` is raised. +174 """ +175 return self._do("delete", "way", way_data) +
Delete way with way_data:
#!python
+{
+ 'id': id of way,
+ 'nd': [] list of nodes,
+ 'tag': dict of tags,
+ 'version': version number of way,
+}
+
+
+Returns updated way_data (without timestamp):
#!python
+{
+ 'id': id of way,
+ 'nd': [] list of nodes,
+ 'tag': dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of way,
+ 'user': username of last change,
+ 'uid': id of user of last change,
+ 'visible': True|False
+}
+
+
+If no authentication information are provided,
+OsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,
+OsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,
+OsmApi.ChangesetClosedApiError is raised.
177 def way_history(self: "OsmApi", way_id: int) -> dict[int, dict[str, Any]]: +178 """ +179 Returns dict with version as key: +180 +181 #!python +182 { +183 1: dict of way version 1, +184 2: dict of way version 2, +185 ... +186 } +187 +188 If the requested element can not be found, +189 `OsmApi.ElementNotFoundApiError` is raised. +190 """ +191 uri = f"/api/0.6/way/{way_id}/history" +192 data = self._session._get(uri) +193 ways = cast(list[Element], dom.OsmResponseToDom(data, tag="way")) +194 result: dict[int, dict[str, Any]] = {} +195 for way in ways: +196 way_data = dom.dom_parse_way(way) +197 result[way_data["version"]] = way_data +198 return result +
Returns dict with version as key:
+ +#!python
+{
+ 1: dict of way version 1,
+ 2: dict of way version 2,
+ ...
+}
+
+
+If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
200 def way_relations(self: "OsmApi", way_id: int) -> list[dict[str, Any]]: +201 """ +202 Returns a list of dicts of relation data containing way `way_id`: +203 +204 #!python +205 [ +206 { +207 'id': id of Relation, +208 'member': [ +209 { +210 'ref': ID of referenced element, +211 'role': optional description of role in relation +212 'type': node|way|relation +213 }, +214 { +215 ... +216 } +217 ] +218 'tag': {} dict of tags, +219 'changeset': id of changeset of last change, +220 'version': version number of Way, +221 'user': username of user that made the last change, +222 'uid': id of user that made the last change, +223 'visible': True|False +224 }, +225 { +226 ... +227 }, +228 ] +229 +230 The `way_id` is a unique identifier for a way. +231 """ +232 uri = f"/api/0.6/way/{way_id}/relations" +233 data = self._session._get(uri) +234 relations = cast( +235 list[Element], dom.OsmResponseToDom(data, tag="relation", allow_empty=True) +236 ) +237 result: list[dict[str, Any]] = [] +238 for relation in relations: +239 relation_data = dom.dom_parse_relation(relation) +240 result.append(relation_data) +241 return result +
Returns a list of dicts of relation data containing way way_id:
#!python
+[
+ {
+ 'id': id of Relation,
+ 'member': [
+ {
+ 'ref': ID of referenced element,
+ 'role': optional description of role in relation
+ 'type': node|way|relation
+ },
+ {
+ ...
+ }
+ ]
+ 'tag': {} dict of tags,
+ 'changeset': id of changeset of last change,
+ 'version': version number of Way,
+ 'user': username of user that made the last change,
+ 'uid': id of user that made the last change,
+ 'visible': True|False
+ },
+ {
+ ...
+ },
+]
+
+
+The way_id is a unique identifier for a way.
243 def way_full(self: "OsmApi", way_id: int) -> list[dict[str, Any]]: +244 """ +245 Returns the full data for way `way_id` as list of dicts: +246 +247 #!python +248 [ +249 { +250 'type': node|way|relation, +251 'data': {} data dict for node|way|relation +252 }, +253 { ... } +254 ] +255 +256 The `way_id` is a unique identifier for a way. +257 +258 If the requested element has been deleted, +259 `OsmApi.ElementDeletedApiError` is raised. +260 +261 If the requested element can not be found, +262 `OsmApi.ElementNotFoundApiError` is raised. +263 """ +264 uri = f"/api/0.6/way/{way_id}/full" +265 data = self._session._get(uri) +266 return parser.parse_osm(data) +
Returns the full data for way way_id as list of dicts:
#!python
+[
+ {
+ 'type': node|way|relation,
+ 'data': {} data dict for node|way|relation
+ },
+ { ... }
+]
+
+
+The way_id is a unique identifier for a way.
If the requested element has been deleted,
+OsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,
+OsmApi.ElementNotFoundApiError is raised.
268 def ways_get(self: "OsmApi", way_id_list: list[int]) -> dict[int, dict[str, Any]]: +269 """ +270 Returns dict with the id of the way as a key for +271 each way in `way_id_list`: +272 +273 #!python +274 { +275 '1234': dict of way data, +276 '5678': dict of way data, +277 ... +278 } +279 +280 `way_id_list` is a list containing unique identifiers for multiple ways. +281 """ +282 way_list = ",".join([str(x) for x in way_id_list]) +283 uri = f"/api/0.6/ways?ways={way_list}" +284 data = self._session._get(uri) +285 ways = cast(list[Element], dom.OsmResponseToDom(data, tag="way")) +286 result: dict[int, dict[str, Any]] = {} +287 for way in ways: +288 way_data = dom.dom_parse_way(way) +289 result[way_data["id"]] = way_data +290 return result +
Returns dict with the id of the way as a key for
+each way in way_id_list:
#!python
+{
+ '1234': dict of way data,
+ '5678': dict of way data,
+ ...
+}
+
+
+way_id_list is a list containing unique identifiers for multiple ways.
def _XmlBuild(ElementType, ElementData, WithHeaders=True, data=None): # noqa - xml = "" - if WithHeaders: - xml += '<?xml version="1.0" encoding="UTF-8"?>\n' - xml += '<osm version="0.6" generator="' - xml += data._created_by + '">\n' - - # <element attr="val"> - xml += " <" + ElementType - if "id" in ElementData: - xml += ' id="' + str(ElementData["id"]) + '"' - if "lat" in ElementData: - xml += ' lat="' + str(ElementData["lat"]) + '"' - if "lon" in ElementData: - xml += ' lon="' + str(ElementData["lon"]) + '"' - if "version" in ElementData: - xml += ' version="' + str(ElementData["version"]) + '"' - visible_str = str(ElementData.get("visible", True)).lower() - xml += ' visible="' + visible_str + '"' - if ElementType in ["node", "way", "relation"]: - xml += ' changeset="' + str(data._CurrentChangesetId) + '"' - xml += ">\n" - - # <tag... /> - for k, v in ElementData.get("tag", {}).items(): - xml += ' <tag k="' + _XmlEncode(k) - xml += '" v="' + _XmlEncode(v) + '"/>\n' - - # <member... /> - for member in ElementData.get("member", []): - xml += ' <member type="' + member["type"] - xml += '" ref="' + str(member["ref"]) - xml += '" role="' + _XmlEncode(member["role"]) - xml += '"/>\n' - - # <nd... /> - for ref in ElementData.get("nd", []): - xml += ' <nd ref="' + str(ref) + '"/>\n' - - # </element> - xml += " </" + ElementType + ">\n" - - if WithHeaders: - xml += "</osm>\n" + + + + +- return xml.encode("utf8") - - -def _XmlEncode(text): - return ( - text.replace("&", "&") - .replace('"', """) - .replace("<", "<") - .replace(">", ">") - ) - - -def _GetXmlValue(DomElement, tag): - try: - elem = DomElement.getElementsByTagName(tag)[0] - return elem.firstChild.nodeValue - except Exception: - return None -1from typing import Any, Optional, TYPE_CHECKING + 2from xml.dom.minidom import Element + 3 + 4if TYPE_CHECKING: + 5 from .OsmApi import OsmApi + 6 + 7 + 8def _xml_build( # noqa: C901 + 9 element_type: str, +10 element_data: dict[str, Any], +11 with_headers: bool = True, +12 *, +13 data: "OsmApi", +14) -> bytes: +15 xml = "" +16 if with_headers: +17 xml += '<?xml version="1.0" encoding="UTF-8"?>\n' +18 xml += '<osm version="0.6" generator="' +19 xml += data._created_by + '">' +20 xml += "\n" +21 +22 # <element attr="val"> +23 xml += " <" + element_type +24 if "id" in element_data: +25 xml += ' id="' + str(element_data["id"]) + '"' +26 if "lat" in element_data: +27 xml += ' lat="' + str(element_data["lat"]) + '"' +28 if "lon" in element_data: +29 xml += ' lon="' + str(element_data["lon"]) + '"' +30 if "version" in element_data: +31 xml += ' version="' + str(element_data["version"]) + '"' +32 visible_str = str(element_data.get("visible", True)).lower() +33 xml += ' visible="' + visible_str + '"' +34 if element_type in ["node", "way", "relation"]: +35 xml += ' changeset="' + str(data._current_changeset_id) + '"' +36 xml += ">\n" +37 +38 # <tag... /> +39 for k, v in element_data.get("tag", {}).items(): +40 xml += ' <tag k="' + _xml_encode(k) +41 xml += '" v="' + _xml_encode(v) + '"/>\n' +42 +43 # <member... /> +44 for member in element_data.get("member", []): +45 xml += ' <member type="' + member["type"] +46 xml += '" ref="' + str(member["ref"]) +47 xml += '" role="' + _xml_encode(member["role"]) +48 xml += '"/>\n' +49 +50 # <nd... /> +51 for ref in element_data.get("nd", []): +52 xml += ' <nd ref="' + str(ref) + '"/>\n' +53 +54 # </element> +55 xml += " </" + element_type + ">\n" +56 +57 if with_headers: +58 xml += "</osm>\n" +59 +60 return xml.encode("utf8") +61 +62 +63def _xml_encode(text: str) -> str: +64 return ( +65 text.replace("&", "&") +66 .replace('"', """) +67 .replace("<", "<") +68 .replace(">", ">") +69 ) +70 +71 +72def _get_xml_value(dom_element: Element, tag: str) -> Optional[str]: +73 try: +74 elem = dom_element.getElementsByTagName(tag)[0] +75 return elem.firstChild.nodeValue # type: ignore[union-attr] +76 except Exception: +77 return None +
The OsmApi module is a wrapper for the OpenStreetMap API.\nAs such it provides an easy access to the functionality of the API.
\n\nYou can find this module on PyPI\nor on GitHub.
\n\nFind all information about changes of the different versions of this module\nin the CHANGELOG.
\n\n{\"role\": \"\", \"ref\":123, \"type\": \"node\"}Main class of osmapi, instanciate this class to use osmapi
\n"}, {"fullname": "osmapi.OsmApi.OsmApi.__init__", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.__init__", "type": "function", "doc": "Initialized the OsmApi object.
\n\nThere are two different ways to authenticate a user.\nEither username and password are supplied directly or the path\nto a passwordfile is given, where on the first line username\nand password must be colon-separated (
To credit the application that supplies changes to OSM, an appid\ncan be provided. This is a string identifying the application.\nIf this is omitted \"osmapi\" is used.
It is possible to configure the URL to connect to using the api\nparameter. By default this is the SSL version of the production API\nof OpenStreetMap, for testing purposes, one might prefer the official\ntest instance at \"api06.dev.openstreetmap.org\" or any other valid\nOSM-API. To use an encrypted connection (HTTPS) simply add 'https://'\nin front of the hostname of the api parameter (e.g.\nhttps://api.openstreetmap.com).
There are several options to control the changeset behaviour. By\ndefault, a programmer has to take care to open and close a changeset\nprior to make changes to OSM.\nBy setting changesetauto to True, osmapi automatically opens\nchangesets.\nThe changesetautotags parameter takes a dict, where each key/value\npair is applied as tags to the changeset.\nThe option changesetautosize defines the size of each\nupload (default: 500) and changesetautomulti defines how many\nuploads should be made before closing a changeset and opening a new\none (default: 1).
The session parameter can be used to provide a custom requests\nhttp session object (requests.Session). This might be useful for\nOAuth authentication, custom adapters, hooks etc.
Finally the timeout parameter is used by the http session to\nthrow an expcetion if the the timeout (in seconds) has passed without\nan answer from the server.
Returns the API capabilities as a dict:
\n\n#!python\n{\n 'area': {\n 'maximum': area in square degrees that can be queried,\n },\n 'changesets': {\n 'maximum_elements': number of elements per changeset,\n },\n 'status': {\n 'api': online|readonly|offline,\n 'database': online|readonly|offline,\n 'gpx': online|readonly|offline,\n },\n 'timeout': {\n 'seconds': timeout in seconds for API calls,\n },\n 'tracepoints': {\n 'per_page': maximum number of points in a GPX track,\n },\n 'version': {\n 'maximum': maximum version of API this server supports,\n 'minimum': minimum version of API this server supports,\n },\n 'waynodes': {\n 'maximum': maximum number of nodes that a way may contain,\n },\n}\n\n\nThe capabilities can be used by a client to\ngain insights of the server in use.
\n", "parameters": ["self"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.NodeGet", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.NodeGet", "type": "function", "doc": "Returns node with NodeId as a dict:
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': {},\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n}\n\n\nIf NodeVersion is supplied, this specific version is returned,\notherwise the latest version is returned.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Creates a node based on the supplied NodeData dict:
#!python\n{\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': {},\n}\n\n\nReturns updated NodeData (without timestamp):
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the supplied information contain an existing node,\nOsmApi.OsmTypeAlreadyExistsError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Updates node with the supplied NodeData dict:
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': {},\n 'version': version number of node,\n}\n\n\nReturns updated NodeData (without timestamp):
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Delete node with NodeData:
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'version': version number of node,\n}\n\n\nReturns updated NodeData (without timestamp):
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
If the requested element has already been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with version as key:
\n\n#!python\n{\n '1': dict of NodeData,\n '2': dict of NodeData,\n ...\n}\n\n\nNodeId is the unique identifier of a node.
Returns a list of dicts of WayData containing node NodeId:
#!python\n[\n {\n 'id': id of Way,\n 'nd': [] list of NodeIds in this way\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n },\n {\n ...\n },\n]\n\n\nThe NodeId is a unique identifier for a node.
Returns a list of dicts of RelationData containing node NodeId:
#!python\n[\n {\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {},\n 'changeset': id of changeset of last change,\n 'version': version number of Way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n },\n {\n ...\n },\n]\n\n\nThe NodeId is a unique identifier for a node.
Returns dict with the id of the Node as a key\nfor each node in NodeIdList:
#!python\n{\n '1234': dict of NodeData,\n '5678': dict of NodeData,\n ...\n}\n\n\nNodeIdList is a list containing unique identifiers\nfor multiple nodes.
Returns way with WayId as a dict:
#!python\n{\n 'id': id of way,\n 'tag': {} tags of this way,\n 'nd': [] list of nodes belonging to this way\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n}\n\n\nIf WayVersion is supplied, this specific version is returned,\notherwise the latest version is returned.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Creates a way based on the supplied WayData dict:
#!python\n{\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n}\n\n\nReturns updated WayData (without timestamp):
#!python\n{\n 'id': id of node,\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing node,\nOsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Updates way with the supplied WayData dict:
#!python\n{\n 'id': id of way,\n 'nd': [] list of nodes,\n 'tag': {},\n 'version': version number of way,\n}\n\n\nReturns updated WayData (without timestamp):
#!python\n{\n 'id': id of node,\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Delete way with WayData:
#!python\n{\n 'id': id of way,\n 'nd': [] list of nodes,\n 'tag': dict of tags,\n 'version': version number of way,\n}\n\n\nReturns updated WayData (without timestamp):
#!python\n{\n 'id': id of node,\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
If the requested element has already been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with version as key:
\n\n#!python\n{\n '1': dict of WayData,\n '2': dict of WayData,\n ...\n}\n\n\nWayId is the unique identifier of a way.
Returns a list of dicts of RelationData containing way WayId:
#!python\n[\n {\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n },\n {\n ...\n },\n]\n\n\nThe WayId is a unique identifier for a way.
Returns the full data for way WayId as list of dicts:
#!python\n[\n {\n 'type': node|way|relation,\n 'data': {} data dict for node|way|relation\n },\n { ... }\n]\n\n\nThe WayId is a unique identifier for a way.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with the id of the way as a key for\neach way in WayIdList:
#!python\n{\n '1234': dict of WayData,\n '5678': dict of WayData,\n ...\n}\n\n\nWayIdList is a list containing unique identifiers for multiple ways.
Returns relation with RelationId as a dict:
#!python\n{\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Relation,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n}\n\n\nIf RelationVersion is supplied, this specific version is returned,\notherwise the latest version is returned.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Creates a relation based on the supplied RelationData dict:
#!python\n{\n 'member': [] list of members,\n 'tag': {} dict of tags,\n}\n\n\nReturns updated RelationData (without timestamp):
#!python\n{\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Relation,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing node,\nOsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Updates relation with the supplied RelationData dict:
#!python\n{\n 'id': id of relation,\n 'member': [] list of member dicts,\n 'tag': {},\n 'version': version number of relation,\n}\n\n\nReturns updated RelationData (without timestamp):
#!python\n{\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags\n 'changeset': id of changeset of last change,\n 'version': version number of Relation,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Delete relation with RelationData dict:
#!python\n{\n 'id': id of relation,\n 'member': [] list of member dicts,\n 'tag': {},\n 'version': version number of relation,\n}\n\n\nReturns updated RelationData (without timestamp):
#!python\n{\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Relation,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
If the requested element has already been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with version as key:
\n\n#!python\n{\n '1': dict of RelationData,\n '2': dict of RelationData,\n ...\n}\n\n\nRelationId is the unique identifier of a relation.
Returns a list of dicts of RelationData\ncontaining relation RelationId:
#!python\n[\n {\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n },\n {\n ...\n },\n]\n\n\nThe RelationId is a unique identifier for a relation.
Returns the full data (all levels) for relation\nRelationId as list of dicts:
#!python\n[\n {\n 'type': node|way|relation,\n 'data': {} data dict for node|way|relation\n },\n { ... }\n]\n\n\nThe RelationId is a unique identifier for a way.
This function is useful for relations containing other relations.
\n\nIf you don't need all levels, use OsmApi.RelationFull\ninstead, which return only 2 levels.
If any relation (on any level) has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns the full data (two levels) for relation\nRelationId as list of dicts:
#!python\n[\n {\n 'type': node|way|relation,\n 'data': {} data dict for node|way|relation\n },\n { ... }\n]\n\n\nThe RelationId is a unique identifier for a way.
If you need all levels, use OsmApi.RelationFullRecur.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with the id of the relation as a key\nfor each relation in RelationIdList:
#!python\n{\n '1234': dict of RelationData,\n '5678': dict of RelationData,\n ...\n}\n\n\nRelationIdList is a list containing unique identifiers\nfor multiple relations.
Context manager for a Changeset.
\n\nIt opens a Changeset, uploads the changes and closes the changeset\nwhen used with the with statement:
#!python\nimport osmapi\n\nwith osmapi.Changeset({\"comment\": \"Import script XYZ\"}) as changeset_id:\n print(f\"Part of changeset {changeset_id}\")\n api.NodeCreate({\"lon\":1, \"lat\":1, \"tag\": {}})\n\n\nIf ChangesetTags are given, this tags are applied (key/value).
Returns ChangesetId
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
Returns changeset with ChangesetId as a dict:
#!python\n{\n 'id': id of Changeset,\n 'open': True|False, wheter or not this changeset is open\n 'tag': {} dict of tags,\n 'created_at': timestamp of creation of this changeset\n 'closed_at': timestamp when changeset was closed\n 'comments_count': amount of comments\n 'discussion': [] list of comment dict (-> `include_discussion`)\n 'max_lon': maximum longitude of changes in this changeset\n 'max_lat': maximum latitude of changes in this changeset\n 'min_lon': minimum longitude of changes in this changeset\n 'min_lat': minimum longitude of changes in this changeset\n 'user': username of user that created this changeset,\n 'uid': id of user that created this changeset,\n}\n\n\nChangesetId is the unique identifier of a changeset.
If include_discussion is set to True the changeset discussion\nwill be available in the result.
Updates current changeset with ChangesetTags.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Opens a changeset.
\n\nIf ChangesetTags are given, this tags are applied (key/value).
Returns ChangesetId
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
Closes current changeset.
\n\nReturns ChangesetId.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Upload data with the ChangesData list of dicts:
#!python\n{\n type: node|way|relation,\n action: create|delete|modify,\n data: {}\n}\n\n\nReturns list with updated ids.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Download data from changeset ChangesetId.
Returns list of dict:
\n\n#!python\n{\n 'type': node|way|relation,\n 'action': create|delete|modify,\n 'data': {}\n}\n\n", "parameters": ["self", "ChangesetId"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.ChangesetsGet", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.ChangesetsGet", "type": "function", "doc": "Returns a dict with the id of the changeset as key\nmatching all criteria:
\n\n#!python\n{\n '1234': dict of ChangesetData,\n '5678': dict of ChangesetData,\n ...\n}\n\n\nAll parameters are optional.
\n", "parameters": ["self", "min_lon", "min_lat", "max_lon", "max_lat", "userid", "username", "closed_after", "created_before", "only_open", "only_closed"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.ChangesetComment", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.ChangesetComment", "type": "function", "doc": "Adds a comment to the changeset ChangesetId
comment should be a string.
Returns the updated ChangesetData dict:
#!python\n{\n 'id': id of Changeset,\n 'open': True|False, wheter or not this changeset is open\n 'tag': {} dict of tags,\n 'created_at': timestamp of creation of this changeset\n 'closed_at': timestamp when changeset was closed\n 'comments_count': amount of comments\n 'max_lon': maximum longitude of changes in this changeset\n 'max_lat': maximum latitude of changes in this changeset\n 'min_lon': minimum longitude of changes in this changeset\n 'min_lat': minimum longitude of changes in this changeset\n 'user': username of user that created this changeset,\n 'uid': id of user that created this changeset,\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Subcribe to the changeset discussion of changeset ChangesetId.
The user will be informed about new comments (i.e. receive an email).
\n\nReturns the updated ChangesetData dict:
#!python\n{\n 'id': id of Changeset,\n 'open': True|False, wheter or not this changeset is open\n 'tag': {} dict of tags,\n 'created_at': timestamp of creation of this changeset\n 'closed_at': timestamp when changeset was closed\n 'comments_count': amount of comments\n 'max_lon': maximum longitude of changes in this changeset\n 'max_lat': maximum latitude of changes in this changeset\n 'min_lon': minimum longitude of changes in this changeset\n 'min_lat': minimum longitude of changes in this changeset\n 'user': username of user that created this changeset,\n 'uid': id of user that created this changeset,\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
Subcribe to the changeset discussion of changeset ChangesetId.
The user will be informed about new comments (i.e. receive an email).
\n\nReturns the updated ChangesetData dict:
#!python\n{\n 'id': id of Changeset,\n 'open': True|False, wheter or not this changeset is open\n 'tag': {} dict of tags,\n 'created_at': timestamp of creation of this changeset\n 'closed_at': timestamp when changeset was closed\n 'comments_count': amount of comments\n 'max_lon': maximum longitude of changes in this changeset\n 'max_lat': maximum latitude of changes in this changeset\n 'min_lon': minimum longitude of changes in this changeset\n 'min_lat': minimum longitude of changes in this changeset\n 'user': username of user that created this changeset,\n 'uid': id of user that created this changeset,\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
Returns a list of dicts of notes in the specified bounding box:
\n\n#!python\n[\n {\n 'id': integer,\n 'action': opened|commented|closed,\n 'status': open|closed\n 'date_created': creation date\n 'date_closed': closing data|None\n 'uid': User ID|None\n 'user': User name|None\n 'comments': {}\n },\n { ... }\n]\n\n\nThe limit parameter defines how many results should be returned.
\n\nclosed specifies the number of days a bug needs to be closed\nto no longer be returned.\nThe value 0 means only open bugs are returned,\n-1 means all bugs are returned.
\n\nAll parameters are optional.
\n", "parameters": ["self", "min_lon", "min_lat", "max_lon", "max_lat", "limit", "closed"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.NoteGet", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.NoteGet", "type": "function", "doc": "Returns a note as dict:
\n\n#!python\n{\n 'id': integer,\n 'action': opened|commented|closed,\n 'status': open|closed\n 'date_created': creation date\n 'date_closed': closing data|None\n 'uid': User ID|None\n 'user': User name|None\n 'comments': {}\n}\n\n\nid is the unique identifier of the note.
Creates a note based on the supplied NoteData dict:
#!python\n{\n 'lat': latitude of note,\n 'lon': longitude of note,\n 'text': text of the note,\n}\n\n\nReturns updated NoteData:
#!python\n{\n 'id': id of note,\n 'lat': latitude of note,\n 'lon': longitude of note,\n 'date_created': date when the note was created\n 'date_closed': date when the note was closed or None if it's open,\n 'status': status of the note (open or closed),\n 'comments': [\n {\n 'date': date of the comment,\n 'action': status of comment (opened, commented, closed),\n 'text': text of the note,\n 'html': html version of the text of the note,\n 'uid': user id of the user creating this note or None\n 'user': username of the user creating this note or None\n }\n ]\n}\n\n", "parameters": ["self", "NoteData"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.NoteComment", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.NoteComment", "type": "function", "doc": "Adds a new comment to a note.
\n\nReturns the updated note.
\n", "parameters": ["self", "NoteId", "comment"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.NoteClose", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.NoteClose", "type": "function", "doc": "Closes a note.
\n\nReturns the updated note.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
Reopens a note.
\n\nReturns the updated note.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns a list of dicts of notes that match the given search query.
\n\nThe limit parameter defines how many results should be returned.
\n\nclosed specifies the number of days a bug needs to be closed\nto no longer be returned.\nThe value 0 means only open bugs are returned,\n-1 means all bugs are returned.
\n", "parameters": ["self", "query", "limit", "closed"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.Map", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.Map", "type": "function", "doc": "Download data in bounding box.
\n\nReturns list of dict:
\n\n#!python\n{\n type: node|way|relation,\n data: {}\n}\n\n", "parameters": ["self", "min_lon", "min_lat", "max_lon", "max_lat"], "funcdef": "def"}, {"fullname": "osmapi.OsmApi.OsmApi.flush", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.flush", "type": "function", "doc": "Force the changes to be uploaded to OSM and the changeset to be closed
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
Returns the (sub-) DOM parsed from an OSM response
\n", "parameters": ["response", "tag", "single", "allow_empty"], "funcdef": "def"}, {"fullname": "osmapi.dom.DomParseNode", "modulename": "osmapi.dom", "qualname": "DomParseNode", "type": "function", "doc": "Returns NodeData for the node.
\n", "parameters": ["DomElement"], "funcdef": "def"}, {"fullname": "osmapi.dom.DomParseWay", "modulename": "osmapi.dom", "qualname": "DomParseWay", "type": "function", "doc": "Returns WayData for the way.
\n", "parameters": ["DomElement"], "funcdef": "def"}, {"fullname": "osmapi.dom.DomParseRelation", "modulename": "osmapi.dom", "qualname": "DomParseRelation", "type": "function", "doc": "Returns RelationData for the relation.
\n", "parameters": ["DomElement"], "funcdef": "def"}, {"fullname": "osmapi.dom.DomParseChangeset", "modulename": "osmapi.dom", "qualname": "DomParseChangeset", "type": "function", "doc": "Returns ChangesetData for the changeset.
\n", "parameters": ["DomElement", "include_discussion"], "funcdef": "def"}, {"fullname": "osmapi.dom.DomParseNote", "modulename": "osmapi.dom", "qualname": "DomParseNote", "type": "function", "doc": "Returns NoteData for the note.
\n", "parameters": ["DomElement"], "funcdef": "def"}, {"fullname": "osmapi.errors", "modulename": "osmapi.errors", "qualname": "", "type": "module", "doc": "\n"}, {"fullname": "osmapi.errors.OsmApiError", "modulename": "osmapi.errors", "qualname": "OsmApiError", "type": "class", "doc": "General OsmApi error class to provide a superclass for all other errors
\n"}, {"fullname": "osmapi.errors.MaximumRetryLimitReachedError", "modulename": "osmapi.errors", "qualname": "MaximumRetryLimitReachedError", "type": "class", "doc": "Error when the maximum amount of retries is reached and we have to give up
\n"}, {"fullname": "osmapi.errors.UsernamePasswordMissingError", "modulename": "osmapi.errors", "qualname": "UsernamePasswordMissingError", "type": "class", "doc": "Error when username or password is missing for an authenticated request
\n"}, {"fullname": "osmapi.errors.NoChangesetOpenError", "modulename": "osmapi.errors", "qualname": "NoChangesetOpenError", "type": "class", "doc": "Error when an operation requires an open changeset, but currently\nno changeset _is_ open
\n"}, {"fullname": "osmapi.errors.ChangesetAlreadyOpenError", "modulename": "osmapi.errors", "qualname": "ChangesetAlreadyOpenError", "type": "class", "doc": "Error when a user tries to open a changeset when there is already\nan open changeset
\n"}, {"fullname": "osmapi.errors.OsmTypeAlreadyExistsError", "modulename": "osmapi.errors", "qualname": "OsmTypeAlreadyExistsError", "type": "class", "doc": "Error when a user tries to create an object that already exsits
\n"}, {"fullname": "osmapi.errors.XmlResponseInvalidError", "modulename": "osmapi.errors", "qualname": "XmlResponseInvalidError", "type": "class", "doc": "Error if the XML response from the OpenStreetMap API is invalid
\n"}, {"fullname": "osmapi.errors.ApiError", "modulename": "osmapi.errors", "qualname": "ApiError", "type": "class", "doc": "Error class, is thrown when an API request fails
\n"}, {"fullname": "osmapi.errors.ApiError.__init__", "modulename": "osmapi.errors", "qualname": "ApiError.__init__", "type": "function", "doc": "\n", "parameters": ["self", "status", "reason", "payload"], "funcdef": "def"}, {"fullname": "osmapi.errors.ApiError.status", "modulename": "osmapi.errors", "qualname": "ApiError.status", "type": "variable", "doc": "HTTP error code
\n"}, {"fullname": "osmapi.errors.ApiError.reason", "modulename": "osmapi.errors", "qualname": "ApiError.reason", "type": "variable", "doc": "Error message
\n"}, {"fullname": "osmapi.errors.ApiError.payload", "modulename": "osmapi.errors", "qualname": "ApiError.payload", "type": "variable", "doc": "Payload of API when this error occured
\n"}, {"fullname": "osmapi.errors.UnauthorizedApiError", "modulename": "osmapi.errors", "qualname": "UnauthorizedApiError", "type": "class", "doc": "Error when the API returned an Unauthorized error,\ne.g. when the provided OAuth token is expired
\n"}, {"fullname": "osmapi.errors.AlreadySubscribedApiError", "modulename": "osmapi.errors", "qualname": "AlreadySubscribedApiError", "type": "class", "doc": "Error when a user tries to subscribe to a changeset\nthat she is already subscribed to
\n"}, {"fullname": "osmapi.errors.NotSubscribedApiError", "modulename": "osmapi.errors", "qualname": "NotSubscribedApiError", "type": "class", "doc": "Error when user tries to unsubscribe from a changeset\nthat he is not subscribed to
\n"}, {"fullname": "osmapi.errors.ElementDeletedApiError", "modulename": "osmapi.errors", "qualname": "ElementDeletedApiError", "type": "class", "doc": "Error when the requested element is deleted
\n"}, {"fullname": "osmapi.errors.ElementNotFoundApiError", "modulename": "osmapi.errors", "qualname": "ElementNotFoundApiError", "type": "class", "doc": "Error if the the requested element was not found
\n"}, {"fullname": "osmapi.errors.ResponseEmptyApiError", "modulename": "osmapi.errors", "qualname": "ResponseEmptyApiError", "type": "class", "doc": "Error when the response to the request is empty
\n"}, {"fullname": "osmapi.errors.ChangesetClosedApiError", "modulename": "osmapi.errors", "qualname": "ChangesetClosedApiError", "type": "class", "doc": "Error if the the changeset in question has already been closed
\n"}, {"fullname": "osmapi.errors.NoteAlreadyClosedApiError", "modulename": "osmapi.errors", "qualname": "NoteAlreadyClosedApiError", "type": "class", "doc": "Error if the the note in question has already been closed
\n"}, {"fullname": "osmapi.errors.VersionMismatchApiError", "modulename": "osmapi.errors", "qualname": "VersionMismatchApiError", "type": "class", "doc": "Error if the provided version does not match the database version\nof the element
\n"}, {"fullname": "osmapi.errors.PreconditionFailedApiError", "modulename": "osmapi.errors", "qualname": "PreconditionFailedApiError", "type": "class", "doc": "Error if the precondition of the operation was not met:
\n\nError if the http request ran into a timeout
\n"}, {"fullname": "osmapi.errors.ConnectionApiError", "modulename": "osmapi.errors", "qualname": "ConnectionApiError", "type": "class", "doc": "Error if there was a network error (e.g. DNS failure, refused connection)\nwhile connecting to the remote server.
\n"}, {"fullname": "osmapi.http", "modulename": "osmapi.http", "qualname": "", "type": "module", "doc": "\n"}, {"fullname": "osmapi.http.OsmApiSession", "modulename": "osmapi.http", "qualname": "OsmApiSession", "type": "class", "doc": "\n"}, {"fullname": "osmapi.http.OsmApiSession.__init__", "modulename": "osmapi.http", "qualname": "OsmApiSession.__init__", "type": "function", "doc": "\n", "parameters": ["self", "base_url", "created_by", "auth", "session", "timeout"], "funcdef": "def"}, {"fullname": "osmapi.http.OsmApiSession.MAX_RETRY_LIMIT", "modulename": "osmapi.http", "qualname": "OsmApiSession.MAX_RETRY_LIMIT", "type": "variable", "doc": "Maximum retries if a call to the remote API fails (default: 5)
\n"}, {"fullname": "osmapi.http.OsmApiSession.close", "modulename": "osmapi.http", "qualname": "OsmApiSession.close", "type": "function", "doc": "\n", "parameters": ["self"], "funcdef": "def"}, {"fullname": "osmapi.parser", "modulename": "osmapi.parser", "qualname": "", "type": "module", "doc": "\n"}, {"fullname": "osmapi.parser.ParseOsm", "modulename": "osmapi.parser", "qualname": "ParseOsm", "type": "function", "doc": "Parse osm data.
\n\nReturns list of dict:
\n\n#!python\n{\n type: node|way|relation,\n data: {}\n}\n\n", "parameters": ["data"], "funcdef": "def"}, {"fullname": "osmapi.parser.ParseOsc", "modulename": "osmapi.parser", "qualname": "ParseOsc", "type": "function", "doc": "Parse osc data.
\n\nReturns list of dict:
\n\n#!python\n{\n type: node|way|relation,\n action: create|delete|modify,\n data: {}\n}\n\n", "parameters": ["data"], "funcdef": "def"}, {"fullname": "osmapi.parser.ParseNotes", "modulename": "osmapi.parser", "qualname": "ParseNotes", "type": "function", "doc": "Parse notes data.
\n\nReturns a list of dict:
\n\n#!python\n[\n {\n 'id': integer,\n 'action': opened|commented|closed,\n 'status': open|closed\n 'date_created': creation date\n 'date_closed': closing data|None\n 'uid': User ID|None\n 'user': User name|None\n 'comments': {}\n },\n { ... }\n]\n\n", "parameters": ["data"], "funcdef": "def"}, {"fullname": "osmapi.xmlbuilder", "modulename": "osmapi.xmlbuilder", "qualname": "", "type": "module", "doc": "\n"}];
+ /** pdoc search index */const docs = {"version": "0.9.5", "fields": ["qualname", "fullname", "annotation", "default_value", "signature", "bases", "doc"], "ref": "fullname", "documentStore": {"docs": {"osmapi": {"fullname": "osmapi", "modulename": "osmapi", "kind": "module", "doc": "\n"}, "osmapi.OsmApi": {"fullname": "osmapi.OsmApi", "modulename": "osmapi.OsmApi", "kind": "module", "doc": "The OsmApi module is a wrapper for the OpenStreetMap API.\nAs such it provides an easy access to the functionality of the API.
\n\nYou can find this module on PyPI\nor on GitHub.
\n\nFind all information about changes of the different versions of this module\nin the CHANGELOG.
\n\n{\"role\": \"\", \"ref\":123, \"type\": \"node\"}Main class of osmapi, instanciate this class to use osmapi
\n", "bases": "osmapi.node.NodeMixin, osmapi.way.WayMixin, osmapi.relation.RelationMixin, osmapi.changeset.ChangesetMixin, osmapi.note.NoteMixin, osmapi.capabilities.CapabilitiesMixin"}, "osmapi.OsmApi.OsmApi.__init__": {"fullname": "osmapi.OsmApi.OsmApi.__init__", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.__init__", "kind": "function", "doc": "Initialized the OsmApi object.
\n\nThere are two different ways to authenticate a user.\nEither username and password are supplied directly or the path\nto a passwordfile is given, where on the first line username\nand password must be colon-separated (
To credit the application that supplies changes to OSM, an appid\ncan be provided. This is a string identifying the application.\nIf this is omitted \"osmapi\" is used.
It is possible to configure the URL to connect to using the api\nparameter. By default this is the SSL version of the production API\nof OpenStreetMap, for testing purposes, one might prefer the official\ntest instance at \"api06.dev.openstreetmap.org\" or any other valid\nOSM-API. To use an encrypted connection (HTTPS) simply add 'https://'\nin front of the hostname of the api parameter (e.g.\nhttps://api.openstreetmap.com).
The session parameter can be used to provide a custom requests\nhttp session object (requests.Session). This might be useful for\nOAuth authentication, custom adapters, hooks etc.
Finally the timeout parameter is used by the http session to\nthrow an expcetion if the the timeout (in seconds) has passed without\nan answer from the server.
Returns the API capabilities as a dict.
\n\nDeprecated since version :\nUse capabilities() instead.
The capabilities can be used by a client to\ngain insights of the server in use.
\n", "signature": "(self) -> dict[str, dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.OsmApi.OsmApi.NodeGet": {"fullname": "osmapi.OsmApi.OsmApi.NodeGet", "modulename": "osmapi.OsmApi", "qualname": "OsmApi.NodeGet", "kind": "function", "doc": "Deprecated since version Use node_get() instead..
Deprecated since version Use node_create() instead..
Deprecated since version Use node_update() instead..
Deprecated since version Use node_delete() instead..
Deprecated since version Use node_history() instead..
Deprecated since version Use node_ways() instead..
Deprecated since version Use node_relations() instead..
Deprecated since version Use nodes_get() instead..
Deprecated since version Use way_get() instead..
Deprecated since version Use way_create() instead..
Deprecated since version Use way_update() instead..
Deprecated since version Use way_delete() instead..
Deprecated since version Use way_history() instead..
Deprecated since version Use way_relations() instead..
Deprecated since version Use way_full() instead..
Deprecated since version Use ways_get() instead..
Deprecated since version Use relation_get() instead..
Deprecated since version Use relation_create() instead..
Deprecated since version Use relation_update() instead..
Deprecated since version Use relation_delete() instead..
Deprecated since version Use relation_history() instead..
Deprecated since version Use relation_relations() instead..
Deprecated since version Use relation_full_recur() instead..
Deprecated since version Use relation_full() instead..
Deprecated since version Use relations_get() instead..
Deprecated since version Use changeset() instead..
Deprecated since version Use changeset_get() instead..
Deprecated since version Use changeset_update() instead..
Deprecated since version Use changeset_create() instead..
Deprecated since version Use changeset_close() instead..
Deprecated since version Use changeset_upload() instead..
Deprecated since version Use changeset_download() instead..
Deprecated since version Use changesets_get() instead..
Deprecated since version Use changeset_comment() instead..
Deprecated since version Use changeset_subscribe() instead..
Deprecated since version Use changeset_unsubscribe() instead..
Deprecated since version Use notes_get() instead..
Deprecated since version Use note_get() instead..
Deprecated since version Use note_create() instead..
Deprecated since version Use note_comment() instead..
Deprecated since version Use note_close() instead..
Deprecated since version Use note_reopen() instead..
Deprecated since version Use notes_search() instead..
Deprecated since version Use map() instead..
Capabilities and miscellaneous operations for the OpenStreetMap API.
\n"}, "osmapi.capabilities.CapabilitiesMixin": {"fullname": "osmapi.capabilities.CapabilitiesMixin", "modulename": "osmapi.capabilities", "qualname": "CapabilitiesMixin", "kind": "class", "doc": "Mixin providing capabilities and misc operations with pythonic method names.
\n"}, "osmapi.capabilities.CapabilitiesMixin.capabilities": {"fullname": "osmapi.capabilities.CapabilitiesMixin.capabilities", "modulename": "osmapi.capabilities", "qualname": "CapabilitiesMixin.capabilities", "kind": "function", "doc": "Returns the API capabilities as a dict.
\n\nThe capabilities can be used by a client to\ngain insights of the server in use.
\n", "signature": "(self: osmapi.OsmApi.OsmApi) -> dict[str, dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.capabilities.CapabilitiesMixin.map": {"fullname": "osmapi.capabilities.CapabilitiesMixin.map", "modulename": "osmapi.capabilities", "qualname": "CapabilitiesMixin.map", "kind": "function", "doc": "Download data in bounding box.
\n\nReturns list of dict with type and data.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tmin_lon: float,\tmin_lat: float,\tmax_lon: float,\tmax_lat: float) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.changeset": {"fullname": "osmapi.changeset", "modulename": "osmapi.changeset", "kind": "module", "doc": "Changeset operations for the OpenStreetMap API.
\n"}, "osmapi.changeset.ChangesetMixin": {"fullname": "osmapi.changeset.ChangesetMixin", "modulename": "osmapi.changeset", "qualname": "ChangesetMixin", "kind": "class", "doc": "Mixin providing changeset-related operations with pythonic method names.
\n"}, "osmapi.changeset.ChangesetMixin.changeset": {"fullname": "osmapi.changeset.ChangesetMixin.changeset", "modulename": "osmapi.changeset", "qualname": "ChangesetMixin.changeset", "kind": "function", "doc": "Context manager for a Changeset.
\n\nIt opens a Changeset, uploads the changes and closes the changeset\nwhen used with the with statement:
#!python\nimport osmapi\n\nwith api.changeset({\"comment\": \"Import script XYZ\"}) as changeset_id:\n print(f\"Part of changeset {changeset_id}\")\n api.node_create({\"lon\":1, \"lat\":1, \"tag\": {}})\n\n\nIf changeset_tags are given, this tags are applied (key/value).
Returns changeset_id
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
Returns changeset with changeset_id as a dict.
changeset_id is the unique identifier of a changeset.
If include_discussion is set to True the changeset discussion\nwill be available in the result.
Updates current changeset with changeset_tags.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Opens a changeset.
\n\nIf changeset_tags are given, this tags are applied (key/value).
Returns changeset_id
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
Closes current changeset.
\n\nReturns changeset_id.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Upload data with the changes_data list of dicts.
Returns list with updated ids.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Download data from changeset changeset_id.
Returns list of dict with type, action, and data.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tchangeset_id: int) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.changeset.ChangesetMixin.changesets_get": {"fullname": "osmapi.changeset.ChangesetMixin.changesets_get", "modulename": "osmapi.changeset", "qualname": "ChangesetMixin.changesets_get", "kind": "function", "doc": "Returns a dict with the id of the changeset as key matching all criteria.
\n\nAll parameters are optional.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tmin_lon: Optional[float] = None,\tmin_lat: Optional[float] = None,\tmax_lon: Optional[float] = None,\tmax_lat: Optional[float] = None,\tuserid: Optional[int] = None,\tusername: Optional[str] = None,\tclosed_after: Optional[str] = None,\tcreated_before: Optional[str] = None,\tonly_open: bool = False,\tonly_closed: bool = False) -> dict[int, dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.changeset.ChangesetMixin.changeset_comment": {"fullname": "osmapi.changeset.ChangesetMixin.changeset_comment", "modulename": "osmapi.changeset", "qualname": "ChangesetMixin.changeset_comment", "kind": "function", "doc": "Adds a comment to the changeset changeset_id.
comment should be a string.
Returns the updated changeset data dict.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Subscribe to the changeset changeset_id.
Returns the updated changeset data dict.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If already subscribed to this changeset,\nOsmApi.AlreadySubscribedApiError is raised.
Unsubscribe from the changeset changeset_id.
Returns the updated changeset data dict.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If not subscribed to this changeset,\nOsmApi.NotSubscribedApiError is raised.
DOM parsing for the OpenStreetMap API.
\n"}, "osmapi.dom.logger": {"fullname": "osmapi.dom.logger", "modulename": "osmapi.dom", "qualname": "logger", "kind": "variable", "doc": "\n", "default_value": "<Logger osmapi.dom (WARNING)>"}, "osmapi.dom.OsmResponseToDom": {"fullname": "osmapi.dom.OsmResponseToDom", "modulename": "osmapi.dom", "qualname": "OsmResponseToDom", "kind": "function", "doc": "Returns the (sub-) DOM parsed from an OSM response
\n", "signature": "(\tresponse: bytes,\ttag: str,\tsingle: bool = False,\tallow_empty: bool = False) -> Union[xml.dom.minidom.Element, list[xml.dom.minidom.Element]]:", "funcdef": "def"}, "osmapi.dom.dom_parse_node": {"fullname": "osmapi.dom.dom_parse_node", "modulename": "osmapi.dom", "qualname": "dom_parse_node", "kind": "function", "doc": "Returns NodeData for the node.
\n", "signature": "(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.dom.dom_parse_way": {"fullname": "osmapi.dom.dom_parse_way", "modulename": "osmapi.dom", "qualname": "dom_parse_way", "kind": "function", "doc": "Returns WayData for the way.
\n", "signature": "(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.dom.dom_parse_relation": {"fullname": "osmapi.dom.dom_parse_relation", "modulename": "osmapi.dom", "qualname": "dom_parse_relation", "kind": "function", "doc": "Returns RelationData for the relation.
\n", "signature": "(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.dom.dom_parse_changeset": {"fullname": "osmapi.dom.dom_parse_changeset", "modulename": "osmapi.dom", "qualname": "dom_parse_changeset", "kind": "function", "doc": "Returns ChangesetData for the changeset.
\n", "signature": "(\tdom_element: xml.dom.minidom.Element,\tinclude_discussion: bool = False) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.dom.dom_parse_note": {"fullname": "osmapi.dom.dom_parse_note", "modulename": "osmapi.dom", "qualname": "dom_parse_note", "kind": "function", "doc": "Returns NoteData for the note.
\n", "signature": "(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.errors": {"fullname": "osmapi.errors", "modulename": "osmapi.errors", "kind": "module", "doc": "Error classes for the OpenStreetMap API.
\n"}, "osmapi.errors.OsmApiError": {"fullname": "osmapi.errors.OsmApiError", "modulename": "osmapi.errors", "qualname": "OsmApiError", "kind": "class", "doc": "General OsmApi error class to provide a superclass for all other errors
\n", "bases": "builtins.Exception"}, "osmapi.errors.MaximumRetryLimitReachedError": {"fullname": "osmapi.errors.MaximumRetryLimitReachedError", "modulename": "osmapi.errors", "qualname": "MaximumRetryLimitReachedError", "kind": "class", "doc": "Error when the maximum amount of retries is reached and we have to give up
\n", "bases": "OsmApiError"}, "osmapi.errors.UsernamePasswordMissingError": {"fullname": "osmapi.errors.UsernamePasswordMissingError", "modulename": "osmapi.errors", "qualname": "UsernamePasswordMissingError", "kind": "class", "doc": "Error when username or password is missing for an authenticated request
\n", "bases": "OsmApiError"}, "osmapi.errors.NoChangesetOpenError": {"fullname": "osmapi.errors.NoChangesetOpenError", "modulename": "osmapi.errors", "qualname": "NoChangesetOpenError", "kind": "class", "doc": "Error when an operation requires an open changeset, but currently\nno changeset _is_ open
\n", "bases": "OsmApiError"}, "osmapi.errors.ChangesetAlreadyOpenError": {"fullname": "osmapi.errors.ChangesetAlreadyOpenError", "modulename": "osmapi.errors", "qualname": "ChangesetAlreadyOpenError", "kind": "class", "doc": "Error when a user tries to open a changeset when there is already\nan open changeset
\n", "bases": "OsmApiError"}, "osmapi.errors.OsmTypeAlreadyExistsError": {"fullname": "osmapi.errors.OsmTypeAlreadyExistsError", "modulename": "osmapi.errors", "qualname": "OsmTypeAlreadyExistsError", "kind": "class", "doc": "Error when a user tries to create an object that already exsits
\n", "bases": "OsmApiError"}, "osmapi.errors.XmlResponseInvalidError": {"fullname": "osmapi.errors.XmlResponseInvalidError", "modulename": "osmapi.errors", "qualname": "XmlResponseInvalidError", "kind": "class", "doc": "Error if the XML response from the OpenStreetMap API is invalid
\n", "bases": "OsmApiError"}, "osmapi.errors.ApiError": {"fullname": "osmapi.errors.ApiError", "modulename": "osmapi.errors", "qualname": "ApiError", "kind": "class", "doc": "Error class, is thrown when an API request fails
\n", "bases": "OsmApiError"}, "osmapi.errors.ApiError.__init__": {"fullname": "osmapi.errors.ApiError.__init__", "modulename": "osmapi.errors", "qualname": "ApiError.__init__", "kind": "function", "doc": "\n", "signature": "(status: int, reason: str, payload: Any)"}, "osmapi.errors.ApiError.status": {"fullname": "osmapi.errors.ApiError.status", "modulename": "osmapi.errors", "qualname": "ApiError.status", "kind": "variable", "doc": "HTTP error code
\n"}, "osmapi.errors.ApiError.reason": {"fullname": "osmapi.errors.ApiError.reason", "modulename": "osmapi.errors", "qualname": "ApiError.reason", "kind": "variable", "doc": "Error message
\n"}, "osmapi.errors.ApiError.payload": {"fullname": "osmapi.errors.ApiError.payload", "modulename": "osmapi.errors", "qualname": "ApiError.payload", "kind": "variable", "doc": "Payload of API when this error occured
\n"}, "osmapi.errors.UnauthorizedApiError": {"fullname": "osmapi.errors.UnauthorizedApiError", "modulename": "osmapi.errors", "qualname": "UnauthorizedApiError", "kind": "class", "doc": "Error when the API returned an Unauthorized error,\ne.g. when the provided OAuth token is expired
\n", "bases": "ApiError"}, "osmapi.errors.AlreadySubscribedApiError": {"fullname": "osmapi.errors.AlreadySubscribedApiError", "modulename": "osmapi.errors", "qualname": "AlreadySubscribedApiError", "kind": "class", "doc": "Error when a user tries to subscribe to a changeset\nthat she is already subscribed to
\n", "bases": "ApiError"}, "osmapi.errors.NotSubscribedApiError": {"fullname": "osmapi.errors.NotSubscribedApiError", "modulename": "osmapi.errors", "qualname": "NotSubscribedApiError", "kind": "class", "doc": "Error when user tries to unsubscribe from a changeset\nthat he is not subscribed to
\n", "bases": "ApiError"}, "osmapi.errors.ElementDeletedApiError": {"fullname": "osmapi.errors.ElementDeletedApiError", "modulename": "osmapi.errors", "qualname": "ElementDeletedApiError", "kind": "class", "doc": "Error when the requested element is deleted
\n", "bases": "ApiError"}, "osmapi.errors.ElementNotFoundApiError": {"fullname": "osmapi.errors.ElementNotFoundApiError", "modulename": "osmapi.errors", "qualname": "ElementNotFoundApiError", "kind": "class", "doc": "Error if the the requested element was not found
\n", "bases": "ApiError"}, "osmapi.errors.ResponseEmptyApiError": {"fullname": "osmapi.errors.ResponseEmptyApiError", "modulename": "osmapi.errors", "qualname": "ResponseEmptyApiError", "kind": "class", "doc": "Error when the response to the request is empty
\n", "bases": "ApiError"}, "osmapi.errors.ChangesetClosedApiError": {"fullname": "osmapi.errors.ChangesetClosedApiError", "modulename": "osmapi.errors", "qualname": "ChangesetClosedApiError", "kind": "class", "doc": "Error if the the changeset in question has already been closed
\n", "bases": "ApiError"}, "osmapi.errors.NoteAlreadyClosedApiError": {"fullname": "osmapi.errors.NoteAlreadyClosedApiError", "modulename": "osmapi.errors", "qualname": "NoteAlreadyClosedApiError", "kind": "class", "doc": "Error if the the note in question has already been closed
\n", "bases": "ApiError"}, "osmapi.errors.VersionMismatchApiError": {"fullname": "osmapi.errors.VersionMismatchApiError", "modulename": "osmapi.errors", "qualname": "VersionMismatchApiError", "kind": "class", "doc": "Error if the provided version does not match the database version\nof the element
\n", "bases": "ApiError"}, "osmapi.errors.PreconditionFailedApiError": {"fullname": "osmapi.errors.PreconditionFailedApiError", "modulename": "osmapi.errors", "qualname": "PreconditionFailedApiError", "kind": "class", "doc": "Error if the precondition of the operation was not met:
\n\nError if the http request ran into a timeout
\n", "bases": "ApiError"}, "osmapi.errors.ConnectionApiError": {"fullname": "osmapi.errors.ConnectionApiError", "modulename": "osmapi.errors", "qualname": "ConnectionApiError", "kind": "class", "doc": "Error if there was a network error (e.g. DNS failure, refused connection)\nwhile connecting to the remote server.
\n", "bases": "ApiError"}, "osmapi.http": {"fullname": "osmapi.http", "modulename": "osmapi.http", "kind": "module", "doc": "HTTP session management for the OpenStreetMap API.
\n"}, "osmapi.http.logger": {"fullname": "osmapi.http.logger", "modulename": "osmapi.http", "qualname": "logger", "kind": "variable", "doc": "\n", "default_value": "<Logger osmapi.http (WARNING)>"}, "osmapi.http.OsmApiSession": {"fullname": "osmapi.http.OsmApiSession", "modulename": "osmapi.http", "qualname": "OsmApiSession", "kind": "class", "doc": "\n"}, "osmapi.http.OsmApiSession.__init__": {"fullname": "osmapi.http.OsmApiSession.__init__", "modulename": "osmapi.http", "qualname": "OsmApiSession.__init__", "kind": "function", "doc": "\n", "signature": "(\tbase_url: str,\tcreated_by: str,\tauth: Optional[Tuple[str, str]] = None,\tsession: Optional[requests.sessions.Session] = None,\ttimeout: int = 30)"}, "osmapi.http.OsmApiSession.MAX_RETRY_LIMIT": {"fullname": "osmapi.http.OsmApiSession.MAX_RETRY_LIMIT", "modulename": "osmapi.http", "qualname": "OsmApiSession.MAX_RETRY_LIMIT", "kind": "variable", "doc": "Maximum retries if a call to the remote API fails (default: 5)
\n", "default_value": "5"}, "osmapi.http.OsmApiSession.close": {"fullname": "osmapi.http.OsmApiSession.close", "modulename": "osmapi.http", "qualname": "OsmApiSession.close", "kind": "function", "doc": "\n", "signature": "(self) -> None:", "funcdef": "def"}, "osmapi.node": {"fullname": "osmapi.node", "modulename": "osmapi.node", "kind": "module", "doc": "Node operations for the OpenStreetMap API.
\n"}, "osmapi.node.NodeMixin": {"fullname": "osmapi.node.NodeMixin", "modulename": "osmapi.node", "qualname": "NodeMixin", "kind": "class", "doc": "Mixin providing node-related operations with pythonic method names.
\n"}, "osmapi.node.NodeMixin.node_get": {"fullname": "osmapi.node.NodeMixin.node_get", "modulename": "osmapi.node", "qualname": "NodeMixin.node_get", "kind": "function", "doc": "Returns node with node_id as a dict:
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': {},\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n}\n\n\nIf node_version is supplied, this specific version is returned,\notherwise the latest version is returned.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Creates a node based on the supplied node_data dict:
#!python\n{\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': {},\n}\n\n\nReturns updated node_data (without timestamp):
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the supplied information contain an existing node,\nOsmApi.OsmTypeAlreadyExistsError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Updates node with the supplied node_data dict:
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': {},\n 'version': version number of node,\n}\n\n\nReturns updated node_data (without timestamp):
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Delete node with node_data:
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'version': version number of node,\n}\n\n\nReturns updated node_data (without timestamp):
#!python\n{\n 'id': id of node,\n 'lat': latitude of node,\n 'lon': longitude of node,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of node,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Returns dict with version as key:
\n\n#!python\n{\n 1: dict of node version 1,\n 2: dict of node version 2,\n ...\n}\n\n\nIf the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns list of dicts of ways that use the node with node_id:
#!python\n[\n {\n 'id': id of way,\n 'nd': list of node ids,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n },\n ...\n]\n\n\nIf the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns list of dicts of relations that use the node with node_id:
#!python\n[\n {\n 'id': id of relation,\n 'member': [\n {\n 'ref': reference id,\n 'role': role,\n 'type': node|way|relation\n },\n ...\n ],\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of relation,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n },\n ...\n]\n\n\nIf the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with id as key:
\n\n#!python\n{\n node_id: dict of node,\n ...\n}\n\n\nIf the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Note operations for the OpenStreetMap API.
\n"}, "osmapi.note.NoteMixin": {"fullname": "osmapi.note.NoteMixin", "modulename": "osmapi.note", "qualname": "NoteMixin", "kind": "class", "doc": "Mixin providing note-related operations with pythonic method names.
\n"}, "osmapi.note.NoteMixin.notes_get": {"fullname": "osmapi.note.NoteMixin.notes_get", "modulename": "osmapi.note", "qualname": "NoteMixin.notes_get", "kind": "function", "doc": "Returns a list of dicts of notes in the specified bounding box.
\n\nThe limit parameter defines how many results should be returned.
\n\nclosed specifies the number of days a bug needs to be closed\nto no longer be returned.\nThe value 0 means only open bugs are returned,\n-1 means all bugs are returned.
\n\nAll parameters are optional.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tmin_lon: float,\tmin_lat: float,\tmax_lon: float,\tmax_lat: float,\tlimit: int = 100,\tclosed: int = 7) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.note.NoteMixin.note_get": {"fullname": "osmapi.note.NoteMixin.note_get", "modulename": "osmapi.note", "qualname": "NoteMixin.note_get", "kind": "function", "doc": "Returns a note as dict.
\n\nnote_id is the unique identifier of the note.
Creates a note based on the supplied note_data dict:
#!python\n{\n 'lat': latitude of note,\n 'lon': longitude of note,\n 'text': text of the note,\n}\n\n\nReturns updated note data.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tnote_data: dict[str, typing.Any]) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.note.NoteMixin.note_comment": {"fullname": "osmapi.note.NoteMixin.note_comment", "modulename": "osmapi.note", "qualname": "NoteMixin.note_comment", "kind": "function", "doc": "Adds a new comment to a note.
\n\nReturns the updated note.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tnote_id: int,\tcomment: str) -> dict[str, typing.Any]:", "funcdef": "def"}, "osmapi.note.NoteMixin.note_close": {"fullname": "osmapi.note.NoteMixin.note_close", "modulename": "osmapi.note", "qualname": "NoteMixin.note_close", "kind": "function", "doc": "Closes a note.
\n\nReturns the updated note.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
Reopens a note.
\n\nReturns the updated note.
\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns a list of dicts of notes that match the given search query.
\n\nThe limit parameter defines how many results should be returned.
\n\nclosed specifies the number of days a bug needs to be closed\nto no longer be returned.\nThe value 0 means only open bugs are returned,\n-1 means all bugs are returned.
\n", "signature": "(\tself: osmapi.OsmApi.OsmApi,\tquery: str,\tlimit: int = 100,\tclosed: int = 7) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.parser": {"fullname": "osmapi.parser", "modulename": "osmapi.parser", "kind": "module", "doc": "\n"}, "osmapi.parser.parse_osm": {"fullname": "osmapi.parser.parse_osm", "modulename": "osmapi.parser", "qualname": "parse_osm", "kind": "function", "doc": "Parse osm data.
\n\nReturns list of dict:
\n\n#!python\n{\n type: node|way|relation,\n data: {}\n}\n\n", "signature": "(data: bytes) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.parser.parse_osc": {"fullname": "osmapi.parser.parse_osc", "modulename": "osmapi.parser", "qualname": "parse_osc", "kind": "function", "doc": "Parse osc data.
\n\nReturns list of dict:
\n\n#!python\n{\n type: node|way|relation,\n action: create|delete|modify,\n data: {}\n}\n\n", "signature": "(data: bytes) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.parser.parse_notes": {"fullname": "osmapi.parser.parse_notes", "modulename": "osmapi.parser", "qualname": "parse_notes", "kind": "function", "doc": "Parse notes data.
\n\nReturns a list of dict:
\n\n#!python\n[\n {\n 'id': integer,\n 'action': opened|commented|closed,\n 'status': open|closed\n 'date_created': creation date\n 'date_closed': closing data|None\n 'uid': User ID|None\n 'user': User name|None\n 'comments': {}\n },\n { ... }\n]\n\n", "signature": "(data: bytes) -> list[dict[str, typing.Any]]:", "funcdef": "def"}, "osmapi.relation": {"fullname": "osmapi.relation", "modulename": "osmapi.relation", "kind": "module", "doc": "Relation operations for the OpenStreetMap API.
\n\nThis module provides pythonic (snake_case) methods for working with OSM relations.
\n"}, "osmapi.relation.RelationMixin": {"fullname": "osmapi.relation.RelationMixin", "modulename": "osmapi.relation", "qualname": "RelationMixin", "kind": "class", "doc": "Mixin providing relation-related operations with pythonic method names.
\n"}, "osmapi.relation.RelationMixin.relation_get": {"fullname": "osmapi.relation.RelationMixin.relation_get", "modulename": "osmapi.relation", "qualname": "RelationMixin.relation_get", "kind": "function", "doc": "Returns relation with relation_id as a dict.
If relation_version is supplied, this specific version is returned,\notherwise the latest version is returned.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Creates a relation based on the supplied relation_data dict.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing relation,\nOsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Updates relation with the supplied relation_data dict.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Delete relation with relation_data.
If no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Returns dict with version as key.
\n\nIf the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns a list of dicts of relation data containing relation relation_id.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns the full data (all levels) for relation relation_id as list of dicts.
This function is useful for relations containing other relations.
\n\nIf you don't need all levels, use relation_full instead,\nwhich return only 2 levels.
If any relation (on any level) has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns the full data (two levels) for relation relation_id as list of dicts.
If you need all levels, use relation_full_recur.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with the id of the relation as a key\nfor each relation in relation_id_list.
relation_id_list is a list containing unique identifiers\nfor multiple relations.
Way operations for the OpenStreetMap API.
\n\nThis module provides pythonic (snake_case) methods for working with OSM ways.
\n"}, "osmapi.way.WayMixin": {"fullname": "osmapi.way.WayMixin", "modulename": "osmapi.way", "qualname": "WayMixin", "kind": "class", "doc": "Mixin providing way-related operations with pythonic method names.
\n"}, "osmapi.way.WayMixin.way_get": {"fullname": "osmapi.way.WayMixin.way_get", "modulename": "osmapi.way", "qualname": "WayMixin.way_get", "kind": "function", "doc": "Returns way with way_id as a dict:
#!python\n{\n 'id': id of way,\n 'tag': {} tags of this way,\n 'nd': [] list of nodes belonging to this way\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'timestamp': timestamp of last change,\n 'visible': True|False\n}\n\n\nIf way_version is supplied, this specific version is returned,\notherwise the latest version is returned.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Creates a way based on the supplied way_data dict:
#!python\n{\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n}\n\n\nReturns updated way_data (without timestamp):
#!python\n{\n 'id': id of node,\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If the supplied information contain an existing node,\nOsmApi.OsmTypeAlreadyExistsError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Updates way with the supplied way_data dict:
#!python\n{\n 'id': id of way,\n 'nd': [] list of nodes,\n 'tag': {},\n 'version': version number of way,\n}\n\n\nReturns updated way_data (without timestamp):
#!python\n{\n 'id': id of node,\n 'nd': [] list of nodes,\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If there is already an open changeset,\nOsmApi.ChangesetAlreadyOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Delete way with way_data:
#!python\n{\n 'id': id of way,\n 'nd': [] list of nodes,\n 'tag': dict of tags,\n 'version': version number of way,\n}\n\n\nReturns updated way_data (without timestamp):
#!python\n{\n 'id': id of way,\n 'nd': [] list of nodes,\n 'tag': dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of way,\n 'user': username of last change,\n 'uid': id of user of last change,\n 'visible': True|False\n}\n\n\nIf no authentication information are provided,\nOsmApi.UsernamePasswordMissingError is raised.
If there is no open changeset,\nOsmApi.NoChangesetOpenError is raised.
If the changeset is already closed,\nOsmApi.ChangesetClosedApiError is raised.
Returns dict with version as key:
\n\n#!python\n{\n 1: dict of way version 1,\n 2: dict of way version 2,\n ...\n}\n\n\nIf the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns a list of dicts of relation data containing way way_id:
#!python\n[\n {\n 'id': id of Relation,\n 'member': [\n {\n 'ref': ID of referenced element,\n 'role': optional description of role in relation\n 'type': node|way|relation\n },\n {\n ...\n }\n ]\n 'tag': {} dict of tags,\n 'changeset': id of changeset of last change,\n 'version': version number of Way,\n 'user': username of user that made the last change,\n 'uid': id of user that made the last change,\n 'visible': True|False\n },\n {\n ...\n },\n]\n\n\nThe way_id is a unique identifier for a way.
Returns the full data for way way_id as list of dicts:
#!python\n[\n {\n 'type': node|way|relation,\n 'data': {} data dict for node|way|relation\n },\n { ... }\n]\n\n\nThe way_id is a unique identifier for a way.
If the requested element has been deleted,\nOsmApi.ElementDeletedApiError is raised.
If the requested element can not be found,\nOsmApi.ElementNotFoundApiError is raised.
Returns dict with the id of the way as a key for\neach way in way_id_list:
#!python\n{\n '1234': dict of way data,\n '5678': dict of way data,\n ...\n}\n\n\nway_id_list is a list containing unique identifiers for multiple ways.