diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d5bb21..187d040 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.5 + rev: v0.6.7 hooks: - id: ruff args: [ --fix ] diff --git a/flask_utils/__init__.py b/flask_utils/__init__.py index 6ebb06f..00ccefd 100644 --- a/flask_utils/__init__.py +++ b/flask_utils/__init__.py @@ -1,5 +1,5 @@ # Increment versions here according to SemVer -__version__ = "0.8.0" +__version__ = "0.9.0" from flask_utils.utils import is_it_true from flask_utils.errors import GoneError @@ -10,6 +10,7 @@ from flask_utils.errors import UnauthorizedError from flask_utils.errors import WebServerIsDownError from flask_utils.errors import FailedDependencyError +from flask_utils.errors import MethodNotAllowedError from flask_utils.errors import ServiceUnavailableError from flask_utils.errors import OriginIsUnreachableError from flask_utils.errors import UnprocessableEntityError @@ -28,6 +29,7 @@ "GoneError", "UnprocessableEntityError", "ServiceUnavailableError", + "MethodNotAllowedError", "validate_params", "is_it_true", "FlaskUtils", diff --git a/flask_utils/errors/__init__.py b/flask_utils/errors/__init__.py index 85196f9..9b00128 100644 --- a/flask_utils/errors/__init__.py +++ b/flask_utils/errors/__init__.py @@ -9,6 +9,7 @@ from flask_utils.errors.unauthorized import UnauthorizedError from flask_utils.errors._error_template import _generate_error_response from flask_utils.errors.failed_dependency import FailedDependencyError +from flask_utils.errors.method_not_allowed import MethodNotAllowedError from flask_utils.errors.web_server_is_down import WebServerIsDownError from flask_utils.errors.service_unavailable import ServiceUnavailableError from flask_utils.errors.unprocessableentity import UnprocessableEntityError @@ -212,6 +213,20 @@ def generate_service_unavailable(error: ServiceUnavailableError) -> Response: return _generate_error_response(error) + @application.errorhandler(MethodNotAllowedError) + def generate_method_not_allowed(error: MethodNotAllowedError) -> Response: + """ + This is the 405 response creator. It will create a 405 response with + a custom message and the 405 code. + + :param error: The error body + :type error: MethodNotAllowedError + + :return: Returns the response formatted + :rtype: flask.Response + """ + return _generate_error_response(error) + __all__ = [ "BadRequestError", @@ -226,5 +241,6 @@ def generate_service_unavailable(error: ServiceUnavailableError) -> Response: "GoneError", "UnprocessableEntityError", "ServiceUnavailableError", + "MethodNotAllowedError", "_register_error_handlers", ] diff --git a/flask_utils/errors/method_not_allowed.py b/flask_utils/errors/method_not_allowed.py new file mode 100644 index 0000000..a93cad4 --- /dev/null +++ b/flask_utils/errors/method_not_allowed.py @@ -0,0 +1,51 @@ +from typing import Optional + +from flask_utils.errors.base_class import _BaseFlaskException + + +class MethodNotAllowedError(_BaseFlaskException): + """This is the MethodNotAllowedError exception class. + + When raised, it will return 405 status code with the message and solution provided. + + :param msg: The message to be displayed in the error. + :type msg: str + :param solution: The solution to the error. + :type solution: Optional[str] + + :Example: + + .. code-block:: python + + from flask_utils.errors import MethodNotAllowedError + + # Inside a Flask route + @app.route('/example', methods=['POST']) + def example_route(): + ... + if some_condition: + raise MethodNotAllowedError("This is a not allowed error.") + + The above code would return the following JSON response from Flask: + + .. code-block:: json + + { + "success": false, + "error": { + "type": "Method Not Allowed", + "name": "Method Not Allowed", + "message": "This is a Method Not Allowed error.", + "solution": "Try again." + }, + "code": 404 + } + + .. versionadded:: 0.1.0 + """ + + def __init__(self, msg: str, solution: Optional[str] = "Try again.") -> None: + self.name = "Method Not Allowed" + self.msg = msg + self.solution = solution + self.status_code = 405 diff --git a/tests/test_errors.py b/tests/test_errors.py index 8f26e2d..51e3fcf 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,5 +1,6 @@ import pytest +from flask_utils import MethodNotAllowedError from flask_utils.errors.gone import GoneError from flask_utils.errors.conflict import ConflictError from flask_utils.errors.notfound import NotFoundError @@ -59,6 +60,10 @@ def unprocessable_entity(): def service_unavailable(): raise ServiceUnavailableError("Service unavailable error") + @flask_client.get("/method_not_allowed") + def method_not_allowed(): + raise MethodNotAllowedError("Method not allowed error") + def test_bad_request_error_handler(client): response = client.get("/bad_request") @@ -179,3 +184,19 @@ def test_service_unavailable_error_handler(client): assert response_json["error"]["solution"] == "Try again later." assert response_json["error"]["name"] == "Service Unavailable" assert response_json["error"]["type"] == "ServiceUnavailableError" + + +def test_method_not_allowed_error_handler(client): + response = client.get("/method_not_allowed") + assert response.status_code == 405 + + response_json = response.get_json() + assert response_json["error"]["message"] == "Method not allowed error" + assert response_json["error"]["solution"] == "Try again." + assert response_json["error"]["name"] == "Method Not Allowed" + assert response_json["error"]["type"] == "MethodNotAllowedError" + + +def test_method_not_allowed_with_post_method(client): + response = client.post("/method_not_allowed") + assert response.status_code == 405 diff --git a/tests/test_generate_error_dict.py b/tests/test_generate_error_dict.py index 9d56ccc..259c1dc 100644 --- a/tests/test_generate_error_dict.py +++ b/tests/test_generate_error_dict.py @@ -1,5 +1,6 @@ import pytest +from flask_utils import MethodNotAllowedError from flask_utils.errors import GoneError from flask_utils.errors import ConflictError from flask_utils.errors import NotFoundError @@ -28,6 +29,7 @@ class TestGenerateErrorDict: ServiceUnavailableError("This is the message", "This is the solution"), OriginIsUnreachableError("This is the message", "This is the solution"), WebServerIsDownError("This is the message", "This is the solution"), + MethodNotAllowedError("This is the message", "This is the solution"), ], ) def test_generate_error_dict(self, error):