Coverage for functions \ flipdare \ firestore \ _app_sub_db.py: 77%
57 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#
14from typing import Any
16from google.cloud.firestore import Client as FirestoreClient
17from google.cloud.firestore_v1.base_document import DocumentSnapshot
18from flipdare.app_types import DatabaseDict
19from flipdare.constants import DEF_RETRIEVAL_WINDOW_HOURS
20from flipdare.firestore._app_db import AppDb
21from flipdare.firestore.core.app_base_model import AppBaseModel
22from flipdare.firestore.core.db_query import OrderByField
23from flipdare.generated.shared.firestore_collections import FirestoreCollections
24from flipdare.wrapper._persisted_wrapper import PersistedWrapper
26__all__ = ["AppSubDb"]
29class AppSubDb[W: PersistedWrapper[Any], T: AppBaseModel](AppDb[W, T]):
30 def __init__(
31 self,
32 client: FirestoreClient,
33 collection_name: FirestoreCollections,
34 sub_collection_name: FirestoreCollections,
35 wrapper_class: type[W], # The Sub Wrapper class e.g. CommentWrapper
36 model_class: type[T], # The Sub Model class e.g. CommentModel
37 def_window_hours: int = DEF_RETRIEVAL_WINDOW_HOURS,
38 ) -> None:
39 super().__init__(
40 client=client,
41 collection_name=collection_name,
42 wrapper_class=wrapper_class,
43 model_class=model_class,
44 def_window_hours=def_window_hours,
45 )
46 self._sub_collection_name = sub_collection_name
48 @property
49 def sub_collection_name(self) -> str:
50 """Get the name of the sub-collection."""
51 return self._sub_collection_name.value
53 def exists_sub(self, parent_id: str, sub_id: str) -> bool:
54 """Check if a sub-collection document exists."""
55 return self._exists_sub(parent_id, self._sub_collection_name, sub_id)
57 def get_sub(self, parent_id: str, sub_id: str) -> W | None:
58 """Get a sub-collection document by ID, returns PersistedWrapper."""
59 data = self._get_sub(parent_id, self._sub_collection_name, sub_id)
60 if data is None:
61 return None
63 return self.wrapper_class.from_dict(data)
65 def get_all_sub(
66 self,
67 parent_id: str,
68 limit: int | None = None,
69 order_by: OrderByField[Any] | None = None,
70 ) -> list[W]:
71 """Get all documents in a sub-collection, returns list of PersistedWrapper."""
72 data_items = self._get_all_sub(
73 parent_id,
74 self._sub_collection_name,
75 order_by=order_by,
76 limit=limit,
77 )
78 if not data_items:
79 return []
81 results: list[W] = []
82 for item in data_items:
83 model = self.wrapper_class.from_dict(item)
84 results.append(model)
85 return results
87 def get_bulk_sub(
88 self,
89 parent_id: str,
90 sub_ids: list[str],
91 ) -> list[W]:
92 """Get multiple sub-collection documents by their IDs, returns list of PersistedWrapper."""
93 data_items = self._get_bulk_sub(parent_id, self._sub_collection_name, sub_ids)
94 if not data_items:
95 return []
97 results: list[W] = []
98 for item in data_items:
99 model = self.wrapper_class.from_dict(item)
100 results.append(model)
101 return results
103 def create_sub(self, parent_id: str, data: T | dict[str, Any]) -> W:
104 """
105 Create a new document in the sub-collection, returns PersistedWrapper.
107 Args:
108 parent_id: Parent document ID
109 data: Model instance or dict to create from
111 Returns:
112 PersistedWrapper for the created document
114 """
115 # Convert to dict for Firestore
116 payload = data if isinstance(data, dict) else data.to_dict()
117 created_data = self._create_sub(parent_id, self._sub_collection_name, payload)
119 return self.wrapper_class.from_dict(created_data)
121 def update_sub(
122 self,
123 parent_id: str,
124 sub_id: str,
125 updates: DatabaseDict,
126 ) -> W | None:
127 """Update a sub-collection document, returns PersistedWrapper."""
128 updated_data = self._update_sub(parent_id, self._sub_collection_name, sub_id, updates)
130 if updated_data is None:
131 return None
133 return self.wrapper_class.from_dict(updated_data)
135 def _cvt_sub_snap_to_model(self, snap: DocumentSnapshot) -> W | None:
136 """Convert DocumentSnapshot to PersistedWrapper[model]."""
137 data = self._cvt_snap_to_data(snap)
138 if data is None:
139 return None
141 return self.wrapper_class.from_dict(data)