Cat Feeder  1.0.0
The Cat feeder project
Loading...
Searching...
No Matches
favicon_admin.py
Go to the documentation of this file.
1r"""
2# +==== BEGIN CatFeeder =================+
3# LOGO:
4# ..............(..../\
5# ...............)..(.')
6# ..............(../..)
7# ...............\‍(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
10# animals/cats
11# /STOP
12# PROJECT: CatFeeder
13# FILE: favicon_admin.py
14# CREATION DATE: 05-01-2026
15# LAST Modified: 1:39:39 13-01-2026
16# DESCRIPTION:
17# This is the backend server in charge of making the actual website work.
18# /STOP
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: This is the handler for the admin favicons.
21# // AR
22# +==== END CatFeeder =================+
23"""
24
25from typing import TYPE_CHECKING, Optional, List, Union, Dict, Any
26
27from fastapi import Response, UploadFile
28from starlette.datastructures import UploadFile as StarletteUploadFile
29from display_tty import Disp, initialise_logger
30
31from . import favicon_constants as FAV_CONST
32from . import favicon_helpers as FAV_HELPERS
33from . import favicon_error_class as FAV_ERR
34
35from ..core import FinalSingleton
36from ..core.runtime_manager import RI, RuntimeManager
37from ..image_reducer import FileFormat
38from ..http_codes import HCI, HttpDataTypes
39from ..utils import CONST
40
41if TYPE_CHECKING:
42 from ..sql import SQL
43 from ..bucket import Bucket
44 from ..image_reducer import ImageReducer
45 from ..server_header import ServerHeaders
46 from ..boilerplates.responses import BoilerplateResponses
47
48
50
51 # --------------------------------------------------------------------------
52 # STATIC CLASS VALUES
53 # --------------------------------------------------------------------------
54
55 # -------------- Initialise the logger globally in the class. --------------
56 disp: Disp = initialise_logger(__qualname__, False)
57
58 # --------------------------------------------------------------------------
59 # CONSTRUCTOR & DESTRUCTOR
60 # --------------------------------------------------------------------------
61
62 def __init__(self, success: int = 0, error: int = 84, debug: bool = False) -> None:
63 """Initialize the ImageReducer instance with dependencies.
64
65 Keyword Arguments:
66 debug: Enable debug logging when True. Defaults to False.
67 """
68 # ------------------------ The logging function ------------------------
69 self.disp.update_disp_debug(debug)
70 self.success = success
71 self.error = error
72 self.disp.log_debug("Initialising...")
73 self.runtime_manager: RuntimeManager = RI
74 self.boilerplate_response: "BoilerplateResponses" = self.runtime_manager.get(
75 "BoilerplateResponses")
76 self.server_header: "ServerHeaders" = self.runtime_manager.get(
77 "ServerHeaders")
78 self.bucket: "Bucket" = self.runtime_manager.get("Bucket")
79 self.sql: "SQL" = self.runtime_manager.get("SQL")
80 self.image_reducerimage_reducer: Optional["ImageReducer"] = self.runtime_manager.get_if_exists(
81 "ImageReducer", None)
82 self.disp.log_debug("Initialised")
83
84 def _no_user_id(self, title: str, token: Optional[str] = None) -> Response:
85 """Return a standardized response when no user id is provided.
86
87 Logs an error and returns the `user_not_found` response constructed by
88 the configured boilerplate response helper.
89
90 Args:
91 title (str): Title to include in the response context.
92 token (Optional[str]): Optional token related to the request.
93
94 Returns:
95 Response: A FastAPI response produced by `BoilerplateResponses`.
96 """
97 self.disp.log_error("There is no specified user id.")
98 return self.boilerplate_response.user_not_found(title, token)
99
100 def _no_data(self, title: str, token: Optional[str] = None) -> Response:
101 """Return a 404 JSON response when no favicon data exists.
102
103 Builds a standardized error body using the boilerplate response helper
104 and returns an HTTP 404 response with appropriate JSON headers.
105
106 Args:
107 title (str): Title to include in the response context.
108 token (Optional[str]): Optional token related to the request.
109
110 Returns:
111 Response: A FastAPI response with HTTP 404 and JSON body.
112 """
113 self.disp.log_error("There is no data available.")
114 body = self.boilerplate_response.build_response_body(
115 title=title,
116 message="No icon available",
117 resp="No data",
118 token=token,
119 error=True
120 )
121 return HCI.not_found(
122 content=body,
123 content_type=HttpDataTypes.JSON,
124 headers=self.server_header.for_json()
125 )
126
127 def _populate_ids(self, data: Dict[str, Any]) -> Dict[str, Any]:
128 """Replace numeric id fields in a favicon data dict with full records.
129
130 Mutates the provided `data` mapping by replacing `type`, `gender` and
131 `season` fields (when present) with the corresponding full records from
132 the database.
133
134 Args:
135 data (Dict[str, Any]): The raw favicon data containing id fields.
136
137 Returns:
138 Dict[str, Any]: The mutated mapping with populated fields.
139 """
140 data["type"] = self.get_favicon_type(data.get("type", -1))
141 data["gender"] = self.get_favicon_gender(data.get("gender", -1))
142 data["season"] = self.get_favicon_season(data.get("season", -1))
143 return data
144
145 def _process_favicon_type_id(self, ftype: Optional[Union[int, str]] = None) -> Optional[int]:
146 """Normalize a favicon type identifier to a numeric id.
147
148 Accepts either an integer id or a string name/id and returns the
149 corresponding integer id when found, otherwise `None`.
150
151 Args:
152 ftype (Optional[Union[int, str]]): The id or name to resolve.
153
154 Returns:
155 Optional[int]: The resolved integer id or `None` if not found.
156 """
157 if not isinstance(ftype, (int, str)):
158 return None
159 data = self.list_favicon_type()
160 ftype_str = str(ftype)
161 for item in data:
162 fid = item.get("id")
163 if str(fid) == ftype_str:
164 return fid
165 if item.get("name") == str(ftype):
166 return fid
167 return None
168
169 def _process_favicon_gender_id(self, gender: Optional[Union[int, str]] = None) -> Optional[int]:
170 """Normalize a favicon gender identifier to a numeric id.
171
172 Accepts either an integer id or a string name/id and returns the
173 corresponding integer id when found, otherwise `None`.
174
175 Args:
176 gender (Optional[Union[int, str]]): The id or name to resolve.
177
178 Returns:
179 Optional[int]: The resolved integer id or `None` if not found.
180 """
181 if not isinstance(gender, (int, str)):
182 return None
183 data = self.list_favicon_type()
184 gender_str = str(gender)
185 for item in data:
186 gid = item.get("id")
187 if str(gid) == gender_str:
188 return gid
189 if item.get("gender") == str(gender):
190 return gid
191 return None
192
193 def _process_favicon_season_id(self, ftype: Optional[Union[int, str]] = None) -> Optional[int]:
194 """Normalize a favicon season identifier to a numeric id.
195
196 Accepts either an integer id or a string name/id and returns the
197 corresponding integer id when found, otherwise `None`.
198
199 Args:
200 ftype (Optional[Union[int, str]]): The id or name to resolve.
201
202 Returns:
203 Optional[int]: The resolved integer id or `None` if not found.
204 """
205 if not isinstance(ftype, (int, str)):
206 return None
207 data = self.list_favicon_type()
208 ftype_str = str(ftype)
209 for item in data:
210 fid = item.get("id")
211 if str(fid) == ftype_str:
212 return fid
213 if item.get("season") == str(ftype):
214 return fid
215 return None
216
217 def _upload_bytes(self, image_data: bytes, icon_id: int, *, title: str = "_upload_bytes") -> str:
218 """Upload image bytes to the configured bucket.
219
220 This is a placeholder for the actual implementation of the image
221 upload logic.
222 """
224 "ImageReducer", self.image_reducerimage_reducer)
225 if self.image_reducerimage_reducer is None:
226 self.disp.log_error(
227 "There is no ImageReducer instance available in the runtime manager", title
228 )
229 raise FAV_ERR.FaviconNoImageReducerError(
230 "ImageReducer instance not found in RuntimeManager"
231 )
232 file_format = self.image_reducerimage_reducer.detect_file_format(
233 image_data).name.lower()
234 img_path = FAV_HELPERS.generate_image_path(
235 f"{icon_id}.{file_format}", str(icon_id)
236 )
237 bucket_status = self.bucket.upload_stream(
238 bucket_name=FAV_CONST.FAVICON_BUCKET_NAME,
239 key_name=img_path,
240 data=image_data
241 )
242 if isinstance(bucket_status, int) and bucket_status != self.success:
243 err_msg = f"Failed to upload favicon image data to bucket '{FAV_CONST.FAVICON_BUCKET_NAME}' at path '{img_path}'"
244 self.disp.log_error(err_msg)
245 raise FAV_ERR.FaviconImageUploadError(err_msg)
246 return img_path
247
248 def _upload_file(self, image_data: UploadFile, icon_id: int, *, title: str = "_upload_bytes") -> str:
249 """Upload an UploadFile to the configured bucket.
250
251 This is a placeholder for the actual implementation of the image
252 upload logic.
253 """
254 self.image_reducerimage_reducer = self.runtime_manager.get_if_exists(
255 "ImageReducer", self.image_reducerimage_reducer)
256 if self.image_reducerimage_reducer is None:
257 self.disp.log_error(
258 "There is no ImageReducer instance available in the runtime manager", title
259 )
260 raise FAV_ERR.FaviconNoImageReducerError(
261 "ImageReducer instance not found in RuntimeManager"
262 )
263 file_data = image_data.file.read()
264 file_name = image_data.filename
265 image_data.file.close()
266 file_format = self.image_reducerimage_reducer.detect_file_format(
267 file_data).name.lower()
268 img_path = FAV_HELPERS.generate_image_path(
269 f"{file_name}.{file_format}", str(icon_id)
270 )
271 bucket_status = self.bucket.upload_stream(
272 bucket_name=FAV_CONST.FAVICON_BUCKET_NAME,
273 key_name=img_path,
274 data=file_data
275 )
276 if isinstance(bucket_status, int) and bucket_status != self.success:
277 err_msg = f"Failed to upload favicon image data to bucket '{FAV_CONST.FAVICON_BUCKET_NAME}' at path '{img_path}'"
278 self.disp.log_error(err_msg)
279 raise FAV_ERR.FaviconImageUploadError(err_msg)
280 return img_path
281
282 def reducer_type_to_data_type(self, reducer_type: FileFormat) -> HttpDataTypes:
283 """(This is a wrapper of the same function in the constants)
284 Convert an ImageReducer FileFormat to an HttpDataTypes value.
285
286 Args:
287 reducer_type (IR_CONST.FileFormat): The image reducer file format.
288
289 Returns:
290 HttpDataTypes: The corresponding HTTP data type.
291 """
292 return FAV_HELPERS.reducer_type_to_data_type(reducer_type)
293
294 def list_favicon_gender(self) -> List[Dict[str, Any]]:
295 """List all favicon genders from the database.
296
297 Returns a list of dictionary records representing available genders.
298
299 Returns:
300 List[Dict[str, Any]]: The gender records.
301 """
302 table: str = FAV_CONST.FAVICON_TABLE_GENDER
303 title = "list_favicon_gender:list_from_table"
304 return FAV_HELPERS.list_from_table(self.sql, table, title=title, disp=self.disp)
305
306 def list_favicon_season(self) -> List[Dict[str, Any]]:
307 """List all favicon seasons from the database.
308
309 Returns a list of dictionary records representing available seasons.
310
311 Returns:
312 List[Dict[str, Any]]: The season records.
313 """
314 table: str = FAV_CONST.FAVICON_TABLE_SEASON
315 title = "list_favicon_season:list_from_table"
316 return FAV_HELPERS.list_from_table(self.sql, table, title=title, disp=self.disp)
317
318 def list_favicon_type(self) -> List[Dict[str, Any]]:
319 """List all favicon types from the database.
320
321 Returns a list of dictionary records representing available types.
322
323 Returns:
324 List[Dict[str, Any]]: The type records.
325 """
326 table: str = FAV_CONST.FAVICON_TABLE_TYPE
327 title = "list_favicon_type:list_from_table"
328 return FAV_HELPERS.list_from_table(self.sql, table, title=title, disp=self.disp)
329
330 def list_favicons(self) -> List[Dict[str, Any]]:
331 """Return list of favicons with resolved type/gender/season fields.
332
333 Fetches raw favicon records from the main table and replaces numeric
334 `type`, `gender` and `season` identifiers with full records resolved
335 via the helper list/get functions.
336
337 Returns:
338 List[Dict[str, Any]]: The favicon records with populated fields.
339 """
340 table: str = FAV_CONST.FAVICON_TABLE_MAIN
341 title = "list_favicons:list_from_table"
342 favicon = FAV_HELPERS.list_from_table(
343 self.sql, table, title=title, disp=self.disp)
344 if len(favicon) == 0:
345 self.disp.log_debug("No favicons available.")
346 return favicon
347 f_type = self.list_favicon_type()
348 f_gender = self.list_favicon_gender()
349 f_season = self.list_favicon_season()
350 for icon in favicon:
351 icon["type"] = FAV_HELPERS.extract_line_from_id(
352 data_list=f_type, entry_id=icon.get("type", -1), disp=self.disp
353 )
354 icon["gender"] = FAV_HELPERS.extract_line_from_id(
355 data_list=f_gender, entry_id=icon.get("gender", -1), disp=self.disp
356 )
357 icon["season"] = FAV_HELPERS.extract_line_from_id(
358 data_list=f_season, entry_id=icon.get("season", -1), disp=self.disp
359 )
360 return favicon
361
362 def get_favicon_gender(self, item_id: Union[int, str] = 1) -> Dict[str, Any]:
363 """Retrieve a single favicon gender record by id.
364
365 Args:
366 item_id (Union[int, str]): The id (or identifier) of the gender.
367
368 Returns:
369 Dict[str, Any]: The gender record or an empty dict if not found.
370 """
371 table: str = FAV_CONST.FAVICON_TABLE_GENDER
372 title = "get_favicon_gender:get_from_table"
373 return FAV_HELPERS.get_from_table(self.sql, table, item_id, title=title, disp=self.disp)
374
375 def get_favicon_season(self, item_id: Union[int, str] = 1) -> Dict[str, Any]:
376 """Retrieve a single favicon season record by id.
377
378 Args:
379 item_id (Union[int, str]): The id (or identifier) of the season.
380
381 Returns:
382 Dict[str, Any]: The season record or an empty dict if not found.
383 """
384 table: str = FAV_CONST.FAVICON_TABLE_SEASON
385 title = "get_favicon_season:get_from_table"
386 return FAV_HELPERS.get_from_table(self.sql, table, item_id, title=title, disp=self.disp)
387
388 def get_favicon_type(self, item_id: Union[int, str] = 1) -> Dict[str, Any]:
389 """Retrieve a single favicon type record by id.
390
391 Args:
392 item_id (Union[int, str]): The id (or identifier) of the type.
393
394 Returns:
395 Dict[str, Any]: The type record or an empty dict if not found.
396 """
397 table: str = FAV_CONST.FAVICON_TABLE_TYPE
398 title = "get_favicon_type:get_from_table"
399 return FAV_HELPERS.get_from_table(self.sql, table, item_id, title=title, disp=self.disp)
400
401 def get_favicon(self, favicon_id, *, fetch_image: bool = True, title: str = "list_user_favicon", token: Optional[str] = None) -> Union[FAV_CONST.FaviconData, Response]:
402 """Retrieve a favicon record and optionally its binary image.
403
404 Args:
405 favicon_id: The id of the favicon to retrieve.
406 fetch_image (bool): If True, attempt to download image bytes from the
407 configured bucket. If False, return metadata only. Defaults to True.
408 title (str): Title used in logging and error responses.
409 token (Optional[str]): Optional token used in error responses.
410
411 Returns:
412 Union[FAV_CONST.FaviconData, Response]: A `FaviconData` object with
413 metadata and optionally `img`/`img_type` populated, or a FastAPI
414 `Response` when an error response should be returned.
415 """
416 table: str = FAV_CONST.FAVICON_TABLE_MAIN
417 final_resp: FAV_CONST.FaviconData = FAV_CONST.FaviconData()
418 self.disp.log_debug(
419 f"Gathering the list of uploaded user icons from table '{table}'"
420 )
421 sql_resp = self.sql.get_data_from_table(
422 table=table,
423 column="*",
424 where=f"id={favicon_id}"
425 )
426 if isinstance(sql_resp, int):
427 self.disp.log_error(
428 f"Failed to gather data for table '{table}'"
429 )
430 return self._no_data(title, token)
431 if len(sql_resp) == 0:
432 return final_resp
433 final_resp.data = CONST.clean_dict(
434 self._populate_ids(
435 sql_resp[0].copy()
436 ),
437 (FAV_CONST.FAVICON_IMAGE_PATH_KEY, ""),
438 self.disp
439 )
440 if sql_resp[0][FAV_CONST.FAVICON_IMAGE_PATH_KEY] is None or sql_resp[0][FAV_CONST.FAVICON_IMAGE_PATH_KEY] == "":
441 self.disp.log_error(
442 f"There is no image path for icon id='{favicon_id}'"
443 )
444 return final_resp
445 if not fetch_image:
446 self.disp.log_debug(
447 "Image fetch not requested; returning data without image."
448 )
449 return final_resp
450 self.image_reducerimage_reducer = self.runtime_manager.get_if_exists(
451 "ImageReducer", self.image_reducerimage_reducer)
452 if self.image_reducerimage_reducer is None:
453 self.disp.log_error(
454 "There is no ImageReducer instance available in the runtime manager"
455 )
456 return final_resp
457 img_path = sql_resp[0][FAV_CONST.FAVICON_IMAGE_PATH_KEY]
458 bucket_resp = self.bucket.download_stream(
459 bucket_name=FAV_CONST.FAVICON_BUCKET_NAME,
460 key_name=img_path
461 )
462 if isinstance(bucket_resp, int):
463 self.disp.log_error(
464 f"Failed to download image data from bucket '{FAV_CONST.FAVICON_BUCKET_NAME}' and path '{img_path}'"
465 )
466 return final_resp
467 final_resp.img = bucket_resp
468 final_resp.img_type = self.reducer_type_to_data_type(
469 self.image_reducerimage_reducer.detect_file_format(final_resp.img)
470 )
471 self.disp.log_debug(
472 f"Data gathered for table '{table}':\n{final_resp}"
473 )
474 return final_resp
475
476 def register_gender(self, gender: str, *, title: str = "register_favicon_gender") -> int:
477 """Register a new favicon gender into the genders table.
478
479 Args:
480 gender (str): The gender label to insert.
481 title (str): Logging/response title. Defaults to "register_favicon_gender".
482
483 Returns:
484 int: `self.success` on success or an error code from the SQL layer.
485 """
486 table: str = FAV_CONST.FAVICON_TABLE_GENDER
487 self.disp.log_debug(
488 f"Registering new favicon gender '{gender}' into table '{table}'", title
489 )
490 column_clean = ["gender"]
491 status = self.sql.insert_data_into_table(
492 table=table,
493 data=[gender],
494 column=column_clean
495 )
496 if status != self.success:
497 self.disp.log_error(
498 f"Failed to register new favicon gender '{gender}' into table '{table}'", title
499 )
500 return status
501 self.disp.log_debug(
502 f"Registered new favicon gender '{gender}' into table '{table}' successfully", title
503 )
504 return self.success
505
506 def register_season(self, season: str, parent_season: Optional[int] = None, *, title: str = "register_favicon_season") -> int:
507 """Register a new favicon season, optionally with a parent season.
508
509 Args:
510 season (str): The season label to insert.
511 parent_season (Optional[int]): Optional id of a parent season.
512 title (str): Logging/response title. Defaults to "register_favicon_season".
513
514 Returns:
515 int: `self.success` on success or an error code from the SQL layer.
516 """
517 table: str = FAV_CONST.FAVICON_TABLE_SEASON
518 self.disp.log_debug(
519 f"Registering new favicon season '{season}' with parent '{parent_season}' into table '{table}'", title
520 )
521 column_clean = ["season"]
522 data_clean: List[Union[str, int, float, None]] = [season]
523 if isinstance(parent_season, int):
524 data = self.get_favicon_season(parent_season)
525 if not data or data.get("id") != parent_season or "season" not in data:
526 self.disp.log_error(
527 f"Parent season id '{parent_season}' does not exist in table '{table}'", title
528 )
529 return self.error
530 column_clean.append("parent_id")
531 data_clean.append(parent_season)
532 status = self.sql.insert_data_into_table(
533 table=table,
534 data=data_clean,
535 column=column_clean
536 )
537 if status != self.success:
538 self.disp.log_error(
539 f"Failed to register new favicon season '{season}' into table '{table}'", title
540 )
541 return status
542 self.disp.log_debug(
543 f"Registered new favicon season '{season}' into table '{table}' successfully", title
544 )
545 return self.success
546
547 def register_type(self, ftype: str, parent_type: Optional[int] = None, *, title: str = "register_favicon_type") -> int:
548 """Register a new favicon type, optionally linked to a parent type.
549
550 Args:
551 ftype (str): The type name to insert.
552 parent_type (Optional[int]): Optional id of a parent type.
553 title (str): Logging/response title. Defaults to "register_favicon_type".
554
555 Returns:
556 int: `self.success` on success or an error code from the SQL layer.
557 """
558 table: str = FAV_CONST.FAVICON_TABLE_TYPE
559 self.disp.log_debug(
560 f"Registering new favicon type '{ftype}' with parent '{parent_type}' into table '{table}'", title
561 )
562 column_clean = ["name"]
563 data_clean: List[Union[str, int, float, None]] = [ftype]
564 if isinstance(parent_type, int):
565 data = self.get_favicon_type(parent_type)
566 if not data or data.get("id") != parent_type or "name" not in data:
567 self.disp.log_error(
568 f"Parent season id '{parent_type}' does not exist in table '{table}'", title
569 )
570 return self.error
571 column_clean.append("parent_id")
572 data_clean.append(parent_type)
573 status = self.sql.insert_data_into_table(
574 table=table,
575 data=data_clean,
576 column=column_clean
577 )
578 if status != self.success:
579 self.disp.log_error(
580 f"Failed to register new favicon type '{ftype}' into table '{table}'", title
581 )
582 return status
583 self.disp.log_debug(
584 f"Registered new favicon type '{ftype}' into table '{table}' successfully", title
585 )
586 return self.success
587
589 self,
590 name: str,
591 price: int = FAV_CONST.FAVICON_DEFAULT_PRICE,
592 ftype: Optional[Union[int, str]] = None,
593 gender: Optional[Union[int, str]] = None,
594 season: Optional[Union[int, str]] = None,
595 default_colour: Optional[str] = None,
596 image_data: Optional[Union[bytes, UploadFile]] = None,
597 source: Optional[str] = None,
598 *, title: str = "register_icon"
599 ) -> int:
600 """Register a new favicon entry and optionally upload its image.
601
602 Args:
603 name (str): The display name for the favicon.
604 price (int): The price associated with the favicon.
605 ftype (Optional[Union[int, str]]): Type id or name to link.
606 gender (Optional[Union[int, str]]): Gender id or name to link.
607 season (Optional[Union[int, str]]): Season id or name to link.
608 default_colour (Optional[str]): Hex colour string for default colour.
609 image_data (Optional[Union[bytes, UploadFile]]): Optional image bytes to upload to the bucket.
610 source (Optional[str]): Optional source metadata for the icon.
611 title (str): Logging/response title.
612
613 Returns:
614 int: The id of the newly created favicon.
615
616 Raises:
617 FAV_ERR.FaviconDatabaseError: If inserting the favicon metadata or
618 retrieving the new id from the database fails.
619 FAV_ERR.FaviconNoImageReducerError: If an ImageReducer instance is not
620 available in the runtime manager when `image_data` is provided.
621 FAV_ERR.FaviconImageUploadError: If uploading image bytes to the
622 configured bucket fails.
623 FAV_ERR.FaviconImagePathUpdateError: If updating the database with
624 the stored image path fails after upload.
625
626 The function inserts the favicon metadata into the main table, resolves
627 type/gender/season identifiers, and if `image_data` is provided it will
628 upload the image to the configured bucket and update the database with
629 the stored image path. Various filesystem/bucket/database errors are
630 raised as specialized `FAV_ERR` exceptions or returned as error codes.
631 """
632 table: str = FAV_CONST.FAVICON_TABLE_MAIN
633 self.disp.log_debug(
634 f"Registering new favicon '{name}' into table '{table}'", title
635 )
636 column_clean = ["name", "price"]
637 data_clean: List[Union[str, int, float, None]] = [name, price]
638 ftype_id = self._process_favicon_type_id(ftype)
639 if isinstance(ftype_id, int):
640 column_clean.append("type")
641 data_clean.append(ftype_id)
642 gender_id = self._process_favicon_gender_id(gender)
643 if isinstance(gender_id, int):
644 column_clean.append("gender")
645 data_clean.append(gender_id)
646 season_id = self._process_favicon_season_id(season)
647 if isinstance(season_id, int):
648 column_clean.append("season")
649 data_clean.append(season_id)
650 if isinstance(default_colour, str) and FAV_HELPERS.is_hex_colour_valid(default_colour):
651 padded_colour = FAV_HELPERS.pad_hex_colour(
652 default_colour, with_alpha=True
653 )
654 column_clean.append("default_colour")
655 data_clean.append(padded_colour)
656 if isinstance(source, str):
657 column_clean.append("source")
658 data_clean.append(source)
659 status = self.sql.insert_data_into_table(
660 table=table,
661 data=data_clean,
662 column=column_clean
663 )
664 if status != self.success:
665 self.disp.log_error(
666 f"Failed to register new favicon '{name}' into table '{table}'", title
667 )
668 raise FAV_ERR.FaviconDatabaseError(
669 "Failed to insert new favicon into database"
670 )
671 self.disp.log_debug(
672 f"Registered new favicon '{name}' into table '{table}' successfully", title
673 )
674 icon_id = self.list_favicons()[-1].get("id")
675 if not icon_id:
676 self.disp.log_error(
677 f"Failed to retrieve the id of the newly registered favicon '{name}'", title
678 )
679 raise FAV_ERR.FaviconDatabaseError(
680 "Could not retrieve newly inserted favicon id"
681 )
682 self.disp.log_debug(f"New favicon id is '{icon_id}'", title)
683 self.disp.log_debug(
684 f"type(Image data) = '{type(image_data)}', image_data={image_data}", title
685 )
686 if isinstance(image_data, bytes):
687 self.disp.log_debug("Uploading image from bytes...", title)
688 img_path = self._upload_bytes(
689 image_data,
690 icon_id,
691 title=f"{title}:_upload_bytes"
692 )
693 elif isinstance(image_data, (UploadFile, StarletteUploadFile)):
694 self.disp.log_debug("Uploading image from UploadFile...", title)
695 img_path = self._upload_file(
696 image_data,
697 icon_id,
698 title=f"{title}:_upload_file"
699 )
700 else:
701 self.disp.log_debug(
702 f"Image data is of unsupported type '{type(image_data)}'; skipping upload.", title
703 )
704 self.disp.log_debug(
705 "No image data provided; skipping upload.", title
706 )
707 return int(icon_id)
708 update_status = self.sql.update_data_in_table(
709 table=table,
710 data=[img_path],
711 column=["img_path"],
712 where=f"id={icon_id}"
713 )
714 if update_status != self.success:
715 self.disp.log_error(
716 f"Failed to update favicon image path in table '{table}' for icon id '{icon_id}'", title
717 )
718 raise FAV_ERR.FaviconImagePathUpdateError(
719 "Failed to update image path in database"
720 )
721 self.disp.log_debug(
722 f"Uploaded favicon image data to bucket '{FAV_CONST.FAVICON_BUCKET_NAME}' at path '{img_path}' successfully", title
723 )
724 return int(icon_id)
725
727 self,
728 icon_id: int,
729 image_data: Optional[Union[bytes, UploadFile]] = None,
730 *, title: str = "register_icon"
731 ) -> int:
732 """Register a new favicon entry and optionally upload its image.
733
734 Args:
735 name (str): The display name for the favicon.
736 price (int): The price associated with the favicon.
737 ftype (Optional[Union[int, str]]): Type id or name to link.
738 gender (Optional[Union[int, str]]): Gender id or name to link.
739 season (Optional[Union[int, str]]): Season id or name to link.
740 default_colour (Optional[str]): Hex colour string for default colour.
741 image_data (Optional[Union[bytes, UploadFile]]): Optional image bytes to upload to the bucket.
742 source (Optional[str]): Optional source metadata for the icon.
743 title (str): Logging/response title.
744
745 Returns:
746 int: The id of the newly created favicon.
747
748 Raises:
749 FAV_ERR.FaviconDatabaseError: If inserting the favicon metadata or
750 retrieving the new id from the database fails.
751 FAV_ERR.FaviconNoImageReducerError: If an ImageReducer instance is not
752 available in the runtime manager when `image_data` is provided.
753 FAV_ERR.FaviconImageUploadError: If uploading image bytes to the
754 configured bucket fails.
755 FAV_ERR.FaviconImagePathUpdateError: If updating the database with
756 the stored image path fails after upload.
757
758 The function inserts the favicon metadata into the main table, resolves
759 type/gender/season identifiers, and if `image_data` is provided it will
760 upload the image to the configured bucket and update the database with
761 the stored image path. Various filesystem/bucket/database errors are
762 raised as specialized `FAV_ERR` exceptions or returned as error codes.
763 """
764 table: str = FAV_CONST.FAVICON_TABLE_MAIN
765 self.disp.log_debug(
766 f"Registering new favicon image '{icon_id}' into table '{table}'", title
767 )
768 self.disp.log_debug(
769 f"type(Image data) = '{type(image_data)}', image_data={image_data}", title
770 )
771 if isinstance(image_data, bytes):
772 self.disp.log_debug("Uploading image from bytes...", title)
773 img_path = self._upload_bytes(
774 image_data,
775 icon_id,
776 title=f"{title}:_upload_bytes"
777 )
778 elif isinstance(image_data, (UploadFile, StarletteUploadFile)):
779 self.disp.log_debug("Uploading image from UploadFile...", title)
780 img_path = self._upload_file(
781 image_data,
782 icon_id,
783 title=f"{title}:_upload_file"
784 )
785 else:
786 self.disp.log_debug(
787 f"Image data is of unsupported type '{type(image_data)}'; skipping upload.", title
788 )
789 self.disp.log_debug(
790 "No image data provided; skipping upload.", title
791 )
792 return int(icon_id)
793 update_status = self.sql.update_data_in_table(
794 table=table,
795 data=[img_path],
796 column=["img_path"],
797 where=f"id={icon_id}"
798 )
799 if update_status != self.success:
800 self.disp.log_error(
801 f"Failed to update favicon image path in table '{table}' for icon id '{icon_id}'", title
802 )
803 raise FAV_ERR.FaviconImagePathUpdateError(
804 "Failed to update image path in database"
805 )
806 self.disp.log_debug(
807 f"Uploaded favicon image data to bucket '{FAV_CONST.FAVICON_BUCKET_NAME}' at path '{img_path}' successfully", title
808 )
809 return int(icon_id)
int register_season(self, str season, Optional[int] parent_season=None, *, str title="register_favicon_season")
Dict[str, Any] _populate_ids(self, Dict[str, Any] data)
Response _no_user_id(self, str title, Optional[str] token=None)
int register_type(self, str ftype, Optional[int] parent_type=None, *, str title="register_favicon_type")
Optional[int] _process_favicon_gender_id(self, Optional[Union[int, str]] gender=None)
str _upload_file(self, UploadFile image_data, int icon_id, *, str title="_upload_bytes")
Union[FAV_CONST.FaviconData, Response] get_favicon(self, favicon_id, *, bool fetch_image=True, str title="list_user_favicon", Optional[str] token=None)
None __init__(self, int success=0, int error=84, bool debug=False)
int register_gender(self, str gender, *, str title="register_favicon_gender")
Response _no_data(self, str title, Optional[str] token=None)
str _upload_bytes(self, bytes image_data, int icon_id, *, str title="_upload_bytes")
Optional[int] _process_favicon_season_id(self, Optional[Union[int, str]] ftype=None)
HttpDataTypes reducer_type_to_data_type(self, FileFormat reducer_type)
Dict[str, Any] get_favicon_gender(self, Union[int, str] item_id=1)
Dict[str, Any] get_favicon_season(self, Union[int, str] item_id=1)
Optional[int] _process_favicon_type_id(self, Optional[Union[int, str]] ftype=None)
int register_icon_image(self, int icon_id, Optional[Union[bytes, UploadFile]] image_data=None, *, str title="register_icon")
int register_icon(self, str name, int price=FAV_CONST.FAVICON_DEFAULT_PRICE, Optional[Union[int, str]] ftype=None, Optional[Union[int, str]] gender=None, Optional[Union[int, str]] season=None, Optional[str] default_colour=None, Optional[Union[bytes, UploadFile]] image_data=None, Optional[str] source=None, *, str title="register_icon")
Dict[str, Any] get_favicon_type(self, Union[int, str] item_id=1)