Coverage for functions \ flipdare \ core \ app_response.py: 91%

64 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-05-08 12:22 +1000

1#!/usr/bin/env python 

2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved. 

3# 

4# This file is part of Flipdare's proprietary software and contains 

5# confidential and copyrighted material. Unauthorised copying, 

6# modification, distribution, or use of this file is strictly 

7# prohibited without prior written permission from Flipdare Pty Ltd. 

8# 

9# This software includes third-party components licensed under MIT, 

10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details. 

11# 

12 

13from __future__ import annotations 

14 

15import json 

16from typing import Self, override 

17 

18import flask 

19 

20from flipdare.app_log import LOG 

21from flipdare.app_types import SchemaDict 

22from flipdare.constants import IS_TRACE 

23from flipdare.error.error_context import ErrorContext 

24from flipdare.generated.schema.error_schema import ErrorSchema 

25from flipdare.generated.schema.success_schema import SuccessSchema 

26from flipdare.generated.shared.app_error_code import AppErrorCode 

27from flipdare.util.debug_util import stringify_debug 

28 

29__all__ = ["BaseResponse", "AppResponse", "AppOkResponse", "AppErrorResponse"] 

30 

31 

32class BaseResponse[TSchema: SchemaDict]: 

33 """ 

34 Thin wrapper around a typed response dict. 

35 

36 Responses don't need validation or error accumulation — the data is already 

37 constructed and trusted. Subclasses override ``get_json`` only when they 

38 need to reshape the payload (nothing currently does). 

39 """ 

40 

41 def __init__(self, http_code: int, data: TSchema) -> None: 

42 self._http_code = http_code 

43 self._data = data 

44 

45 @classmethod 

46 def ok_data(cls, data: TSchema) -> Self: 

47 return cls(http_code=200, data=data) 

48 

49 @property 

50 def http_code(self) -> int: 

51 return self._http_code 

52 

53 def to_dict(self) -> TSchema: 

54 """Return the typed payload dict — ready to hand back to Firebase.""" 

55 return self._data 

56 

57 def to_json(self) -> str: 

58 return json.dumps(self._data, default=str) 

59 

60 def raw_response(self) -> flask.Response: 

61 if IS_TRACE: 

62 msg = f"Returning response with HTTP {self._http_code} and data: {stringify_debug(self._data)}" 

63 LOG().trace(msg) 

64 

65 return flask.Response(self.to_json(), status=self._http_code, mimetype="application/json") 

66 

67 @override 

68 def __repr__(self) -> str: 

69 return f"{self.__class__.__name__}(http_code={self._http_code}, data={self._data})" 

70 

71 @override 

72 def __str__(self) -> str: 

73 return self.__repr__() 

74 

75 

76class AppResponse[TSchema: SchemaDict](BaseResponse[TSchema]): 

77 """For returning structured Models/Schemas.""" 

78 

79 def __init__(self, data: TSchema, http_code: int = 200) -> None: 

80 super().__init__(http_code=http_code, data=data) 

81 

82 

83class AppOkResponse(BaseResponse[SuccessSchema]): 

84 """For simple 'OK' or 'Action Completed' responses.""" 

85 

86 def __init__(self, message: str, http_code: int = 200) -> None: 

87 data: SuccessSchema = {"message": message} 

88 super().__init__(http_code=http_code, data=data) 

89 

90 @classmethod 

91 def ok(cls) -> Self: 

92 return cls(message="OK") 

93 

94 @classmethod 

95 def message(cls, message: str) -> Self: 

96 return cls(message=message) 

97 

98 

99class AppErrorResponse(BaseResponse[ErrorSchema]): 

100 def __init__(self, data: ErrorSchema, http_code: int = 500) -> None: 

101 super().__init__(http_code=http_code, data=data) 

102 

103 @classmethod 

104 def from_context(cls, ctx: ErrorContext) -> Self: 

105 return cls(data=ctx.to_dict(), http_code=ctx.http_code) 

106 

107 @classmethod 

108 def message( 

109 cls, 

110 url: str, 

111 message: str, 

112 error_code: AppErrorCode = AppErrorCode.SERVER, 

113 error: Exception | None = None, 

114 ) -> Self: 

115 ctx = ErrorContext(endpoint=url, error_code=error_code, message=message, error=error) 

116 return cls.from_context(ctx) 

117 

118 @override 

119 def to_dict(self) -> ErrorSchema: 

120 return self._data