From 83868015ffbea8a781175766a1c03f5392acad5d Mon Sep 17 00:00:00 2001 From: Leo Kirchner Date: Fri, 6 Jan 2023 09:06:57 +0100 Subject: [PATCH] Add http_timeout configuration setting to configure timeout when getting document via HTTP. --- README.md | 4 ++++ openapi_python_client/__init__.py | 6 +++--- openapi_python_client/config.py | 1 + tests/test___init__.py | 30 +++++++++++++++++------------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 4baa93c9e..51c5cb4d6 100644 --- a/README.md +++ b/README.md @@ -165,5 +165,9 @@ If you are carefully curating your `title` properties already to ensure no dupli If this option results in conflicts, you will need to manually override class names instead via the `class_overrides` option. +### http_timeout + +By default, the timeout for retrieving the schema file via HTTP is 5 seconds. In case there is an error when retrieving the schema, you might try and increase this setting to a higher value. + [changelog.md]: CHANGELOG.md [poetry]: https://python-poetry.org/ diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index ff0a132ca..2d9b2d27d 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -311,7 +311,7 @@ def _get_project_for_url_or_path( # pylint: disable=too-many-arguments custom_template_path: Optional[Path] = None, file_encoding: str = "utf-8", ) -> Union[Project, GeneratorError]: - data_dict = _get_document(url=url, path=path) + data_dict = _get_document(url=url, path=path, timeout=config.http_timeout) if isinstance(data_dict, GeneratorError): return data_dict openapi = GeneratorData.from_dict(data_dict, config=config) @@ -395,14 +395,14 @@ def _load_yaml_or_json(data: bytes, content_type: Optional[str]) -> Union[Dict[s return GeneratorError(header=f"Invalid YAML from provided source: {err}") -def _get_document(*, url: Optional[str], path: Optional[Path]) -> Union[Dict[str, Any], GeneratorError]: +def _get_document(*, url: Optional[str], path: Optional[Path], timeout: int) -> Union[Dict[str, Any], GeneratorError]: yaml_bytes: bytes content_type: Optional[str] if url is not None and path is not None: return GeneratorError(header="Provide URL or Path, not both.") if url is not None: try: - response = httpx.get(url) + response = httpx.get(url, timeout=timeout) yaml_bytes = response.content if "content-type" in response.headers: content_type = response.headers["content-type"].split(";")[0] diff --git a/openapi_python_client/config.py b/openapi_python_client/config.py index d63d708db..afca35758 100644 --- a/openapi_python_client/config.py +++ b/openapi_python_client/config.py @@ -34,6 +34,7 @@ class Config(BaseModel): "black .", ] field_prefix: str = "field_" + http_timeout: int = 5 @staticmethod def load_from_path(path: Path) -> "Config": diff --git a/tests/test___init__.py b/tests/test___init__.py index 85da7b18b..fcf767cc9 100644 --- a/tests/test___init__.py +++ b/tests/test___init__.py @@ -7,6 +7,8 @@ from openapi_python_client import Config, ErrorLevel, GeneratorError, Project +default_http_timeout = Config.schema()["properties"]["http_timeout"]["default"] + def test__get_project_for_url_or_path(mocker): data_dict = mocker.MagicMock() @@ -17,12 +19,13 @@ def test__get_project_for_url_or_path(mocker): url = mocker.MagicMock() path = mocker.MagicMock() config = mocker.MagicMock() + config.http_timeout = default_http_timeout from openapi_python_client import MetaType, _get_project_for_url_or_path project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config) - _get_document.assert_called_once_with(url=url, path=path) + _get_document.assert_called_once_with(url=url, path=path, timeout=default_http_timeout) from_dict.assert_called_once_with(data_dict, config=config) _Project.assert_called_once_with( openapi=openapi, custom_template_path=None, meta=MetaType.POETRY, file_encoding="utf-8", config=config @@ -39,12 +42,13 @@ def test__get_project_for_url_or_path_generator_error(mocker): url = mocker.MagicMock() path = mocker.MagicMock() config = mocker.MagicMock() + config.http_timeout = default_http_timeout from openapi_python_client import MetaType, _get_project_for_url_or_path project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=config) - _get_document.assert_called_once_with(url=url, path=path) + _get_document.assert_called_once_with(url=url, path=path, timeout=default_http_timeout) from_dict.assert_called_once_with(data_dict, config=config) _Project.assert_not_called() assert project == error @@ -62,7 +66,7 @@ def test__get_project_for_url_or_path_document_error(mocker): project = _get_project_for_url_or_path(url=url, path=path, meta=MetaType.POETRY, config=Config()) - _get_document.assert_called_once_with(url=url, path=path) + _get_document.assert_called_once_with(url=url, path=path, timeout=default_http_timeout) from_dict.assert_not_called() assert project == error @@ -153,7 +157,7 @@ def test__get_document_no_url_or_path(self, mocker): from openapi_python_client import _get_document - result = _get_document(url=None, path=None) + result = _get_document(url=None, path=None, timeout=default_http_timeout) assert result == GeneratorError(header="No URL or Path provided") get.assert_not_called() @@ -167,7 +171,7 @@ def test__get_document_url_and_path(self, mocker): from openapi_python_client import _get_document - result = _get_document(url=mocker.MagicMock(), path=mocker.MagicMock()) + result = _get_document(url=mocker.MagicMock(), path=mocker.MagicMock(), timeout=default_http_timeout) assert result == GeneratorError(header="Provide URL or Path, not both.") get.assert_not_called() @@ -182,10 +186,10 @@ def test__get_document_bad_url(self, mocker): from openapi_python_client import _get_document url = mocker.MagicMock() - result = _get_document(url=url, path=None) + result = _get_document(url=url, path=None, timeout=default_http_timeout) assert result == GeneratorError(header="Could not get OpenAPI document from provided URL") - get.assert_called_once_with(url) + get.assert_called_once_with(url, timeout=default_http_timeout) _Path.assert_not_called() loads.assert_not_called() @@ -197,9 +201,9 @@ def test__get_document_url_no_path(self, mocker): from openapi_python_client import _get_document url = "test" - _get_document(url=url, path=None) + _get_document(url=url, path=None, timeout=default_http_timeout) - get.assert_called_once_with(url) + get.assert_called_once_with(url, timeout=default_http_timeout) _Path.assert_not_called() loads.assert_called_once_with(get().content) @@ -211,7 +215,7 @@ def test__get_document_path_no_url(self, tmp_path, mocker): from openapi_python_client import _get_document - _get_document(url=None, path=path) + _get_document(url=None, path=path, timeout=default_http_timeout) get.assert_not_called() loads.assert_called_once_with(b"some test data") @@ -222,7 +226,7 @@ def test__get_document_bad_yaml(self, mocker, tmp_path): path = tmp_path / "test.yaml" path.write_text("'") - result = _get_document(url=None, path=path) + result = _get_document(url=None, path=path, timeout=default_http_timeout) get.assert_not_called() assert isinstance(result, GeneratorError) @@ -241,7 +245,7 @@ class FakeResponse: from openapi_python_client import _get_document url = mocker.MagicMock() - result = _get_document(url=url, path=None) + result = _get_document(url=url, path=None, timeout=default_http_timeout) get.assert_called_once() json_loads.assert_called_once_with(FakeResponse.content.decode()) @@ -258,7 +262,7 @@ class FakeResponse: from openapi_python_client import _get_document url = mocker.MagicMock() - result = _get_document(url=url, path=None) + result = _get_document(url=url, path=None, timeout=default_http_timeout) get.assert_called_once() assert result == GeneratorError(