overpy
Advanced tools
| Frequently Asked Questions | ||
| ========================== | ||
| 429 Too Many Requests | ||
| --------------------- | ||
| If too many requests are send from the same IP address the server blocks some requests to avoid that a user uses up all resources. | ||
| For more information have a look at the `Overpass API documentation <http://overpass-api.de/command_line.html>`_. | ||
| OverPy tries to fetch missing information automatically. | ||
| To limit the number of requests you should try to fetch all required information/data(relations, ways, nodes, tags, ...) with the initial query. |
| { | ||
| "version": 0.6, | ||
| "generator": "Overpass API", | ||
| "osm3s": { | ||
| "timestamp_osm_base": "2017-03-17T22:05:02Z", | ||
| "timestamp_areas_base": "2017-03-17T18:38:02Z", | ||
| "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." | ||
| }, | ||
| "elements": [ | ||
| ], | ||
| "remark": "runtime error: Query timed out in \"query\" at line 4 after 2 seconds." | ||
| } |
| { | ||
| "version": 0.6, | ||
| "generator": "Overpass API", | ||
| "osm3s": { | ||
| "timestamp_osm_base": "2017-03-17T22:05:02Z", | ||
| "timestamp_areas_base": "2017-03-17T18:38:02Z", | ||
| "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." | ||
| }, | ||
| "elements": [ | ||
| ], | ||
| "remark": "runtime remark: Test" | ||
| } |
| { | ||
| "version": 0.6, | ||
| "generator": "Overpass API", | ||
| "osm3s": { | ||
| "timestamp_osm_base": "2017-03-17T22:05:02Z", | ||
| "timestamp_areas_base": "2017-03-17T18:38:02Z", | ||
| "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." | ||
| }, | ||
| "elements": [ | ||
| ], | ||
| "remark": "Test remark" | ||
| } |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <osm version="0.6" generator="Overpass API"> | ||
| <note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note> | ||
| <meta osm_base="2017-03-17T22:03:02Z" areas="2017-03-17T18:38:02Z"/> | ||
| <remark> runtime error: Query timed out in "query" at line 4 after 2 seconds. </remark> | ||
| </osm> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <osm version="0.6" generator="Overpass API"> | ||
| <note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note> | ||
| <meta osm_base="2017-03-17T22:03:02Z" areas="2017-03-17T18:38:02Z"/> | ||
| <remark> runtime remark: Test </remark> | ||
| </osm> |
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <osm version="0.6" generator="Overpass API"> | ||
| <note>The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.</note> | ||
| <meta osm_base="2017-03-17T22:03:02Z" areas="2017-03-17T18:38:02Z"/> | ||
| <remark> Test remark </remark> | ||
| </osm> |
+12
-0
@@ -9,2 +9,14 @@ Changelog | ||
| 0.5 (2021-04-14) | ||
| ~~~~~~~~~~~~~~~~ | ||
| * Add support to retry a failed request | ||
| * Improve test handlers to fix issues with Py2.7 and PyPy | ||
| * Improve tests | ||
| * Add Support for Python 3.6 | ||
| * Add function to handle remark tags and elements | ||
| * Add support to parse data from ET.Element | ||
| * Move attribute modifiers from class to package | ||
| * Change to use Exception instead of BaseException | ||
| 0.4 (2016-12-08) | ||
@@ -11,0 +23,0 @@ ~~~~~~~~~~~~~~~~ |
@@ -11,2 +11,3 @@ Welcome to Python Overpass API's documentation! | ||
| example | ||
| faq | ||
| api | ||
@@ -22,2 +23,1 @@ contributing | ||
| * :ref:`search` | ||
| Metadata-Version: 1.1 | ||
| Name: overpy | ||
| Version: 0.4 | ||
| Version: 0.5 | ||
| Summary: Python Wrapper to access the OpenStreepMap Overpass API | ||
@@ -16,7 +16,7 @@ Home-page: https://github.com/DinoTools/python-overpy | ||
| .. image:: https://pypip.in/version/overpy/badge.svg | ||
| .. image:: https://img.shields.io/pypi/v/overpy.svg | ||
| :target: https://pypi.python.org/pypi/overpy/ | ||
| :alt: Latest Version | ||
| .. image:: https://pypip.in/license/overpy/badge.svg | ||
| .. image:: https://img.shields.io/pypi/l/overpy.svg | ||
| :target: https://pypi.python.org/pypi/overpy/ | ||
@@ -123,3 +123,4 @@ :alt: License | ||
| Classifier: Programming Language :: Python :: 3.5 | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||
| Classifier: Programming Language :: Python :: Implementation :: PyPy |
@@ -13,2 +13,3 @@ CHANGELOG.rst | ||
| docs/source/example.rst | ||
| docs/source/faq.rst | ||
| docs/source/index.rst | ||
@@ -42,2 +43,5 @@ docs/source/introduction.rst | ||
| tests/json/relation-04.json | ||
| tests/json/remark-runtime-error-01.json | ||
| tests/json/remark-runtime-remark-01.json | ||
| tests/json/remark-unknown-01.json | ||
| tests/json/result-expand-01.json | ||
@@ -60,2 +64,5 @@ tests/json/result-expand-02.json | ||
| tests/xml/relation-04.xml | ||
| tests/xml/remark-runtime-error-01.xml | ||
| tests/xml/remark-runtime-remark-01.xml | ||
| tests/xml/remark-unknown-01.xml | ||
| tests/xml/way-01.xml | ||
@@ -62,0 +69,0 @@ tests/xml/way-02.xml |
| overpy | ||
| tests |
@@ -16,3 +16,3 @@ __all__ = [ | ||
| __version__ = "0.4" | ||
| __version__ = "0.5" | ||
@@ -23,2 +23,2 @@ __author__ = "PhiBo (DinoTools)" | ||
| __license__ = "MIT" | ||
| __copyright__ = "Copyright 2014-2016 %s" % __author__ | ||
| __copyright__ = "Copyright 2014-2021 %s" % __author__ |
+147
-58
@@ -8,2 +8,3 @@ from collections import OrderedDict | ||
| import sys | ||
| import time | ||
@@ -22,2 +23,12 @@ from overpy import exception | ||
| # Try to convert some common attributes | ||
| # http://wiki.openstreetmap.org/wiki/Elements#Common_attributes | ||
| GLOBAL_ATTRIBUTE_MODIFIERS = { | ||
| "changeset": int, | ||
| "timestamp": lambda ts: datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ"), | ||
| "uid": int, | ||
| "version": int, | ||
| "visible": lambda v: v.lower() == "true" | ||
| } | ||
| if PY2: | ||
@@ -46,7 +57,12 @@ from urllib2 import urlopen | ||
| Class to access the Overpass API | ||
| :cvar default_max_retry_count: Global max number of retries (Default: 0) | ||
| :cvar default_retry_timeout: Global time to wait between tries (Default: 1.0s) | ||
| """ | ||
| default_max_retry_count = 0 | ||
| default_read_chunk_size = 4096 | ||
| default_retry_timeout = 1.0 | ||
| default_url = "http://overpass-api.de/api/interpreter" | ||
| def __init__(self, read_chunk_size=None, url=None, xml_parser=XML_PARSER_SAX): | ||
| def __init__(self, read_chunk_size=None, url=None, xml_parser=XML_PARSER_SAX, max_retry_count=None, retry_timeout=None): | ||
| """ | ||
@@ -59,2 +75,6 @@ :param read_chunk_size: Max size of each chunk read from the server response | ||
| :type xml_parser: Integer | ||
| :param max_retry_count: Max number of retries (Default: default_max_retry_count) | ||
| :type max_retry_count: Integer | ||
| :param retry_timeout: Time to wait between tries (Default: default_retry_timeout) | ||
| :type retry_timeout: float | ||
| """ | ||
@@ -70,4 +90,29 @@ self.url = self.default_url | ||
| self.read_chunk_size = read_chunk_size | ||
| if max_retry_count is None: | ||
| max_retry_count = self.default_max_retry_count | ||
| self.max_retry_count = max_retry_count | ||
| if retry_timeout is None: | ||
| retry_timeout = self.default_retry_timeout | ||
| self.retry_timeout = retry_timeout | ||
| self.xml_parser = xml_parser | ||
| def _handle_remark_msg(self, msg): | ||
| """ | ||
| Try to parse the message provided with the remark tag or element. | ||
| :param str msg: The message | ||
| :raises overpy.exception.OverpassRuntimeError: If message starts with 'runtime error:' | ||
| :raises overpy.exception.OverpassRuntimeRemark: If message starts with 'runtime remark:' | ||
| :raises overpy.exception.OverpassUnknownError: If we are unable to identify the error | ||
| """ | ||
| msg = msg.strip() | ||
| if msg.startswith("runtime error:"): | ||
| raise exception.OverpassRuntimeError(msg=msg) | ||
| elif msg.startswith("runtime remark:"): | ||
| raise exception.OverpassRuntimeRemark(msg=msg) | ||
| raise exception.OverpassUnknownError(msg=msg) | ||
| def query(self, query): | ||
@@ -84,53 +129,82 @@ """ | ||
| try: | ||
| f = urlopen(self.url, query) | ||
| except HTTPError as e: | ||
| f = e | ||
| retry_num = 0 | ||
| retry_exceptions = [] | ||
| do_retry = True if self.max_retry_count > 0 else False | ||
| while retry_num <= self.max_retry_count: | ||
| if retry_num > 0: | ||
| time.sleep(self.retry_timeout) | ||
| retry_num += 1 | ||
| try: | ||
| f = urlopen(self.url, query) | ||
| except HTTPError as e: | ||
| f = e | ||
| response = f.read(self.read_chunk_size) | ||
| while True: | ||
| data = f.read(self.read_chunk_size) | ||
| if len(data) == 0: | ||
| break | ||
| response = response + data | ||
| f.close() | ||
| response = f.read(self.read_chunk_size) | ||
| while True: | ||
| data = f.read(self.read_chunk_size) | ||
| if len(data) == 0: | ||
| break | ||
| response = response + data | ||
| f.close() | ||
| if f.code == 200: | ||
| if PY2: | ||
| http_info = f.info() | ||
| content_type = http_info.getheader("content-type") | ||
| else: | ||
| content_type = f.getheader("Content-Type") | ||
| if f.code == 200: | ||
| if PY2: | ||
| http_info = f.info() | ||
| content_type = http_info.getheader("content-type") | ||
| else: | ||
| content_type = f.getheader("Content-Type") | ||
| if content_type == "application/json": | ||
| return self.parse_json(response) | ||
| if content_type == "application/json": | ||
| return self.parse_json(response) | ||
| if content_type == "application/osm3s+xml": | ||
| return self.parse_xml(response) | ||
| if content_type == "application/osm3s+xml": | ||
| return self.parse_xml(response) | ||
| raise exception.OverpassUnknownContentType(content_type) | ||
| e = exception.OverpassUnknownContentType(content_type) | ||
| if not do_retry: | ||
| raise e | ||
| retry_exceptions.append(e) | ||
| continue | ||
| if f.code == 400: | ||
| msgs = [] | ||
| for msg in self._regex_extract_error_msg.finditer(response): | ||
| tmp = self._regex_remove_tag.sub(b"", msg.group("msg")) | ||
| try: | ||
| tmp = tmp.decode("utf-8") | ||
| except UnicodeDecodeError: | ||
| tmp = repr(tmp) | ||
| msgs.append(tmp) | ||
| if f.code == 400: | ||
| msgs = [] | ||
| for msg in self._regex_extract_error_msg.finditer(response): | ||
| tmp = self._regex_remove_tag.sub(b"", msg.group("msg")) | ||
| try: | ||
| tmp = tmp.decode("utf-8") | ||
| except UnicodeDecodeError: | ||
| tmp = repr(tmp) | ||
| msgs.append(tmp) | ||
| raise exception.OverpassBadRequest( | ||
| query, | ||
| msgs=msgs | ||
| ) | ||
| e = exception.OverpassBadRequest( | ||
| query, | ||
| msgs=msgs | ||
| ) | ||
| if not do_retry: | ||
| raise e | ||
| retry_exceptions.append(e) | ||
| continue | ||
| if f.code == 429: | ||
| raise exception.OverpassTooManyRequests | ||
| if f.code == 429: | ||
| e = exception.OverpassTooManyRequests | ||
| if not do_retry: | ||
| raise e | ||
| retry_exceptions.append(e) | ||
| continue | ||
| if f.code == 504: | ||
| raise exception.OverpassGatewayTimeout | ||
| if f.code == 504: | ||
| e = exception.OverpassGatewayTimeout | ||
| if not do_retry: | ||
| raise e | ||
| retry_exceptions.append(e) | ||
| continue | ||
| raise exception.OverpassUnknownHTTPStatusCode(f.code) | ||
| e = exception.OverpassUnknownHTTPStatusCode(f.code) | ||
| if not do_retry: | ||
| raise e | ||
| retry_exceptions.append(e) | ||
| continue | ||
| raise exception.MaxRetriesReached(retry_count=retry_num, exceptions=retry_exceptions) | ||
| def parse_json(self, data, encoding="utf-8"): | ||
@@ -150,2 +224,4 @@ """ | ||
| data = json.loads(data, parse_float=Decimal) | ||
| if "remark" in data: | ||
| self._handle_remark_msg(msg=data.get("remark")) | ||
| return Result.from_json(data, api=self) | ||
@@ -172,2 +248,6 @@ | ||
| m = re.compile("<remark>(?P<msg>[^<>]*)</remark>").search(data) | ||
| if m: | ||
| self._handle_remark_msg(m.group("msg")) | ||
| return Result.from_xml(data, api=self, parser=parser) | ||
@@ -290,19 +370,35 @@ | ||
| @classmethod | ||
| def from_xml(cls, data, api=None, parser=XML_PARSER_SAX): | ||
| def from_xml(cls, data, api=None, parser=None): | ||
| """ | ||
| Create a new instance and load data from xml object. | ||
| Create a new instance and load data from xml data or object. | ||
| .. note:: | ||
| If parser is set to None, the functions tries to find the best parse. | ||
| By default the SAX parser is chosen if a string is provided as data. | ||
| The parser is set to DOM if an xml.etree.ElementTree.Element is provided as data value. | ||
| :param data: Root element | ||
| :type data: xml.etree.ElementTree.Element | ||
| :param api: | ||
| :type data: str | xml.etree.ElementTree.Element | ||
| :param api: The instance to query additional information if required. | ||
| :type api: Overpass | ||
| :param parser: Specify the parser to use(DOM or SAX) | ||
| :type parser: Integer | ||
| :param parser: Specify the parser to use(DOM or SAX)(Default: None = autodetect, defaults to SAX) | ||
| :type parser: Integer | None | ||
| :return: New instance of Result object | ||
| :rtype: Result | ||
| """ | ||
| if parser is None: | ||
| if isinstance(data, str): | ||
| parser = XML_PARSER_SAX | ||
| else: | ||
| parser = XML_PARSER_DOM | ||
| result = cls(api=api) | ||
| if parser == XML_PARSER_DOM: | ||
| import xml.etree.ElementTree as ET | ||
| root = ET.fromstring(data) | ||
| if isinstance(data, str): | ||
| root = ET.fromstring(data) | ||
| elif isinstance(data, ET.Element): | ||
| root = data | ||
| else: | ||
| raise exception.OverPyException("Unable to detect data type.") | ||
@@ -534,13 +630,6 @@ for elem_cls in [Node, Way, Relation, Area]: | ||
| self._result = result | ||
| # Try to convert some common attributes | ||
| # http://wiki.openstreetmap.org/wiki/Elements#Common_attributes | ||
| self._attribute_modifiers = { | ||
| "changeset": int, | ||
| "timestamp": lambda ts: datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ"), | ||
| "uid": int, | ||
| "version": int, | ||
| "visible": lambda v: v.lower() == "true" | ||
| } | ||
| self.attributes = attributes | ||
| for n, m in self._attribute_modifiers.items(): | ||
| # ToDo: Add option to modify attribute modifiers | ||
| attribute_modifiers = dict(GLOBAL_ATTRIBUTE_MODIFIERS.items()) | ||
| for n, m in attribute_modifiers.items(): | ||
| if n in self.attributes: | ||
@@ -547,0 +636,0 @@ self.attributes[n] = m(self.attributes[n]) |
+60
-2
@@ -1,2 +0,2 @@ | ||
| class OverPyException(BaseException): | ||
| class OverPyException(Exception): | ||
| """OverPy base exception""" | ||
@@ -40,2 +40,14 @@ pass | ||
| class MaxRetriesReached(OverPyException): | ||
| """ | ||
| Raised if max retries reached and the Overpass server didn't respond with a result. | ||
| """ | ||
| def __init__(self, retry_count, exceptions): | ||
| self.exceptions = exceptions | ||
| self.retry_count = retry_count | ||
| def __str__(self): | ||
| return "Unable get any result from the Overpass API server after %d retries." % self.retry_count | ||
| class OverpassBadRequest(OverPyException): | ||
@@ -66,2 +78,25 @@ """ | ||
| class OverpassError(OverPyException): | ||
| """ | ||
| Base exception to report errors if the response returns a remark tag or element. | ||
| .. note:: | ||
| If you are not sure which of the subexceptions you should use, use this one and try to parse the message. | ||
| For more information have a look at https://github.com/DinoTools/python-overpy/issues/62 | ||
| :param str msg: The message from the remark tag or element | ||
| """ | ||
| def __init__(self, msg=None): | ||
| #: The message from the remark tag or element | ||
| self.msg = msg | ||
| def __str__(self): | ||
| if self.msg is None: | ||
| return "No error message provided" | ||
| if not isinstance(self.msg, str): | ||
| return str(self.msg) | ||
| return self.msg | ||
| class OverpassGatewayTimeout(OverPyException): | ||
@@ -75,2 +110,18 @@ """ | ||
| class OverpassRuntimeError(OverpassError): | ||
| """ | ||
| Raised if the server returns a remark-tag(xml) or remark element(json) with a message starting with | ||
| 'runtime error:'. | ||
| """ | ||
| pass | ||
| class OverpassRuntimeRemark(OverpassError): | ||
| """ | ||
| Raised if the server returns a remark-tag(xml) or remark element(json) with a message starting with | ||
| 'runtime remark:'. | ||
| """ | ||
| pass | ||
| class OverpassTooManyRequests(OverPyException): | ||
@@ -100,2 +151,9 @@ """ | ||
| class OverpassUnknownError(OverpassError): | ||
| """ | ||
| Raised if the server returns a remark-tag(xml) or remark element(json) and we are unable to find any reason. | ||
| """ | ||
| pass | ||
| class OverpassUnknownHTTPStatusCode(OverPyException): | ||
@@ -112,2 +170,2 @@ """ | ||
| def __str__(self): | ||
| return "Unknown/Unhandled status code: %d" % self.code | ||
| return "Unknown/Unhandled status code: %d" % self.code |
+4
-3
| Metadata-Version: 1.1 | ||
| Name: overpy | ||
| Version: 0.4 | ||
| Version: 0.5 | ||
| Summary: Python Wrapper to access the OpenStreepMap Overpass API | ||
@@ -16,7 +16,7 @@ Home-page: https://github.com/DinoTools/python-overpy | ||
| .. image:: https://pypip.in/version/overpy/badge.svg | ||
| .. image:: https://img.shields.io/pypi/v/overpy.svg | ||
| :target: https://pypi.python.org/pypi/overpy/ | ||
| :alt: Latest Version | ||
| .. image:: https://pypip.in/license/overpy/badge.svg | ||
| .. image:: https://img.shields.io/pypi/l/overpy.svg | ||
| :target: https://pypi.python.org/pypi/overpy/ | ||
@@ -123,3 +123,4 @@ :alt: License | ||
| Classifier: Programming Language :: Python :: 3.5 | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: Implementation :: CPython | ||
| Classifier: Programming Language :: Python :: Implementation :: PyPy |
+2
-2
@@ -8,7 +8,7 @@ Python Overpass Wrapper | ||
| .. image:: https://pypip.in/version/overpy/badge.svg | ||
| .. image:: https://img.shields.io/pypi/v/overpy.svg | ||
| :target: https://pypi.python.org/pypi/overpy/ | ||
| :alt: Latest Version | ||
| .. image:: https://pypip.in/license/overpy/badge.svg | ||
| .. image:: https://img.shields.io/pypi/l/overpy.svg | ||
| :target: https://pypi.python.org/pypi/overpy/ | ||
@@ -15,0 +15,0 @@ :alt: License |
+0
-1
@@ -13,3 +13,2 @@ [metadata] | ||
| tag_date = 0 | ||
| tag_svn_revision = 0 | ||
+2
-1
@@ -42,2 +42,3 @@ #!/usr/bin/env python | ||
| "Programming Language :: Python :: 3.5", | ||
| "Programming Language :: Python :: 3.6", | ||
| "Programming Language :: Python :: Implementation :: CPython", | ||
@@ -48,3 +49,3 @@ "Programming Language :: Python :: Implementation :: PyPy" | ||
| install_requires=[], | ||
| packages=find_packages(exclude=["*.tests", "*.tests.*"]), | ||
| packages=find_packages(exclude=["*.tests", "*.tests.*", "tests"]), | ||
| include_package_data=True, | ||
@@ -51,0 +52,0 @@ package_data={ |
+29
-18
| import os | ||
| import sys | ||
| import time | ||
| from multiprocessing import Process | ||
| import threading | ||
| from threading import Lock | ||
@@ -9,5 +8,7 @@ | ||
| if PY2: | ||
| from SocketServer import TCPServer, BaseRequestHandler | ||
| from SocketServer import BaseRequestHandler, TCPServer, ThreadingMixIn | ||
| from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | ||
| else: | ||
| from socketserver import TCPServer, BaseRequestHandler | ||
| from socketserver import BaseRequestHandler, TCPServer, ThreadingMixIn | ||
| from http.server import BaseHTTPRequestHandler, HTTPServer | ||
@@ -23,2 +24,16 @@ TCPServer.allow_reuse_address = True | ||
| class OverpyBaseRequestHandler(BaseRequestHandler): | ||
| def handle(self): | ||
| for data in self.get_response(self): | ||
| self.request.send(data) | ||
| @staticmethod | ||
| def get_response(self): | ||
| yield b"" | ||
| class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): | ||
| pass | ||
| def read_file(filename, mode="r"): | ||
@@ -29,9 +44,2 @@ filename = os.path.join(os.path.dirname(__file__), filename) | ||
| def server_thread(server): | ||
| request = server.get_request() | ||
| server.process_request(*request) | ||
| server.server_close() | ||
| server.socket.close() | ||
| def new_server_thread(handle_cls, port=None): | ||
@@ -45,14 +53,17 @@ global current_port | ||
| server = TCPServer( | ||
| server = ThreadedHTTPServer( | ||
| (HOST, port), | ||
| handle_cls | ||
| ) | ||
| p = Process(target=server_thread, args=(server,)) | ||
| p.start() | ||
| # Give the server some time to bind | ||
| # Is there a better option? | ||
| time.sleep(0.2) | ||
| server_thread = threading.Thread(target=server.serve_forever) | ||
| server_thread.daemon = True | ||
| server_thread.start() | ||
| return ( | ||
| "http://%s:%d" % (HOST, port), | ||
| p | ||
| server | ||
| ) | ||
| def stop_server_thread(server): | ||
| server.shutdown() | ||
| server.server_close() |
@@ -42,2 +42,24 @@ import overpy | ||
| assert e.code == 123 | ||
| assert str(e).endswith("123") | ||
| assert str(e).endswith("123") | ||
| def test_overpass_error(self): | ||
| exceptions = [ | ||
| overpy.exception.OverpassError, | ||
| overpy.exception.OverpassRuntimeError, | ||
| overpy.exception.OverpassRuntimeRemark, | ||
| overpy.exception.OverpassUnknownError | ||
| ] | ||
| for cls in exceptions: | ||
| e = cls(msg="Test message") | ||
| assert e.msg == "Test message" | ||
| assert str(e) == "Test message" | ||
| for cls in exceptions: | ||
| e = cls() | ||
| assert e.msg is None | ||
| assert str(e) == "No error message provided" | ||
| for cls in exceptions: | ||
| e = cls(msg=123) | ||
| assert e.msg == 123 | ||
| assert str(e) == "123" |
+18
-1
@@ -101,2 +101,19 @@ import pytest | ||
| } | ||
| ) | ||
| ) | ||
| class TestRemark(object): | ||
| def test_remark_runtime_error(self): | ||
| api = overpy.Overpass() | ||
| with pytest.raises(overpy.exception.OverpassRuntimeError): | ||
| api.parse_json(read_file("json/remark-runtime-error-01.json")) | ||
| def test_remark_runtime_remark(self): | ||
| api = overpy.Overpass() | ||
| with pytest.raises(overpy.exception.OverpassRuntimeRemark): | ||
| api.parse_json(read_file("json/remark-runtime-remark-01.json")) | ||
| def test_remark_unknown(self): | ||
| api = overpy.Overpass() | ||
| with pytest.raises(overpy.exception.OverpassUnknownError): | ||
| api.parse_json(read_file("json/remark-unknown-01.json")) |
+122
-69
@@ -5,90 +5,129 @@ import pytest | ||
| from tests import BaseRequestHandler | ||
| from tests import read_file, new_server_thread | ||
| from tests import BaseHTTPRequestHandler | ||
| from tests import read_file, new_server_thread, stop_server_thread | ||
| class HandleOverpassBadRequest(BaseRequestHandler): | ||
| def handle_bad_request(request): | ||
| request.send_response(400, "Bad Request") | ||
| request.send_header("Content-Type", "text/html; charset=utf-8") | ||
| request.end_headers() | ||
| request.wfile.write(read_file("response/bad-request.html", "rb")) | ||
| def handle_bad_request_encoding(request): | ||
| request.send_response(400, "Bad Request") | ||
| request.send_header("Content-Type", "text/html; charset=utf-8") | ||
| request.end_headers() | ||
| request.wfile.write(read_file("response/bad-request-encoding.html", "rb")) | ||
| def handle_too_many_requests(request): | ||
| request.send_response(429, "Too Many Requests") | ||
| request.send_header("Content-Type", "text/html; charset=utf-8") | ||
| request.end_headers() | ||
| request.wfile.write(b"Too Many Requests") | ||
| def handle_gateway_timeout(request): | ||
| request.send_response(504, "Gateway Timeout") | ||
| request.send_header("Content-Type", "text/html; charset=utf-8") | ||
| request.end_headers() | ||
| request.wfile.write(b"Gateway Timeout") | ||
| def handle_unknown_content_type(request): | ||
| request.send_response(200, "OK") | ||
| request.send_header("Content-Type", "application/foobar") | ||
| request.end_headers() | ||
| request.wfile.write(read_file("xml/way-02.xml", "rb")) | ||
| def handle_unknown_http_status_code(request): | ||
| request.send_response(123, "Unknown") | ||
| request.send_header("Content-Type", "text/html; charset=utf-8") | ||
| request.end_headers() | ||
| request.wfile.write(b"Unknown status code") | ||
| class HandleOverpassBadRequest(BaseHTTPRequestHandler): | ||
| """ | ||
| Simulate the response if the query string has syntax errors | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 400 Bad Request\r\n") | ||
| self.request.send(b"Content-Type text/html; charset=utf-8\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("response/bad-request.html", "rb")) | ||
| def do_POST(self): | ||
| handle_bad_request(self) | ||
| class HandleOverpassBadRequestEncoding(BaseRequestHandler): | ||
| class HandleOverpassBadRequestEncoding(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 400 Bad Request\r\n") | ||
| self.request.send(b"Content-Type text/html; charset=utf-8\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("response/bad-request-encoding.html", "rb")) | ||
| def do_POST(self): | ||
| handle_bad_request_encoding(self) | ||
| class HandleOverpassTooManyRequests(BaseRequestHandler): | ||
| class HandleOverpassTooManyRequests(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 429 Too Many Requests\r\n") | ||
| self.request.send(b"Content-Type text/html; charset=utf-8\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(b"Too Many Requests") | ||
| def do_POST(self): | ||
| handle_too_many_requests(self) | ||
| class HandleOverpassGatewayTimeout(BaseRequestHandler): | ||
| class HandleOverpassGatewayTimeout(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 504 Gateway Timeout\r\n") | ||
| self.request.send(b"Content-Type text/html; charset=utf-8\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(b"Gateway Timeout") | ||
| def do_POST(self): | ||
| handle_gateway_timeout(self) | ||
| class HandleOverpassUnknownHTTPStatusCode(BaseRequestHandler): | ||
| class HandleOverpassUnknownHTTPStatusCode(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 123 Unknown\r\n") | ||
| self.request.send(b"Content-Type text/html; charset=utf-8\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(b"Unknown status code") | ||
| def do_POST(self): | ||
| handle_unknown_http_status_code(self) | ||
| class HandleResponseJSON(BaseRequestHandler): | ||
| class HandleResponseJSON(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/json\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("json/way-02.json", "rb")) | ||
| def do_POST(self): | ||
| self.send_response(200, "OK") | ||
| self.send_header("Content-Type", "application/json") | ||
| self.end_headers() | ||
| self.wfile.write(read_file("json/way-02.json", "rb")) | ||
| class HandleResponseXML(BaseRequestHandler): | ||
| class HandleResponseXML(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/osm3s+xml\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("xml/way-02.xml", "rb")) | ||
| def do_POST(self): | ||
| self.send_response(200, "OK") | ||
| self.send_header("Content-Type", "application/osm3s+xml") | ||
| self.end_headers() | ||
| self.wfile.write(read_file("xml/way-02.xml", "rb")) | ||
| class HandleResponseUnknown(BaseRequestHandler): | ||
| class HandleResponseUnknown(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/foobar\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("xml/way-02.xml", "rb")) | ||
| def do_POST(self): | ||
| handle_unknown_content_type(self) | ||
| class HandleRetry(BaseHTTPRequestHandler): | ||
| default_handler_func = [ | ||
| handle_bad_request, | ||
| handle_bad_request_encoding, | ||
| handle_too_many_requests, | ||
| handle_gateway_timeout, | ||
| handle_unknown_content_type, | ||
| handle_unknown_http_status_code | ||
| ] | ||
| def do_POST(self): | ||
| f = self.default_handler_func.pop(0) | ||
| f(self) | ||
| class TestQuery(object): | ||
| def test_chunk_size(self): | ||
| url, t = new_server_thread(HandleResponseJSON) | ||
| url, server = new_server_thread(HandleResponseJSON) | ||
@@ -98,7 +137,7 @@ api = overpy.Overpass(read_chunk_size=128) | ||
| result = api.query("[out:json];node(50.745,7.17,50.75,7.18);out;") | ||
| t.join() | ||
| stop_server_thread(server) | ||
| assert len(result.nodes) > 0 | ||
| def test_overpass_syntax_error(self): | ||
| url, t = new_server_thread(HandleOverpassBadRequest) | ||
| url, server = new_server_thread(HandleOverpassBadRequest) | ||
@@ -113,3 +152,3 @@ api = overpy.Overpass() | ||
| )) | ||
| t.join() | ||
| stop_server_thread(server) | ||
@@ -122,3 +161,3 @@ def test_overpass_syntax_error_encoding_error(self): | ||
| url, t = new_server_thread(HandleOverpassBadRequestEncoding) | ||
| url, server = new_server_thread(HandleOverpassBadRequestEncoding) | ||
@@ -133,6 +172,6 @@ api = overpy.Overpass() | ||
| )) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_overpass_too_many_requests(self): | ||
| url, t = new_server_thread(HandleOverpassTooManyRequests) | ||
| url, server = new_server_thread(HandleOverpassTooManyRequests) | ||
@@ -146,6 +185,6 @@ api = overpy.Overpass() | ||
| )) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_overpass_gateway_timeout(self): | ||
| url, t = new_server_thread(HandleOverpassGatewayTimeout) | ||
| url, server = new_server_thread(HandleOverpassGatewayTimeout) | ||
@@ -159,6 +198,6 @@ api = overpy.Overpass() | ||
| )) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_overpass_unknown_status_code(self): | ||
| url, t = new_server_thread(HandleOverpassUnknownHTTPStatusCode) | ||
| url, server = new_server_thread(HandleOverpassUnknownHTTPStatusCode) | ||
@@ -172,6 +211,6 @@ api = overpy.Overpass() | ||
| )) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_response_json(self): | ||
| url, t = new_server_thread(HandleResponseJSON) | ||
| url, server = new_server_thread(HandleResponseJSON) | ||
@@ -181,7 +220,7 @@ api = overpy.Overpass() | ||
| result = api.query("[out:json];node(50.745,7.17,50.75,7.18);out;") | ||
| t.join() | ||
| stop_server_thread(server) | ||
| assert len(result.nodes) > 0 | ||
| def test_response_unknown(self): | ||
| url, t = new_server_thread(HandleResponseUnknown) | ||
| url, server = new_server_thread(HandleResponseUnknown) | ||
@@ -192,6 +231,6 @@ api = overpy.Overpass() | ||
| api.query("[out:xml];node(50.745,7.17,50.75,7.18);out;") | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_response_xml(self): | ||
| url, t = new_server_thread(HandleResponseXML) | ||
| url, server = new_server_thread(HandleResponseXML) | ||
@@ -201,3 +240,17 @@ api = overpy.Overpass() | ||
| result = api.query("[out:xml];node(50.745,7.17,50.75,7.18);out;") | ||
| t.join() | ||
| assert len(result.nodes) > 0 | ||
| stop_server_thread(server) | ||
| assert len(result.nodes) > 0 | ||
| def test_retry(self): | ||
| url, server = new_server_thread(HandleRetry) | ||
| api = overpy.Overpass() | ||
| # HandleRetry.default_handler_cls should contain at least 2 classes to process | ||
| api.max_retry_count = len(HandleRetry.default_handler_func) - 1 | ||
| api.url = url | ||
| with pytest.raises(overpy.exception.MaxRetriesReached): | ||
| api.query(( | ||
| "way(1);" | ||
| "out body;" | ||
| )) | ||
| stop_server_thread(server) |
+26
-25
@@ -5,33 +5,34 @@ import pytest | ||
| from tests import read_file, new_server_thread, BaseRequestHandler | ||
| from tests import BaseHTTPRequestHandler | ||
| from tests import read_file, new_server_thread, stop_server_thread | ||
| class HandleResponseJSON01(BaseRequestHandler): | ||
| class HandleResponseJSON01(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/json\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("json/result-way-01.json", "rb")) | ||
| def do_POST(self): | ||
| self.send_response(200, "OK") | ||
| self.send_header("Content-Type", "application/json") | ||
| self.end_headers() | ||
| self.wfile.write(read_file("json/result-way-01.json", "rb")) | ||
| class HandleResponseJSON02(BaseRequestHandler): | ||
| class HandleResponseJSON02(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/json\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("json/result-way-02.json", "rb")) | ||
| def do_POST(self): | ||
| self.send_response(200, "OK") | ||
| self.send_header("Content-Type", "application/json") | ||
| self.end_headers() | ||
| self.wfile.write(read_file("json/result-way-02.json", "rb")) | ||
| class HandleResponseJSON03(BaseRequestHandler): | ||
| class HandleResponseJSON03(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/json\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("json/result-way-03.json", "rb")) | ||
| def do_POST(self): | ||
| self.send_response(200, "OK") | ||
| self.send_header("Content-Type", "application/json") | ||
| self.end_headers() | ||
| self.wfile.write(read_file("json/result-way-03.json", "rb")) | ||
@@ -41,3 +42,3 @@ | ||
| def test_missing_unresolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON01) | ||
| url, server = new_server_thread(HandleResponseJSON01) | ||
@@ -61,6 +62,6 @@ api = overpy.Overpass() | ||
| assert len(result.nodes) == 0 | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_missing_partly_unresolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -84,6 +85,6 @@ api = overpy.Overpass() | ||
| assert len(result.nodes) == 1 | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_missing_resolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON03) | ||
| url, server = new_server_thread(HandleResponseJSON03) | ||
@@ -107,2 +108,2 @@ api = overpy.Overpass() | ||
| t.join() | ||
| stop_server_thread(server) |
+41
-23
@@ -5,13 +5,14 @@ import pytest | ||
| from tests import read_file, new_server_thread, BaseRequestHandler | ||
| from tests.base_class import BaseTestWay | ||
| from tests import read_file, new_server_thread, stop_server_thread, BaseHTTPRequestHandler | ||
| class HandleResponseJSON02(BaseRequestHandler): | ||
| class HandleResponseJSON02(BaseHTTPRequestHandler): | ||
| """ | ||
| """ | ||
| def handle(self): | ||
| self.request.send(b"HTTP/1.0 200 OK\r\n") | ||
| self.request.send(b"Content-Type: application/json\r\n") | ||
| self.request.send(b"\r\n") | ||
| self.request.send(read_file("json/result-expand-02.json", "rb")) | ||
| def do_POST(self): | ||
| self.send_response(200, "OK") | ||
| self.send_header("Content-Type", "application/json") | ||
| self.end_headers() | ||
| self.wfile.write(read_file("json/result-expand-02.json", "rb")) | ||
@@ -51,3 +52,3 @@ | ||
| def test_missing_unresolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -60,6 +61,6 @@ api = overpy.Overpass() | ||
| result1.get_area(123, resolve_missing=True) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_missing_resolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -80,3 +81,3 @@ api = overpy.Overpass() | ||
| t.join() | ||
| stop_server_thread(server) | ||
@@ -86,3 +87,3 @@ | ||
| def test_missing_unresolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -95,6 +96,6 @@ api = overpy.Overpass() | ||
| result1.get_node(123, resolve_missing=True) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_missing_resolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -115,8 +116,25 @@ api = overpy.Overpass() | ||
| t.join() | ||
| stop_server_thread(server) | ||
| class TestPickle(BaseTestWay): | ||
| def test_way02(self): | ||
| """ | ||
| Try to pickle and unpickle the result object | ||
| """ | ||
| import pickle | ||
| api = overpy.Overpass() | ||
| result = api.parse_json(read_file("json/way-02.json")) | ||
| self._test_way02(result) | ||
| # do pickle and unpickle | ||
| result_string = pickle.dumps(result) | ||
| new_result = pickle.loads(result_string) | ||
| # test new result | ||
| self._test_way02(new_result) | ||
| class TestRelation(object): | ||
| def test_missing_unresolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -129,6 +147,6 @@ api = overpy.Overpass() | ||
| result1.get_relation(123, resolve_missing=True) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_missing_resolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -149,3 +167,3 @@ api = overpy.Overpass() | ||
| t.join() | ||
| stop_server_thread(server) | ||
@@ -155,3 +173,3 @@ | ||
| def test_missing_unresolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -164,6 +182,6 @@ api = overpy.Overpass() | ||
| result1.get_way(123, resolve_missing=True) | ||
| t.join() | ||
| stop_server_thread(server) | ||
| def test_missing_resolvable(self): | ||
| url, t = new_server_thread(HandleResponseJSON02) | ||
| url, server = new_server_thread(HandleResponseJSON02) | ||
@@ -184,2 +202,2 @@ api = overpy.Overpass() | ||
| t.join() | ||
| stop_server_thread(server) |
+39
-0
@@ -172,1 +172,40 @@ import pytest | ||
| overpy.Way.from_xml(node) | ||
| class TestParser(BaseTestNodes): | ||
| def test_exception(self): | ||
| with pytest.raises(overpy.exception.OverPyException): | ||
| overpy.Result.from_xml(123) | ||
| def test_xml_element(self): | ||
| import xml.etree.ElementTree as ET | ||
| data = read_file("xml/node-01.xml") | ||
| root = ET.fromstring(data) | ||
| result = overpy.Result.from_xml(root) | ||
| assert isinstance(result, overpy.Result) | ||
| self._test_node01(result) | ||
| def test_xml_autodetect_parser(self): | ||
| data = read_file("xml/node-01.xml") | ||
| result = overpy.Result.from_xml(data) | ||
| assert isinstance(result, overpy.Result) | ||
| self._test_node01(result) | ||
| class TestRemark(object): | ||
| def test_remark_runtime_error(self): | ||
| api = overpy.Overpass() | ||
| with pytest.raises(overpy.exception.OverpassRuntimeError): | ||
| api.parse_xml(read_file("xml/remark-runtime-error-01.xml")) | ||
| def test_remark_runtime_remark(self): | ||
| api = overpy.Overpass() | ||
| with pytest.raises(overpy.exception.OverpassRuntimeRemark): | ||
| api.parse_xml(read_file("xml/remark-runtime-remark-01.xml")) | ||
| def test_remark_unknown(self): | ||
| api = overpy.Overpass() | ||
| with pytest.raises(overpy.exception.OverpassUnknownError): | ||
| api.parse_xml(read_file("xml/remark-unknown-01.xml")) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
255451
5.82%69
11.29%5769
5.12%