11import dataclasses
22import logging
33import uuid
4+ from datetime import datetime
45from typing import Callable
56
67import azure .cosmos .cosmos_client as cosmos_client
78import azure .cosmos .exceptions as exceptions
89from azure .cosmos import ContainerProxy , PartitionKey
910from flask import Flask
11+ from werkzeug .exceptions import HTTPException
1012
1113from commons .data_access_layer .database import CRUDDao
12- from time_tracker_api .security import current_user_tenant_id
14+ from time_tracker_api .security import current_user_tenant_id , current_user_id
1315
1416
1517class CosmosDBFacade :
@@ -75,12 +77,14 @@ class CosmosDBRepository:
7577 def __init__ (self , container_id : str ,
7678 partition_key_attribute : str ,
7779 mapper : Callable = None ,
80+ order_fields : list = [],
7881 custom_cosmos_helper : CosmosDBFacade = None ):
7982 global cosmos_helper
8083 self .cosmos_helper = custom_cosmos_helper or cosmos_helper
8184 if self .cosmos_helper is None : # pragma: no cover
8285 raise ValueError ("The cosmos_db module has not been initialized!" )
8386 self .mapper = mapper
87+ self .order_fields = order_fields
8488 self .container : ContainerProxy = self .cosmos_helper .db .get_container_client (container_id )
8589 self .partition_key_attribute : str = partition_key_attribute
8690
@@ -93,7 +97,23 @@ def from_definition(cls, container_definition: dict,
9397 mapper = mapper ,
9498 custom_cosmos_helper = custom_cosmos_helper )
9599
100+ @staticmethod
101+ def create_sql_condition_for_visibility (visible_only : bool , container_name = 'c' ) -> str :
102+ if visible_only :
103+ # We are considering that `deleted == null` is not a choice
104+ return 'NOT IS_DEFINED(%s.deleted)' % container_name
105+ return 'true'
106+
107+ @staticmethod
108+ def check_visibility (item , throw_not_found_if_deleted ):
109+ if throw_not_found_if_deleted and item .get ('deleted' ) is not None :
110+ raise exceptions .CosmosResourceNotFoundError (message = 'Deleted item' ,
111+ status_code = 404 )
112+
113+ return item
114+
96115 def create (self , data : dict , mapper : Callable = None ):
116+ self .on_create (data )
97117 function_mapper = self .get_mapper_or_dict (mapper )
98118 return function_mapper (self .container .create_item (body = data ))
99119
@@ -108,10 +128,12 @@ def find_all(self, partition_key_value: str, max_count=None, offset=0,
108128 max_count = self .get_page_size_or (max_count )
109129 result = self .container .query_items (
110130 query = """
111- SELECT * FROM c WHERE c.{partition_key_attribute}=@partition_key_value AND {visibility_condition}
131+ SELECT * FROM c WHERE c.{partition_key_attribute}=@partition_key_value
132+ AND {visibility_condition} {order_clause}
112133 OFFSET @offset LIMIT @max_count
113134 """ .format (partition_key_attribute = self .partition_key_attribute ,
114- visibility_condition = self .create_sql_condition_for_visibility (visible_only )),
135+ visibility_condition = self .create_sql_condition_for_visibility (visible_only ),
136+ order_clause = self .create_sql_order_clause ()),
115137 parameters = [
116138 {"name" : "@partition_key_value" , "value" : partition_key_value },
117139 {"name" : "@offset" , "value" : offset },
@@ -130,6 +152,7 @@ def partial_update(self, id: str, changes: dict, partition_key_value: str,
130152 return self .update (id , item_data , mapper = mapper )
131153
132154 def update (self , id : str , item_data : dict , mapper : Callable = None ):
155+ self .on_update (item_data )
133156 function_mapper = self .get_mapper_or_dict (mapper )
134157 return function_mapper (self .container .replace_item (id , body = item_data ))
135158
@@ -141,19 +164,6 @@ def delete(self, id: str, partition_key_value: str, mapper: Callable = None):
141164 def delete_permanently (self , id : str , partition_key_value : str ) -> None :
142165 self .container .delete_item (id , partition_key_value )
143166
144- def check_visibility (self , item , throw_not_found_if_deleted ):
145- if throw_not_found_if_deleted and item .get ('deleted' ) is not None :
146- raise exceptions .CosmosResourceNotFoundError (message = 'Deleted item' ,
147- status_code = 404 )
148-
149- return item
150-
151- def create_sql_condition_for_visibility (self , visible_only : bool , container_name = 'c' ) -> str :
152- if visible_only :
153- # We are considering that `deleted == null` is not a choice
154- return 'NOT IS_DEFINED(%s.deleted)' % container_name
155- return 'true'
156-
157167 def get_mapper_or_dict (self , alternative_mapper : Callable ) -> Callable :
158168 return alternative_mapper or self .mapper or dict
159169
@@ -162,11 +172,28 @@ def get_page_size_or(self, custom_page_size: int) -> int:
162172 # or any other repository for the settings
163173 return custom_page_size or 100
164174
175+ def on_create (self , new_item_data : dict ):
176+ if new_item_data .get ('id' ) is None :
177+ new_item_data ['id' ] = str (uuid .uuid4 ())
178+
179+ def on_update (self , update_item_data : dict ):
180+ pass
181+
182+ def create_sql_order_clause (self ):
183+ if len (self .order_fields ) > 0 :
184+ return "ORDER BY c.{}" .format (", c." .join (self .order_fields ))
185+ else :
186+ return ""
187+
165188
166189class CosmosDBDao (CRUDDao ):
167190 def __init__ (self , repository : CosmosDBRepository ):
168191 self .repository = repository
169192
193+ @property
194+ def partition_key_value (self ):
195+ return current_user_tenant_id ()
196+
170197 def get_all (self ) -> list :
171198 tenant_id : str = self .partition_key_value
172199 return self .repository .find_all (partition_key_value = tenant_id )
@@ -176,8 +203,8 @@ def get(self, id):
176203 return self .repository .find (id , partition_key_value = tenant_id )
177204
178205 def create (self , data : dict ):
179- data ['id' ] = str ( uuid . uuid4 ())
180- data ['tenant_id ' ] = self . partition_key_value
206+ data [self . repository . partition_key_attribute ] = self . partition_key_value
207+ data ['owner_id ' ] = current_user_id ()
181208 return self .repository .create (data )
182209
183210 def update (self , id , data : dict ):
@@ -189,9 +216,22 @@ def delete(self, id):
189216 tenant_id : str = current_user_tenant_id ()
190217 self .repository .delete (id , partition_key_value = tenant_id )
191218
192- @property
193- def partition_key_value (self ):
194- return current_user_tenant_id ()
219+
220+ class CustomError (HTTPException ):
221+ def __init__ (self , status_code : int , description : str = None ):
222+ self .code = status_code
223+ self .description = description
224+
225+
226+ def current_datetime ():
227+ return datetime .utcnow ()
228+
229+
230+ def datetime_str (value : datetime ):
231+ if value is not None :
232+ return value .isoformat ()
233+ else :
234+ return None
195235
196236
197237def init_app (app : Flask ) -> None :
0 commit comments