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
« 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#
13from __future__ import annotations
15import json
16from typing import Self, override
18import flask
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
29__all__ = ["BaseResponse", "AppResponse", "AppOkResponse", "AppErrorResponse"]
32class BaseResponse[TSchema: SchemaDict]:
33 """
34 Thin wrapper around a typed response dict.
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 """
41 def __init__(self, http_code: int, data: TSchema) -> None:
42 self._http_code = http_code
43 self._data = data
45 @classmethod
46 def ok_data(cls, data: TSchema) -> Self:
47 return cls(http_code=200, data=data)
49 @property
50 def http_code(self) -> int:
51 return self._http_code
53 def to_dict(self) -> TSchema:
54 """Return the typed payload dict — ready to hand back to Firebase."""
55 return self._data
57 def to_json(self) -> str:
58 return json.dumps(self._data, default=str)
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)
65 return flask.Response(self.to_json(), status=self._http_code, mimetype="application/json")
67 @override
68 def __repr__(self) -> str:
69 return f"{self.__class__.__name__}(http_code={self._http_code}, data={self._data})"
71 @override
72 def __str__(self) -> str:
73 return self.__repr__()
76class AppResponse[TSchema: SchemaDict](BaseResponse[TSchema]):
77 """For returning structured Models/Schemas."""
79 def __init__(self, data: TSchema, http_code: int = 200) -> None:
80 super().__init__(http_code=http_code, data=data)
83class AppOkResponse(BaseResponse[SuccessSchema]):
84 """For simple 'OK' or 'Action Completed' responses."""
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)
90 @classmethod
91 def ok(cls) -> Self:
92 return cls(message="OK")
94 @classmethod
95 def message(cls, message: str) -> Self:
96 return cls(message=message)
99class AppErrorResponse(BaseResponse[ErrorSchema]):
100 def __init__(self, data: ErrorSchema, http_code: int = 500) -> None:
101 super().__init__(http_code=http_code, data=data)
103 @classmethod
104 def from_context(cls, ctx: ErrorContext) -> Self:
105 return cls(data=ctx.to_dict(), http_code=ctx.http_code)
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)
118 @override
119 def to_dict(self) -> ErrorSchema:
120 return self._data