55from enums .metadata_field_names import DocumentReferenceMetadataFields
66from enums .supported_document_types import SupportedDocumentTypes
77from models .document_reference import DocumentReference
8- from pydantic import ValidationError
8+ from pydantic import BaseModel , ValidationError
99from services .base .dynamo_service import DynamoDBService
1010from services .base .s3_service import S3Service
1111from utils .audit_logging_setup import LoggingService
1212from utils .common_query_filters import NotDeleted
13- from utils .dynamo_utils import filter_uploaded_docs_and_recently_uploading_docs
14- from utils .exceptions import (
15- DocumentServiceException ,
16- FileUploadInProgress ,
17- NoAvailableDocument ,
18- )
13+ from utils .exceptions import DocumentServiceException
1914
2015logger = LoggingService (__name__ )
2116
2217
2318class DocumentService :
19+ """Service for document operations."""
20+
2421 def __init__ (self ):
2522 self .s3_service = S3Service ()
2623 self .dynamo_service = DynamoDBService ()
24+ self ._lg_table_name = os .getenv ("LLOYD_GEORGE_DYNAMODB_NAME" )
25+ self ._lg_s3_bucket = os .getenv ("LLOYD_GEORGE_BUCKET_NAME" )
26+
27+ @property
28+ def table_name (self ) -> str :
29+ """DynamoDB table name. Can be overridden by child classes."""
30+ return self ._lg_table_name
31+
32+ @property
33+ def s3_bucket (self ) -> str :
34+ """S3 bucket name. Can be overridden by child classes."""
35+ return self ._lg_s3_bucket
36+
37+ @property
38+ def model_class (self ) -> type [BaseModel ]:
39+ """Pydantic model class. Can be overridden by child classes."""
40+ return DocumentReference
2741
2842 def fetch_available_document_references_by_type (
2943 self ,
@@ -34,56 +48,74 @@ def fetch_available_document_references_by_type(
3448 table_name = doc_type .get_dynamodb_table_name ()
3549
3650 return self .fetch_documents_from_table_with_nhs_number (
37- nhs_number , table_name , query_filter = query_filter
51+ nhs_number ,
52+ table_name ,
53+ query_filter = query_filter ,
3854 )
3955
4056 def fetch_documents_from_table_with_nhs_number (
41- self , nhs_number : str , table : str , query_filter : Attr | ConditionBase = None
42- ) -> list [DocumentReference ]:
57+ self ,
58+ nhs_number : str ,
59+ table : str | None = None ,
60+ query_filter : Attr | ConditionBase = None ,
61+ model_class : type [BaseModel ] = None ,
62+ ) -> list :
63+ """Fetch documents by NHS number from specified or configured table."""
64+ table_name = table or self .table_name
65+
4366 documents = self .fetch_documents_from_table (
44- table = table ,
4567 index_name = "NhsNumberIndex" ,
4668 search_key = "NhsNumber" ,
4769 search_condition = nhs_number ,
4870 query_filter = query_filter ,
71+ table_name = table_name ,
72+ model_class = model_class ,
4973 )
50-
5174 return documents
5275
5376 def fetch_documents_from_table (
5477 self ,
55- table : str ,
5678 search_condition : str ,
5779 search_key : str ,
5880 index_name : str | None = None ,
5981 query_filter : Attr | ConditionBase = None ,
60- ) -> list [DocumentReference ]:
82+ table_name : str | None = None ,
83+ model_class : type [BaseModel ] = None ,
84+ ) -> list :
85+ """Fetch documents from specified or configured table using model_class."""
6186 documents = []
87+ table_name = table_name or self .table_name
88+ model_class = model_class or self .model_class
6289
6390 response = self .dynamo_service .query_table (
64- table_name = table ,
91+ table_name = table_name ,
6592 index_name = index_name ,
6693 search_key = search_key ,
6794 search_condition = search_condition ,
6895 query_filter = query_filter ,
6996 )
7097 for item in response :
7198 try :
72- document = DocumentReference .model_validate (item )
99+ document = model_class .model_validate (item )
73100 documents .append (document )
74101 except ValidationError as e :
75102 logger .error (f"Validation error on document: { item } " )
76103 logger .error (f"{ e } " )
77104 continue
78105 return documents
79106
80- def get_nhs_numbers_based_on_ods_code (self , ods_code : str ) -> list [str ]:
107+ def get_nhs_numbers_based_on_ods_code (
108+ self , ods_code : str , table_name : str | None = None
109+ ) -> list [str ]:
110+ """Get unique NHS numbers for patients with given ODS code."""
111+ table_name = table_name or self .table_name
112+
81113 documents = self .fetch_documents_from_table (
82- table = os .environ ["LLOYD_GEORGE_DYNAMODB_NAME" ],
83114 index_name = "OdsCodeIndex" ,
84115 search_key = DocumentReferenceMetadataFields .CURRENT_GP_ODS .value ,
85116 search_condition = ods_code ,
86117 query_filter = NotDeleted ,
118+ table_name = table_name ,
87119 )
88120 nhs_numbers = list ({document .nhs_number for document in documents })
89121 return nhs_numbers
@@ -139,21 +171,27 @@ def delete_document_object(self, bucket: str, key: str):
139171
140172 def update_document (
141173 self ,
142- table_name : str ,
143- document_reference : DocumentReference ,
144- update_fields_name : set [str ] = None ,
174+ table_name : str | None = None ,
175+ document : BaseModel = None ,
176+ update_fields_name : set [str ] | None = None ,
145177 ):
178+ """Update document in specified or configured table."""
179+ table_name = table_name or self .table_name
180+
146181 self .dynamo_service .update_item (
147182 table_name = table_name ,
148- key_pair = {DocumentReferenceMetadataFields .ID .value : document_reference .id },
149- updated_fields = document_reference .model_dump (
183+ key_pair = {DocumentReferenceMetadataFields .ID .value : document .id },
184+ updated_fields = document .model_dump (
150185 exclude_none = True , by_alias = True , include = update_fields_name
151186 ),
152187 )
153188
154189 def hard_delete_metadata_records (
155- self , table_name : str , document_references : list [DocumentReference ]
190+ self , table_name : str , document_references : list [BaseModel ]
156191 ):
192+ """Permanently delete metadata from specified or configured table."""
193+ table_name = table_name or self .table_name
194+
157195 logger .info (f"Deleting items in table: { table_name } (HARD DELETE)" )
158196 primary_key_name = DocumentReferenceMetadataFields .ID .value
159197 for reference in document_references :
@@ -162,41 +200,26 @@ def hard_delete_metadata_records(
162200 self .dynamo_service .delete_item (table_name , deletion_key )
163201
164202 @staticmethod
165- def is_upload_in_process (record : DocumentReference ):
203+ def is_upload_in_process (record : DocumentReference ) -> bool :
204+ """Check if a document upload is currently in progress."""
166205 return (
167206 not record .uploaded
168207 and record .uploading
169208 and record .last_updated_within_three_minutes ()
170209 and record .doc_status != "final"
171210 )
172211
173- def get_available_lloyd_george_record_for_patient (
174- self , nhs_number
175- ) -> list [DocumentReference ]:
176- filter_expression = filter_uploaded_docs_and_recently_uploading_docs ()
177- available_docs = self .fetch_available_document_references_by_type (
178- nhs_number ,
179- SupportedDocumentTypes .LG ,
180- query_filter = filter_expression ,
181- )
182-
183- file_in_progress_message = (
184- "The patients Lloyd George record is in the process of being uploaded"
185- )
186- if not available_docs :
187- raise NoAvailableDocument ()
188- for document in available_docs :
189- if document .uploading and not document .uploaded :
190- raise FileUploadInProgress (file_in_progress_message )
191- return available_docs
192-
193212 def get_batch_document_references_by_id (
194213 self , document_ids : list [str ], doc_type : SupportedDocumentTypes
195- ) -> list [ DocumentReference ] :
214+ ) -> list :
196215 table_name = doc_type .get_dynamodb_table_name ()
216+
217+ table_name = table_name or self .table_name
218+ model_class = self .model_class
219+
197220 response = self .dynamo_service .batch_get_items (
198221 table_name = table_name , key_list = document_ids
199222 )
200223
201- found_docs = [DocumentReference .model_validate (item ) for item in response ]
224+ found_docs = [model_class .model_validate (item ) for item in response ]
202225 return found_docs
0 commit comments