diff --git a/.github/workflows/CI_APIUtils.yml b/.github/workflows/CI_APIUtils.yml index 0cd8196..22af550 100644 --- a/.github/workflows/CI_APIUtils.yml +++ b/.github/workflows/CI_APIUtils.yml @@ -26,7 +26,7 @@ jobs: with_caching: false testbed_hello_local: - name: HelloAPI Testbed + name: HelloAPI Local Testbed needs: build uses: ./.github/workflows/TEST_HelloAPI_local.yml @@ -96,6 +96,6 @@ jobs: run: echo "Deployed to ${{ env.DEPLOY_URL }}" testbed_hello_remote: - name: APIUtils Testbed + name: HelloAPI Remote Testbed needs: deploy_helloapi uses: ./.github/workflows/TEST_HelloAPI_remote.yml diff --git a/.github/workflows/TEST_APIResponse.yml b/.github/workflows/TEST_APIResponse.yml index 6d45383..e3ad890 100644 --- a/.github/workflows/TEST_APIResponse.yml +++ b/.github/workflows/TEST_APIResponse.yml @@ -35,6 +35,8 @@ jobs: uses: opengamedata/setup-ogd-py-dependencies@v1.0 with: python_version: ${{ vars.OGD_PYTHON_VERSION }} + - name: Local self-install + run: python -m pip install -e . - name: Set up Config File uses: ./.github/actions/test_config with: @@ -45,7 +47,7 @@ jobs: # 3. Perform export - name: Execute testbed - run: python -m unittest discover -s tests/cases/utils -p "t_APIResponse.py" -t ./ + run: python -m unittest discover -s tests/cases/models -p "t_APIResponse.py" -t ./ # 4. Cleanup & complete - name: Upload logs as artifacts diff --git a/src/ogd/apis/HelloAPI.py b/src/ogd/apis/HelloAPI.py index 6e9f7fe..9a32825 100644 --- a/src/ogd/apis/HelloAPI.py +++ b/src/ogd/apis/HelloAPI.py @@ -14,7 +14,9 @@ # import locals from ogd.apis.configs.ServerConfig import ServerConfig -from ogd.apis.utils.APIResponse import APIResponse, RESTType, ResponseStatus +from ogd.apis.models.enums.ResponseStatus import ResponseStatus +from ogd.apis.models.enums.RESTType import RESTType +from ogd.apis.models.APIResponse import APIResponse class HelloAPI: @staticmethod @@ -32,7 +34,7 @@ def get(self): req_type = RESTType.GET, val = None, msg = "Hello! You GETted successfully!", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict def post(self): @@ -40,7 +42,7 @@ def post(self): req_type = RESTType.POST, val = None, msg = "Hello! You POSTed successfully!", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict def put(self): @@ -48,7 +50,7 @@ def put(self): req_type = RESTType.PUT, val = None, msg = "Hello! You PUTted successfully!", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict class ParamHello(Resource): @@ -57,7 +59,7 @@ def get(self, name): req_type = RESTType.GET, val = None, msg = f"Hello {name}! You GETted successfully!", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict def post(self, name): @@ -65,7 +67,7 @@ def post(self, name): req_type = RESTType.POST, val = None, msg = f"Hello {name}! You POSTed successfully!", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict def put(self, name): @@ -73,7 +75,7 @@ def put(self, name): req_type = RESTType.PUT, val = None, msg = f"Hello {name}! You PUTted successfully!", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict class Version(Resource): @@ -82,5 +84,5 @@ def get(self): req_type = RESTType.GET, val = { "version" : str(HelloAPI.server_config.Version) }, msg = f"Successfully retrieved API version.", - status = ResponseStatus.SUCCESS) + status = ResponseStatus.OK) return ret_val.AsDict diff --git a/src/ogd/apis/utils/APIResponse.py b/src/ogd/apis/models/APIResponse.py similarity index 71% rename from src/ogd/apis/utils/APIResponse.py rename to src/ogd/apis/models/APIResponse.py index 79f642e..7e9b143 100644 --- a/src/ogd/apis/utils/APIResponse.py +++ b/src/ogd/apis/models/APIResponse.py @@ -7,8 +7,7 @@ # import standard libraries import json -from enum import IntEnum -from typing import Any, Dict, Optional, Set +from typing import Any, Dict, Optional # import 3rd-party libraries from flask import Response @@ -18,64 +17,8 @@ import ogd.core.requests.RequestResult as RequestResult # Import local files - -class RESTType(IntEnum): - """Simple enumerated type to track type of a REST request. - """ - GET = 1 - POST = 2 - PUT = 3 - - def __str__(self): - """Stringify function for RESTTypes. - - :return: Simple string version of the name of a RESTType - :rtype: _type_ - """ - match self.value: - case RESTType.GET: - return "GET" - case RESTType.POST: - return "POST" - case RESTType.PUT: - return "PUT" - case _: - return "INVALID REST TYPE" - -class ResponseStatus(IntEnum): - """Simple enumerated type to track the status of an API request result. - """ - NONE = 1 - SUCCESS = 200 - ERR_REQ = 400 - ERR_NOTFOUND = 404 - ERR_SRV = 500 - - @staticmethod - def ServerErrors() -> Set["ResponseStatus"]: - return {ResponseStatus.ERR_SRV} - - @staticmethod - def ClientErrors() -> Set["ResponseStatus"]: - return {ResponseStatus.ERR_REQ, ResponseStatus.ERR_NOTFOUND} - - def __str__(self): - """Stringify function for ResponseStatus objects. - - :return: Simple string version of the name of a ResponseStatus - :rtype: _type_ - """ - match self.value: - case ResponseStatus.NONE: - return "NONE" - case ResponseStatus.SUCCESS: - return "SUCCESS" - case ResponseStatus.ERR_SRV: - return "SERVER ERROR" - case ResponseStatus.ERR_REQ: - return "REQUEST ERROR" - case _: - return "INVALID STATUS TYPE" +from ogd.apis.models.enums.RESTType import RESTType +from ogd.apis.models.enums.ResponseStatus import ResponseStatus class APIResponse: def __init__(self, req_type:Optional[RESTType], val:Optional[Map], msg:str, status:ResponseStatus): @@ -113,11 +56,11 @@ def FromRequestResult(result:RequestResult.RequestResult, req_type:RESTType) -> _status : ResponseStatus match result.Status: case RequestResult.ResultStatus.SUCCESS: - _status = ResponseStatus.SUCCESS + _status = ResponseStatus.OK case RequestResult.ResultStatus.FAILURE: - _status = ResponseStatus.ERR_REQ + _status = ResponseStatus.BAD_REQUEST case _: - _status = ResponseStatus.ERR_SRV + _status = ResponseStatus.INTERNAL_ERR ret_val = APIResponse(req_type=req_type, val={"session_count":result.SessionCount, "duration":str(result.Duration)}, msg=result.Message, status=_status) return ret_val @@ -199,14 +142,14 @@ def AsFlaskResponse(self) -> Response: return Response(response=self.AsJSON, status=self.Status.value, mimetype='application/json') def RequestErrored(self, msg:str, status:Optional[ResponseStatus]=None): - self._status = status if status is not None and status in ResponseStatus.ClientErrors() else ResponseStatus.ERR_REQ + self._status = status if status is not None and status in ResponseStatus.ClientErrors() else ResponseStatus.BAD_REQUEST self.Message = f"ERROR: {msg}" def ServerErrored(self, msg:str, status:Optional[ResponseStatus]=None): - self._status = status if status is not None and status in ResponseStatus.ServerErrors() else ResponseStatus.ERR_SRV + self._status = status if status is not None and status in ResponseStatus.ServerErrors() else ResponseStatus.INTERNAL_ERR self.Message = f"SERVER ERROR: {msg}" def RequestSucceeded(self, msg:str, val:Optional[Map]): - self._status = ResponseStatus.SUCCESS + self._status = ResponseStatus.OK self.Message = f"SUCCESS: {msg}" self.Value = val diff --git a/src/ogd/apis/models/__init__.py b/src/ogd/apis/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ogd/apis/models/enums/RESTType.py b/src/ogd/apis/models/enums/RESTType.py new file mode 100644 index 0000000..0a9b539 --- /dev/null +++ b/src/ogd/apis/models/enums/RESTType.py @@ -0,0 +1,25 @@ +from enum import IntEnum +from typing import Set + +class RESTType(IntEnum): + """Simple enumerated type to track type of a REST request. + """ + GET = 1 + POST = 2 + PUT = 3 + + def __str__(self): + """Stringify function for RESTTypes. + + :return: Simple string version of the name of a RESTType + :rtype: _type_ + """ + match self.value: + case RESTType.GET: + return "GET" + case RESTType.POST: + return "POST" + case RESTType.PUT: + return "PUT" + case _: + return "INVALID REST TYPE" \ No newline at end of file diff --git a/src/ogd/apis/models/enums/ResponseStatus.py b/src/ogd/apis/models/enums/ResponseStatus.py new file mode 100644 index 0000000..4d7783e --- /dev/null +++ b/src/ogd/apis/models/enums/ResponseStatus.py @@ -0,0 +1,98 @@ +from enum import IntEnum +from typing import Set + +class ResponseStatus(IntEnum): + """Enumerated type to track the status of an API request result. + """ + NONE = 1 + CONTINUE = 100 + OK = 200 + MULTI_CHOICES = 300 + BAD_REQUEST = 400 + INTERNAL_ERR = 500 + + # 100s + SWITCHING_PROTOCOLS = 101 + PROCESSING = 102 + EARLY_HINTS = 103 + + # 200s + CREATED = 201 + ACCEPTED = 202 + NON_AUTHORITATIVE = 203 + NO_CONTENT = 204 + RESET = 205 + PARTIAL = 206 + MULTI_STATUS = 207 + ALREADY_REPORTED = 208 + IM_USED = 226 + + # 300s + MOVED = 301 + FOUND = 302 + SEE_OTHER = 303 + NOT_MODIFIED = 304 + TEMPORARY_REDIR = 307 + PERMANENT_REDIR = 308 + + # 400s + UNAUTORIZED = 401 + PAYMENT_REQUIRED = 402 + FORBIDDEN = 403 + NOT_FOUND = 404 + METHOD_NOT_ALLOWED = 405 + NOT_ACCEPTABLE = 406 + PROXY_AUTH_REQUIRED = 407 + REQUEST_TIMEOUT = 408 + CONFLICT = 409 + GONE = 410 + LENGTH_REQUIRED = 411 + PRECONDITION_FAILED = 412 + CONENT_TOO_LARGE = 413 + URI_TOO_LONG = 414 + UNSUPPORTED_MEDIA = 415 + RANGE_INVALID = 416 + EXPECTATION_FAIL = 417 + IM_A_TEAPOT = 418 + MISDIRECTED = 421 + TOO_EARLY = 425 + UPGRADE_REQUIRED = 426 + PRECONDITION_REQUIRED = 428 + TOO_MANY_REQUESTS = 429 + HEADERS_TOO_LARGE = 431 + ILLEGAL = 451 + + # 400s WebDAV + UNPROCESSABLE = 422 + LOCKED = 423 + FAILED_DEPENDENCY = 424 + + # 500s + NOT_IMPLEMENTED = 501 + BAD_GATEWAY = 502 + UNAVAILABLE = 503 + GATEWAY_TIMEOUT = 504 + UNSUPPORTED_HTTP_VERSION = 505 + VARIANT_NEGOTIATES = 506 + NOT_EXTENDED = 510 + NETWORK_AUTH_REQUIRED = 511 + + # 500s WebDAV + INSUFFICIENT_STORAGE = 507 + LOOP_DETECTED = 508 + + @staticmethod + def ClientErrors() -> Set["ResponseStatus"]: + return {status for status in set(ResponseStatus) if status in range(400, 499)} + + @staticmethod + def ServerErrors() -> Set["ResponseStatus"]: + return {status for status in set(ResponseStatus) if status in range(500, 599)} + + def __str__(self): + """Stringify function for ResponseStatus objects. + + :return: Simple string version of the name of a ResponseStatus + :rtype: _type_ + """ + return self.name diff --git a/src/ogd/apis/models/enums/__init__.py b/src/ogd/apis/models/enums/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/models/__init__.py b/tests/cases/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/utils/t_APIResponse.py b/tests/cases/models/t_APIResponse.py similarity index 80% rename from tests/cases/utils/t_APIResponse.py rename to tests/cases/models/t_APIResponse.py index 017f7a0..51ad3d1 100644 --- a/tests/cases/utils/t_APIResponse.py +++ b/tests/cases/models/t_APIResponse.py @@ -8,7 +8,9 @@ from ogd.common.configs.TestConfig import TestConfig from ogd.common.utils.Logger import Logger # import locals -from src.ogd.apis.utils.APIResponse import APIResponse, RESTType, ResponseStatus +from src.ogd.apis.models.enums.ResponseStatus import ResponseStatus +from src.ogd.apis.models.enums.RESTType import RESTType +from src.ogd.apis.models.APIResponse import APIResponse from tests.config.t_config import settings @@ -20,7 +22,7 @@ def setUpClass(cls) -> None: Logger.InitializeLogger(level=_level, use_logfile=False) def setUp(self): - self.response : APIResponse = APIResponse(req_type=RESTType.GET, val={"foo":"bar"}, msg="Complete", status=ResponseStatus.SUCCESS) + self.response : APIResponse = APIResponse(req_type=RESTType.GET, val={"foo":"bar"}, msg="Complete", status=ResponseStatus.OK) @unittest.skip("Not yet implemented") def test_FromRequestResult(self): @@ -28,12 +30,12 @@ def test_FromRequestResult(self): # TODO : we could just do a different setUp(...) function in a sub-class of t_APIResponse, I think, instead of repeating every line of every property test. def test_FromFromDict_recall(self): - _response = APIResponse.FromDict(self.response.AsDict, status=ResponseStatus.SUCCESS) + _response = APIResponse.FromDict(self.response.AsDict, status=ResponseStatus.OK) if _response: self.assertEqual(_response.Type, RESTType.GET) self.assertEqual(_response.Value, {"foo":"bar"}) self.assertEqual(_response.Message, "Complete") - self.assertEqual(_response.Status, ResponseStatus.SUCCESS) + self.assertEqual(_response.Status, ResponseStatus.OK) else: self.fail("_response was None, failure in parsing FromDict!") @@ -57,7 +59,7 @@ def test_Message(self): self.assertEqual(self.response.Message, "Complete") def test_Status(self): - self.assertEqual(self.response.Status, ResponseStatus.SUCCESS) + self.assertEqual(self.response.Status, ResponseStatus.OK) def test_AsDict(self): expected = { @@ -85,42 +87,42 @@ def test_AsFlaskResponse(self): def test_RequestErrored_default_status(self): self.response.RequestErrored("Default request error") self.assertEqual(self.response.Message, "ERROR: Default request error") - self.assertEqual(self.response.Status, ResponseStatus.ERR_REQ) + self.assertEqual(self.response.Status, ResponseStatus.BAD_REQUEST) def test_RequestErrored_general_status(self): - self.response.RequestErrored("General request error", ResponseStatus.ERR_REQ) + self.response.RequestErrored("General request error", ResponseStatus.BAD_REQUEST) self.assertEqual(self.response.Message, "ERROR: General request error") - self.assertEqual(self.response.Status, ResponseStatus.ERR_REQ) + self.assertEqual(self.response.Status, ResponseStatus.BAD_REQUEST) def test_RequestErrored_notfound_status(self): - self.response.RequestErrored("404 request error", ResponseStatus.ERR_NOTFOUND) + self.response.RequestErrored("404 request error", ResponseStatus.NOT_FOUND) self.assertEqual(self.response.Message, "ERROR: 404 request error") - self.assertEqual(self.response.Status, ResponseStatus.ERR_NOTFOUND) + self.assertEqual(self.response.Status, ResponseStatus.NOT_FOUND) def test_RequestErrored_invalid_status(self): - self.response.RequestErrored("Invalid choice of code for request error, should give default error code", ResponseStatus.ERR_SRV) + self.response.RequestErrored("Invalid choice of code for request error, should give default error code", ResponseStatus.INTERNAL_ERR) self.assertEqual(self.response.Message, "ERROR: Invalid choice of code for request error, should give default error code") - self.assertEqual(self.response.Status, ResponseStatus.ERR_REQ) + self.assertEqual(self.response.Status, ResponseStatus.BAD_REQUEST) def test_ServerErrored_default_status(self): self.response.ServerErrored("Default server error") self.assertEqual(self.response.Message, "SERVER ERROR: Default server error") - self.assertEqual(self.response.Status, ResponseStatus.ERR_SRV) + self.assertEqual(self.response.Status, ResponseStatus.INTERNAL_ERR) def test_ServerErrored_general_status(self): self.response.ServerErrored("General server error") - self.assertEqual(self.response.Message, "SERVER ERROR: General server error", ResponseStatus.ERR_SRV) - self.assertEqual(self.response.Status, ResponseStatus.ERR_SRV) + self.assertEqual(self.response.Message, "SERVER ERROR: General server error", ResponseStatus.INTERNAL_ERR) + self.assertEqual(self.response.Status, ResponseStatus.INTERNAL_ERR) def test_ServerErrored_invalid_status(self): - self.response.ServerErrored("Invalid choice of code for server error, should give default error code", ResponseStatus.ERR_REQ) + self.response.ServerErrored("Invalid choice of code for server error, should give default error code", ResponseStatus.BAD_REQUEST) self.assertEqual(self.response.Message, "SERVER ERROR: Invalid choice of code for server error, should give default error code") - self.assertEqual(self.response.Status, ResponseStatus.ERR_SRV) + self.assertEqual(self.response.Status, ResponseStatus.INTERNAL_ERR) def test_RequestSucceeded(self): self.response.RequestSucceeded(msg="Default server success", val={"foo":"bar"}) self.assertEqual(self.response.Message, "SUCCESS: Default server success") - self.assertEqual(self.response.Status, ResponseStatus.SUCCESS) + self.assertEqual(self.response.Status, ResponseStatus.OK) self.assertEqual(self.response.Value, {"foo":"bar"}) if __name__ == '__main__':