From ad3ed00defd2e24948afb2cfc224e287398441a2 Mon Sep 17 00:00:00 2001 From: "Sean R. Abraham" Date: Sat, 21 Dec 2024 07:53:08 -0500 Subject: [PATCH] continue the rename of "incident report" to "field report" this takes us most of the way down to the persistence layer https://github.com/burningmantech/ranger-ims-server/issues/1469 --- src/ims/application/_api.py | 20 +- src/ims/application/_web.py | 20 +- src/ims/auth/_provider.py | 14 +- src/ims/auth/test/test_provider.py | 24 +- .../report_template/_report_template.py | 2 +- src/ims/store/__init__.py | 4 +- src/ims/store/_abc.py | 58 ++-- src/ims/store/_db.py | 316 +++++++++--------- src/ims/store/_exceptions.py | 4 +- src/ims/store/export/_json.py | 6 +- src/ims/store/export/test/test_json.py | 2 +- src/ims/store/mysql/_queries.py | 24 +- src/ims/store/mysql/test/test_store.py | 2 +- src/ims/store/sqlite/_queries.py | 24 +- src/ims/store/sqlite/test/test_store.py | 2 +- src/ims/store/sqlite/test/test_store_core.py | 2 +- src/ims/store/test/base.py | 4 +- src/ims/store/test/database.py | 6 +- src/ims/store/test/report.py | 312 +++++++++-------- 19 files changed, 415 insertions(+), 431 deletions(-) diff --git a/src/ims/application/_api.py b/src/ims/application/_api.py index f2da45c1a..b276f513a 100644 --- a/src/ims/application/_api.py +++ b/src/ims/application/_api.py @@ -708,14 +708,14 @@ async def listFieldReportsResource( user: IMSUser = request.user # type: ignore[attr-defined] fieldReports = ( fieldReport - for fieldReport in await store.incidentReports( + for fieldReport in await store.fieldReports( event_id, excludeSystemEntries=excludeSystemEntries ) if user.shortNames[0] in (entry.author for entry in fieldReport.reportEntries) ) elif incidentNumberText is None: - fieldReports = await store.incidentReports( + fieldReports = await store.fieldReports( event_id, excludeSystemEntries=excludeSystemEntries ) else: @@ -724,7 +724,7 @@ async def listFieldReportsResource( except ValueError: return invalidQueryResponse(request, "incident", incidentNumberText) - fieldReports = await store.incidentReportsAttachedToIncident( + fieldReports = await store.fieldReportsAttachedToIncident( eventID=event_id, incidentNumber=incidentNumber ) @@ -822,7 +822,7 @@ async def newFieldReportResource( # Store the field report - fieldReport = await self.config.store.createIncidentReport(fieldReport, author) + fieldReport = await self.config.store.createFieldReport(fieldReport, author) self._log.info( "User {author} created new field report #{fieldReport.number} via JSON", @@ -858,11 +858,11 @@ async def readFieldReportResource( return notFoundResponse(request) del field_report_number - fieldReport = await self.config.store.incidentReportWithNumber( + fieldReport = await self.config.store.fieldReportWithNumber( event_id, fieldReportNumber ) - await self.config.authProvider.authorizeRequestForIncidentReport( + await self.config.authProvider.authorizeRequestForFieldReport( request, fieldReport ) @@ -909,11 +909,11 @@ async def editFieldReportResource( return invalidQueryResponse(request, "incident", incidentNumberText) if action == "attach": - await store.attachIncidentReportToIncident( + await store.attachFieldReportToIncident( fieldReportNumber, event_id, incidentNumber, author ) elif action == "detach": - await store.detachIncidentReportFromIncident( + await store.detachFieldReportFromIncident( fieldReportNumber, event_id, incidentNumber, author ) else: @@ -967,7 +967,7 @@ def _cast(obj: Any) -> Any: await applyEdit( edits, IncidentReportJSONKey.summary, - store.setIncidentReport_summary, + store.setFieldReport_summary, ) jsonEntries = edits.get(IncidentReportJSONKey.reportEntries.value, UNSET) @@ -984,7 +984,7 @@ def _cast(obj: Any) -> Any: for jsonEntry in jsonEntries ) - await store.addReportEntriesToIncidentReport( + await store.addReportEntriesToFieldReport( event_id, fieldReportNumber, entries, author ) diff --git a/src/ims/application/_web.py b/src/ims/application/_web.py index c63306428..4f3323e29 100644 --- a/src/ims/application/_web.py +++ b/src/ims/application/_web.py @@ -43,7 +43,7 @@ from ims.element.root import RootPage from ims.ext.klein import static from ims.model import Event -from ims.store import NoSuchIncidentReportError +from ims.store import NoSuchFieldReportError from ._klein import Router, notFoundResponse, redirect @@ -243,37 +243,37 @@ async def viewFieldReportPage( """ Endpoint for the field report page. """ - incidentReportNumber: int | None + fieldReportNumber: int | None config = self.config if number == "new": await config.authProvider.authorizeRequest( request, event_id, Authorization.writeFieldReports ) - incidentReportNumber = None + fieldReportNumber = None del number else: try: - incidentReportNumber = int(number) + fieldReportNumber = int(number) except ValueError: return notFoundResponse(request) del number try: - incidentReport = await config.store.incidentReportWithNumber( - event_id, incidentReportNumber + fieldReport = await config.store.fieldReportWithNumber( + event_id, fieldReportNumber ) - except NoSuchIncidentReportError: + except NoSuchFieldReportError: await config.authProvider.authorizeRequest( request, event_id, Authorization.readIncidents ) return notFoundResponse(request) - await config.authProvider.authorizeRequestForIncidentReport( - request, incidentReport + await config.authProvider.authorizeRequestForFieldReport( + request, fieldReport ) event = Event(id=event_id) - return FieldReportPage(config=config, event=event, number=incidentReportNumber) + return FieldReportPage(config=config, event=event, number=fieldReportNumber) @router.route(_unprefix(URLs.viewFieldReportTemplate), methods=("HEAD", "GET")) @static diff --git a/src/ims/auth/_provider.py b/src/ims/auth/_provider.py index ffea0790c..df9bc84ef 100644 --- a/src/ims/auth/_provider.py +++ b/src/ims/auth/_provider.py @@ -441,20 +441,20 @@ async def authorizeRequest( ) raise NotAuthorizedError("User not authorized") - async def authorizeRequestForIncidentReport( - self, request: IRequest, incidentReport: IncidentReport + async def authorizeRequestForFieldReport( + self, request: IRequest, fieldReport: IncidentReport ) -> None: """ Determine whether the user attached to a request has the required - authorizations to access the incident report with the given number. + authorizations to access the field report with the given number. """ # An author of the incident report should be allowed to read and write # to it, provided they have writeFieldReports on the event. userIsAuthor = False user: IMSUser = request.user # type: ignore[attr-defined] - if user is not None and incidentReport.reportEntries: + if user is not None and fieldReport.reportEntries: rangerHandle = user.shortNames[0] - for reportEntry in incidentReport.reportEntries: + for reportEntry in fieldReport.reportEntries: if reportEntry.author == rangerHandle: userIsAuthor = True break @@ -465,7 +465,7 @@ async def authorizeRequestForIncidentReport( try: await self.authorizeRequest( request, - incidentReport.eventID, + fieldReport.eventID, Authorization.writeFieldReports, ) except NotAuthorizedError: @@ -478,5 +478,5 @@ async def authorizeRequestForIncidentReport( # Authorize the user if they have readIncidents permission await self.authorizeRequest( - request, incidentReport.eventID, Authorization.readIncidents + request, fieldReport.eventID, Authorization.readIncidents ) diff --git a/src/ims/auth/test/test_provider.py b/src/ims/auth/test/test_provider.py index 0b6981700..31eaaa7ec 100644 --- a/src/ims/auth/test/test_provider.py +++ b/src/ims/auth/test/test_provider.py @@ -800,9 +800,9 @@ def test_authorizeReqForIncidentReport(self) -> None: # Stage 1: user doesn't have writeFieldReports, so no incident # report can be read. self.failureResultOf( - provider.authorizeRequestForIncidentReport( + provider.authorizeRequestForFieldReport( request=request, - incidentReport=reportByUser, + fieldReport=reportByUser, ), NotAuthorizedError, ) @@ -811,16 +811,16 @@ def test_authorizeReqForIncidentReport(self) -> None: # Stage 2: user is a reporter self.successResultOf(store.setReporters(event, (personUser,))) self.successResultOf( - provider.authorizeRequestForIncidentReport( + provider.authorizeRequestForFieldReport( request=request, - incidentReport=reportByUser, + fieldReport=reportByUser, ), ) self.assertEqual(request.authorizations, Authorization.writeFieldReports) self.failureResultOf( - provider.authorizeRequestForIncidentReport( + provider.authorizeRequestForFieldReport( request=request, - incidentReport=reportNotByUser, + fieldReport=reportNotByUser, ), NotAuthorizedError, ) @@ -830,15 +830,15 @@ def test_authorizeReqForIncidentReport(self) -> None: self.successResultOf(store.setReporters(event, ())) self.successResultOf(store.setReaders(event, (personUser,))) self.successResultOf( - provider.authorizeRequestForIncidentReport( + provider.authorizeRequestForFieldReport( request=request, - incidentReport=reportByUser, + fieldReport=reportByUser, ), ) self.successResultOf( - provider.authorizeRequestForIncidentReport( + provider.authorizeRequestForFieldReport( request=request, - incidentReport=reportNotByUser, + fieldReport=reportNotByUser, ), ) self.assertEqual( @@ -850,9 +850,9 @@ def test_authorizeReqForIncidentReport(self) -> None: self.successResultOf(store.setReaders(event, ())) self.successResultOf(store.setWriters(event, (personUser,))) self.successResultOf( - provider.authorizeRequestForIncidentReport( + provider.authorizeRequestForFieldReport( request=request, - incidentReport=reportNotByUser, + fieldReport=reportNotByUser, ), ) self.assertEqual( diff --git a/src/ims/element/incident/report_template/_report_template.py b/src/ims/element/incident/report_template/_report_template.py index 5100431ee..4270f69c0 100644 --- a/src/ims/element/incident/report_template/_report_template.py +++ b/src/ims/element/incident/report_template/_report_template.py @@ -32,7 +32,7 @@ @mutable(kw_only=True) class FieldReportTemplatePage(Page): """ - Incident report template page. + Field report template page. """ name: str = title diff --git a/src/ims/store/__init__.py b/src/ims/store/__init__.py index 02572eb7a..a33e532f7 100644 --- a/src/ims/store/__init__.py +++ b/src/ims/store/__init__.py @@ -20,15 +20,15 @@ from ._abc import IMSDataStore from ._exceptions import ( + NoSuchFieldReportError, NoSuchIncidentError, - NoSuchIncidentReportError, StorageError, ) __all__ = ( "IMSDataStore", + "NoSuchFieldReportError", "NoSuchIncidentError", - "NoSuchIncidentReportError", "StorageError", ) diff --git a/src/ims/store/_abc.py b/src/ims/store/_abc.py index e4863f32f..0736c5c57 100644 --- a/src/ims/store/_abc.py +++ b/src/ims/store/_abc.py @@ -321,69 +321,67 @@ async def addReportEntriesToIncident( ### @abstractmethod - async def incidentReports( + async def fieldReports( self, eventID: str, *, excludeSystemEntries: bool = False ) -> Iterable[IncidentReport]: """ - Look up all incident reports in the given event. + Look up all field reports in the given event. """ @abstractmethod - async def incidentReportWithNumber( - self, eventID: str, number: int - ) -> IncidentReport: + async def fieldReportWithNumber(self, eventID: str, number: int) -> IncidentReport: """ - Look up the incident report with the given number. + Look up the field report with the given number. """ @abstractmethod - async def createIncidentReport( - self, incidentReport: IncidentReport, author: str + async def createFieldReport( + self, fieldReport: IncidentReport, author: str ) -> IncidentReport: """ - Create a new incident report. + Create a new field report. - The incident report number is determined by the database and must be - specified as zero in the given incident report. + The field report number is determined by the database and must be + specified as zero in the given field report. - The stored incident report is returned with the incident report number + The stored field report is returned with the field report number assigned to it by the data store, and with initial (automatic) report entries added. """ @abstractmethod - async def importIncidentReport(self, incidentReport: IncidentReport) -> None: + async def importFieldReport(self, fieldReport: IncidentReport) -> None: """ - Import an incident and add it into the given event. + Import a field report and add it into the given event. - This differs from :meth:`IMSDataStore.createIncidentReport` in that the - incident report is added exactly as is; the incident report's number is + This differs from :meth:`IMSDataStore.createFieldReport` in that the + field report is added exactly as is; the field report's number is not modified (and must be greater than zero), and no automatic entries are added to it. """ @abstractmethod - async def setIncidentReport_summary( + async def setFieldReport_summary( self, eventID: str, - incidentReportNumber: int, + fieldReportNumber: int, summary: str, author: str, ) -> None: """ - Set the summary for the incident report with the given number. + Set the summary for the field report with the given number. """ @abstractmethod - async def addReportEntriesToIncidentReport( + async def addReportEntriesToFieldReport( self, eventID: str, - incidentReportNumber: int, + fieldReportNumber: int, reportEntries: Iterable[ReportEntry], author: str, ) -> None: """ - Add the given report entries to incident report with the given number. + Add the given report entries to field report with the given number. """ ### @@ -391,36 +389,36 @@ async def addReportEntriesToIncidentReport( ### @abstractmethod - async def incidentReportsAttachedToIncident( + async def fieldReportsAttachedToIncident( self, eventID: str, incidentNumber: int ) -> Iterable[IncidentReport]: """ - Look up all incident reports attached to the incident with the given + Look up all field reports attached to the incident with the given number in the given event. """ @abstractmethod - async def attachIncidentReportToIncident( + async def attachFieldReportToIncident( self, - incidentReportNumber: int, + fieldReportNumber: int, eventID: str, incidentNumber: int, author: str, ) -> None: """ - Attach the incident report with the given number to the incident with + Attach the field report with the given number to the incident with the given number in the given event. """ @abstractmethod - async def detachIncidentReportFromIncident( + async def detachFieldReportFromIncident( self, - incidentReportNumber: int, + fieldReportNumber: int, eventID: str, incidentNumber: int, author: str, ) -> None: """ - Detach the incident report with the given number from the incident with + Detach the field report with the given number from the incident with the given number in the given event. """ diff --git a/src/ims/store/_db.py b/src/ims/store/_db.py index 282524495..12008fda7 100644 --- a/src/ims/store/_db.py +++ b/src/ims/store/_db.py @@ -45,8 +45,8 @@ from ._abc import IMSDataStore from ._exceptions import ( + NoSuchFieldReportError, NoSuchIncidentError, - NoSuchIncidentReportError, StorageError, ) @@ -115,18 +115,18 @@ class Queries: setIncident_locationDescription: Query clearIncidentRangers: Query clearIncidentIncidentTypes: Query - incidentReport: Query - incidentReport_reportEntries: Query - incidentReportNumbers: Query - maxIncidentReportNumber: Query - incidentReports: Query - incidentReports_reportEntries: Query - createIncidentReport: Query - attachReportEntryToIncidentReport: Query - setIncidentReport_summary: Query - attachIncidentReportToIncident: Query - detachedIncidentReportNumbers: Query - attachedIncidentReportNumbers: Query + fieldReport: Query + fieldReport_reportEntries: Query + fieldReportNumbers: Query + maxFieldReportNumber: Query + fieldReports: Query + fieldReports_reportEntries: Query + createFieldReport: Query + attachReportEntryToFieldReport: Query + setFieldReport_summary: Query + attachFieldReportToIncident: Query + detachedFieldReportNumbers: Query + attachedFieldReportNumbers: Query @frozen(kw_only=True) @@ -554,7 +554,7 @@ async def createConcentricStreet(self, eventID: str, id: str, name: str) -> None async def detachedReportEntries(self) -> Iterable[ReportEntry]: """ Look up all report entries that are not attached to either an incident - or an incident report. + or a field report. There shouldn't be any of these; so if there are any, it's an indication of a bug. """ @@ -623,9 +623,9 @@ def _fetchIncidents( incidentTypes = ( loads(str(row["INCIDENT_TYPES"])) if row["INCIDENT_TYPES"] else [] ) - incidentReportNumbers = [] + fieldReportNumbers = [] if row["INCIDENT_REPORT_NUMBERS"]: - incidentReportNumbers = [ + fieldReportNumbers = [ int(val) for val in loads(str(row["INCIDENT_REPORT_NUMBERS"])) ] incidentNumber = cast(int, row["NUMBER"]) @@ -655,7 +655,7 @@ def _fetchIncidents( reportEntries=cast( Iterable[ReportEntry], reportEntries[incidentNumber] ), - incidentReportNumbers=cast(Iterable[int], incidentReportNumbers), + incidentReportNumbers=cast(Iterable[int], fieldReportNumbers), ) ) return results @@ -704,7 +704,7 @@ def notFound() -> NoReturn: else: concentric = str(row["LOCATION_CONCENTRIC"]) - incidentReportNumbers = self._fetchAttachedIncidentReportNumbers( + fieldReportNumbers = self._fetchAttachedFieldReportNumbers( txn, eventID, incidentNumber ) @@ -727,7 +727,7 @@ def notFound() -> NoReturn: rangerHandles=cast(Iterable[str], rangerHandles), incidentTypes=cast(Iterable[str], incidentTypes), reportEntries=cast(Iterable[ReportEntry], reportEntries), - incidentReportNumbers=incidentReportNumbers, + incidentReportNumbers=fieldReportNumbers, ) def _fetchIncidentNumbers(self, txn: Transaction, eventID: str) -> Iterable[int]: @@ -1439,10 +1439,10 @@ def addReportEntriesToIncident(txn: Transaction) -> None: self._notifyIncidentUpdate(eventID, incidentNumber) ### - # Incident Reports + # Field Reports ### - def _fetchIncidentReports( + def _fetchFieldReports( self, txn: Transaction, eventID: str, excludeSystemEntries: bool ) -> Iterable[IncidentReport]: parameters: Parameters = { @@ -1451,13 +1451,13 @@ def _fetchIncidentReports( "generatedLTE": 0 if excludeSystemEntries else 1, } - txn.execute(self.query.incidentReports_reportEntries.text, parameters) + txn.execute(self.query.fieldReports_reportEntries.text, parameters) - # incident report number -> report entry + # field report number -> report entry reports = defaultdict[int, list[ReportEntry]](list) for row in txn.fetchall(): - incidentReportNumber = cast(int, row["INCIDENT_REPORT_NUMBER"]) - reports[incidentReportNumber].append( + fieldReportNumber = cast(int, row["INCIDENT_REPORT_NUMBER"]) + reports[fieldReportNumber].append( ReportEntry( created=self.fromDateTimeValue(row["CREATED"]), author=cast(str, row["AUTHOR"]), @@ -1467,39 +1467,37 @@ def _fetchIncidentReports( ) results = list[IncidentReport]() - txn.execute(self.query.incidentReports.text, parameters) + txn.execute(self.query.fieldReports.text, parameters) for row in txn.fetchall(): - incidentReportNumber = cast(int, row["NUMBER"]) + fieldReportNumber = cast(int, row["NUMBER"]) results.append( IncidentReport( eventID=eventID, - number=incidentReportNumber, + number=fieldReportNumber, created=self.fromDateTimeValue(row["CREATED"]), summary=cast(Optional[str], row["SUMMARY"]), incidentNumber=cast(Optional[int], row["INCIDENT_NUMBER"]), reportEntries=cast( - Iterable[ReportEntry], reports[incidentReportNumber] + Iterable[ReportEntry], reports[fieldReportNumber] ), ) ) return tuple[IncidentReport, ...](r for r in results) - def _fetchIncidentReport( - self, txn: Transaction, eventID: str, incidentReportNumber: int + def _fetchFieldReport( + self, txn: Transaction, eventID: str, fieldReportNumber: int ) -> IncidentReport: parameters: Parameters = { "eventID": eventID, - "incidentReportNumber": incidentReportNumber, + "incidentReportNumber": fieldReportNumber, } def notFound() -> NoReturn: - raise NoSuchIncidentReportError( - f"No incident report #{incidentReportNumber}" - ) + raise NoSuchFieldReportError(f"No field report #{fieldReportNumber}") try: - txn.execute(self.query.incidentReport.text, parameters) + txn.execute(self.query.fieldReport.text, parameters) except OverflowError: notFound() @@ -1507,7 +1505,7 @@ def notFound() -> NoReturn: if row is None: notFound() - txn.execute(self.query.incidentReport_reportEntries.text, parameters) + txn.execute(self.query.fieldReport_reportEntries.text, parameters) reportEntries = tuple( ReportEntry( @@ -1521,67 +1519,63 @@ def notFound() -> NoReturn: return IncidentReport( eventID=eventID, - number=incidentReportNumber, + number=fieldReportNumber, created=self.fromDateTimeValue(row["CREATED"]), summary=cast(Optional[str], row["SUMMARY"]), incidentNumber=cast(Optional[int], row["INCIDENT_NUMBER"]), reportEntries=cast(Iterable[ReportEntry], reportEntries), ) - def _fetchIncidentReportNumbers( - self, txn: Transaction, eventID: str - ) -> Iterable[int]: - txn.execute(self.query.incidentReportNumbers.text, {"eventID": eventID}) + def _fetchFieldReportNumbers(self, txn: Transaction, eventID: str) -> Iterable[int]: + txn.execute(self.query.fieldReportNumbers.text, {"eventID": eventID}) return (cast(int, row["NUMBER"]) for row in txn.fetchall()) - async def incidentReports( + async def fieldReports( self, eventID: str, excludeSystemEntries: bool = False ) -> Iterable[IncidentReport]: """ - See :meth:`IMSDataStore.incidentReports`. + See :meth:`IMSDataStore.fieldReports`. """ - def incidentReports(txn: Transaction) -> Iterable[IncidentReport]: - return self._fetchIncidentReports( + def fieldReports(txn: Transaction) -> Iterable[IncidentReport]: + return self._fetchFieldReports( txn, eventID, excludeSystemEntries=excludeSystemEntries ) try: - return await self.runInteraction(incidentReports) - except NoSuchIncidentReportError: + return await self.runInteraction(fieldReports) + except NoSuchFieldReportError: raise except StorageError as e: self._log.critical( - "Unable to look up incident reports: {error}", + "Unable to look up field reports: {error}", error=e, ) raise - async def incidentReportWithNumber( - self, eventID: str, number: int - ) -> IncidentReport: + async def fieldReportWithNumber(self, eventID: str, number: int) -> IncidentReport: """ - See :meth:`IMSDataStore.incidentReportWithNumber`. + See :meth:`IMSDataStore.fieldReportWithNumber`. """ - def incidentReportWithNumber(txn: Transaction) -> IncidentReport: - return self._fetchIncidentReport(txn, eventID, number) + def fieldReportWithNumber(txn: Transaction) -> IncidentReport: + return self._fetchFieldReport(txn, eventID, number) try: - return await self.runInteraction(incidentReportWithNumber) + return await self.runInteraction(fieldReportWithNumber) except StorageError as e: self._log.critical( - "Unable to look up incident report #{number}: {error}", + "Unable to look up field report #{number}: {error}", number=number, error=e, ) raise - def _nextIncidentReportNumber(self, txn: Transaction) -> int: + def _nextFieldReportNumber(self, txn: Transaction) -> int: """ - Look up the next available incident report number. + Look up the next available field report number. """ - txn.execute(self.query.maxIncidentReportNumber.text) + txn.execute(self.query.maxFieldReportNumber.text) row = txn.fetchone() assert row is not None number = cast(Optional[int], row["max(NUMBER)"]) @@ -1589,10 +1583,10 @@ def _nextIncidentReportNumber(self, txn: Transaction) -> int: return 1 return number + 1 - def _createAndAttachReportEntriesToIncidentReport( + def _createAndAttachReportEntriesToFieldReport( self, eventID: str, - incidentReportNumber: int, + fieldReportNumber: int, reportEntries: Iterable[ReportEntry], txn: Transaction, ) -> None: @@ -1603,154 +1597,154 @@ def _createAndAttachReportEntriesToIncidentReport( # Join to incident txn.execute( - self.query.attachReportEntryToIncidentReport.text, + self.query.attachReportEntryToFieldReport.text, { "eventID": eventID, - "incidentReportNumber": incidentReportNumber, + "incidentReportNumber": fieldReportNumber, "reportEntryID": txn.lastrowid, }, ) self._log.info( - "Attached report entries to incident report " - "{eventID}#{incidentReportNumber}: {reportEntries}", + "Attached report entries to field report " + "{eventID}#{fieldReportNumber}: {reportEntries}", storeWriteClass=IncidentReport, eventID=eventID, - incidentReportNumber=incidentReportNumber, + fieldReportNumber=fieldReportNumber, reportEntries=reportEntries, ) - async def _createIncidentReport( + async def _createFieldReport( self, - incidentReport: IncidentReport, + fieldReport: IncidentReport, author: str | None, directImport: bool, ) -> IncidentReport: if directImport: if author is not None: - raise ValueError("Incident report author may not be specified.") - if incidentReport.number <= 0: - raise ValueError("Incident report number must be greater than zero.") + raise ValueError("Field report author may not be specified.") + if fieldReport.number <= 0: + raise ValueError("Field report number must be greater than zero.") else: if not author: - raise ValueError("Incident report author is required.") - if incidentReport.number != 0: - raise ValueError("Incident report number must be zero.") + raise ValueError("Field report author is required.") + if fieldReport.number != 0: + raise ValueError("Field report number must be zero.") - for reportEntry in incidentReport.reportEntries: + for reportEntry in fieldReport.reportEntries: assert not reportEntry.automatic # Add initial report entries - reportEntries = tuple(self._initialReportEntries(incidentReport, author)) - incidentReport = incidentReport.replace( - reportEntries=(reportEntries + tuple(incidentReport.reportEntries)) + reportEntries = tuple(self._initialReportEntries(fieldReport, author)) + fieldReport = fieldReport.replace( + reportEntries=(reportEntries + tuple(fieldReport.reportEntries)) ) - def createIncidentReport( - txn: Transaction, incidentReport: IncidentReport = incidentReport + def createFieldReport( + txn: Transaction, fieldReport: IncidentReport = fieldReport ) -> IncidentReport: if not directImport: # Assign the incident number a number - number = self._nextIncidentReportNumber(txn) - incidentReport = incidentReport.replace(number=number) + number = self._nextFieldReportNumber(txn) + fieldReport = fieldReport.replace(number=number) # Write incident row - created = self.asDateTimeValue(incidentReport.created) + created = self.asDateTimeValue(fieldReport.created) txn.execute( - self.query.createIncidentReport.text, + self.query.createFieldReport.text, { - "eventID": incidentReport.eventID, - "incidentReportNumber": incidentReport.number, + "eventID": fieldReport.eventID, + "incidentReportNumber": fieldReport.number, "incidentReportCreated": created, - "incidentReportSummary": incidentReport.summary, - "incidentNumber": incidentReport.incidentNumber, + "incidentReportSummary": fieldReport.summary, + "incidentNumber": fieldReport.incidentNumber, }, ) # Add report entries - self._createAndAttachReportEntriesToIncidentReport( - incidentReport.eventID, - incidentReport.number, - incidentReport.reportEntries, + self._createAndAttachReportEntriesToFieldReport( + fieldReport.eventID, + fieldReport.number, + fieldReport.reportEntries, txn, ) - return incidentReport + return fieldReport try: - incidentReport = await self.runInteraction(createIncidentReport) + fieldReport = await self.runInteraction(createFieldReport) except StorageError as e: self._log.critical( - "Unable to create incident report {incidentReport}: {error}", - incidentReport=incidentReport, + "Unable to create field report {fieldReport}: {error}", + fieldReport=fieldReport, author=author, error=e, ) raise self._log.info( - "Created incident report: {incidentReport}", + "Created field report: {fieldReport}", storeWriteClass=IncidentReport, - incidentReport=incidentReport, + fieldReport=fieldReport, ) - return incidentReport + return fieldReport - async def createIncidentReport( - self, incidentReport: IncidentReport, author: str + async def createFieldReport( + self, fieldReport: IncidentReport, author: str ) -> IncidentReport: """ - See :meth:`IMSDataStore.createIncidentReport`. + See :meth:`IMSDataStore.createFieldReport`. """ - return await self._createIncidentReport( - incidentReport, author=author, directImport=False + return await self._createFieldReport( + fieldReport, author=author, directImport=False ) - async def importIncidentReport(self, incidentReport: IncidentReport) -> None: + async def importFieldReport(self, fieldReport: IncidentReport) -> None: """ - See :meth:`IMSDataStore.importIncidentReport`. + See :meth:`IMSDataStore.importFieldReport`. """ - await self._createIncidentReport(incidentReport, author=None, directImport=True) + await self._createFieldReport(fieldReport, author=None, directImport=True) - async def _setIncidentReportAttribute( + async def _setFieldReportAttribute( self, query: str, eventID: str, - incidentReportNumber: int, + fieldReportNumber: int, attribute: str, value: ParameterValue, author: str, ) -> None: autoEntry = self._automaticReportEntry(author, now(), attribute, value) - def setIncidentReportAttribute(txn: Transaction) -> None: + def setFieldReportAttribute(txn: Transaction) -> None: txn.execute( query, { "eventID": eventID, - "incidentReportNumber": incidentReportNumber, + "incidentReportNumber": fieldReportNumber, "value": value, }, ) # Add report entries - self._createAndAttachReportEntriesToIncidentReport( + self._createAndAttachReportEntriesToFieldReport( eventID, - incidentReportNumber, + fieldReportNumber, (autoEntry,), txn, ) try: - await self.runInteraction(setIncidentReportAttribute) + await self.runInteraction(setFieldReportAttribute) except StorageError as e: self._log.critical( - "Author {author} unable to update incident report " - "{eventID}#{incidentReportNumber} " + "Author {author} unable to update field report " + "{eventID}#{fieldReportNumber} " "({attribute}={value}): {error}", query=query, eventID=eventID, - incidentReportNumber=incidentReportNumber, + fieldReportNumber=fieldReportNumber, attribute=attribute, value=value, author=author, @@ -1759,45 +1753,45 @@ def setIncidentReportAttribute(txn: Transaction) -> None: raise self._log.info( - "{author} updated incident report #{incidentReportNumber}: " + "{author} updated field report #{fieldReportNumber}: " "{attribute}={value}", storeWriteClass=IncidentReport, query=query, eventID=eventID, - incidentReportNumber=incidentReportNumber, + fieldReportNumber=fieldReportNumber, attribute=attribute, value=value, author=author, ) - async def setIncidentReport_summary( + async def setFieldReport_summary( self, eventID: str, - incidentReportNumber: int, + fieldReportNumber: int, summary: str, author: str, ) -> None: """ - See :meth:`IMSDataStore.setIncidentReport_summary`. + See :meth:`IMSDataStore.setFieldReport_summary`. """ - await self._setIncidentReportAttribute( - self.query.setIncidentReport_summary.text, + await self._setFieldReportAttribute( + self.query.setFieldReport_summary.text, eventID, - incidentReportNumber, + fieldReportNumber, "summary", summary, author, ) - async def addReportEntriesToIncidentReport( + async def addReportEntriesToFieldReport( self, eventID: str, - incidentReportNumber: int, + fieldReportNumber: int, reportEntries: Iterable[ReportEntry], author: str, ) -> None: """ - See :meth:`IMSDataStore.addReportEntriesToIncidentReport`. + See :meth:`IMSDataStore.addReportEntriesToFieldReport`. """ reportEntries = tuple(reportEntries) @@ -1811,72 +1805,70 @@ async def addReportEntriesToIncidentReport( if reportEntry.author != author: raise ValueError(f"Report entry {reportEntry} has author != {author}") - def addReportEntriesToIncidentReport(txn: Transaction) -> None: - self._createAndAttachReportEntriesToIncidentReport( - eventID, incidentReportNumber, reportEntries, txn + def addReportEntriesToFieldReport(txn: Transaction) -> None: + self._createAndAttachReportEntriesToFieldReport( + eventID, fieldReportNumber, reportEntries, txn ) try: - await self.runInteraction(addReportEntriesToIncidentReport) + await self.runInteraction(addReportEntriesToFieldReport) except StorageError as e: self._log.critical( "Author {author} unable to create report entries " - "{reportEntries} to incident report " - "{eventID}#{incidentReportNumber}: {error}", + "{reportEntries} to field report " + "{eventID}#{fieldReportNumber}: {error}", author=author, reportEntries=reportEntries, - incidentReportNumber=incidentReportNumber, + fieldReportNumber=fieldReportNumber, eventID=eventID, error=e, ) raise ### - # Incident to Incident Report Relationships + # Incident to Field Report Relationships ### - def _fetchDetachedIncidentReportNumbers( + def _fetchDetachedFieldReportNumbers( self, txn: Transaction, eventID: str ) -> Iterable[int]: txn.execute( - self.query.detachedIncidentReportNumbers.text, + self.query.detachedFieldReportNumbers.text, {"eventID": eventID}, ) return (cast(int, row["NUMBER"]) for row in txn.fetchall()) - def _fetchAttachedIncidentReportNumbers( + def _fetchAttachedFieldReportNumbers( self, txn: Transaction, eventID: str, incidentNumber: int ) -> Iterable[int]: txn.execute( - self.query.attachedIncidentReportNumbers.text, + self.query.attachedFieldReportNumbers.text, {"eventID": eventID, "incidentNumber": incidentNumber}, ) return (cast(int, row["NUMBER"]) for row in txn.fetchall()) - async def incidentReportsAttachedToIncident( + async def fieldReportsAttachedToIncident( self, eventID: str, incidentNumber: int ) -> Iterable[IncidentReport]: """ - See :meth:`IMSDataStore.attachedIncidentReports`. + See :meth:`IMSDataStore.attachedFieldReports`. """ - def incidentReportsAttachedToIncident( + def fieldReportsAttachedToIncident( txn: Transaction, ) -> Iterable[IncidentReport]: return tuple( - self._fetchIncidentReport(txn, eventID, number) + self._fetchFieldReport(txn, eventID, number) for number in tuple( - self._fetchAttachedIncidentReportNumbers( - txn, eventID, incidentNumber - ) + self._fetchAttachedFieldReportNumbers(txn, eventID, incidentNumber) ) ) try: - return await self.runInteraction(incidentReportsAttachedToIncident) + return await self.runInteraction(fieldReportsAttachedToIncident) except StorageError as e: self._log.critical( - "Unable to look up incident reports attached to incident " + "Unable to look up field reports attached to incident " "#{incidentNumber} in event {eventID}: {error}", incidentNumber=incidentNumber, eventID=eventID, @@ -1884,39 +1876,39 @@ def incidentReportsAttachedToIncident( ) raise - async def attachIncidentReportToIncident( + async def attachFieldReportToIncident( self, - incidentReportNumber: int, + fieldReportNumber: int, eventID: str, incidentNumber: int, author: str, ) -> None: """ - See :meth:`IMSDataStore.attachIncidentReportToIncident`. + See :meth:`IMSDataStore.attachFieldReportToIncident`. """ - await self._setIncidentReportAttribute( - self.query.attachIncidentReportToIncident.text, + await self._setFieldReportAttribute( + self.query.attachFieldReportToIncident.text, eventID, - incidentReportNumber, + fieldReportNumber, "incident_number", incidentNumber, author, ) - async def detachIncidentReportFromIncident( + async def detachFieldReportFromIncident( self, - incidentReportNumber: int, + fieldReportNumber: int, eventID: str, incidentNumber: int, author: str, ) -> None: """ - See :meth:`IMSDataStore.detachIncidentReportFromIncident`. + See :meth:`IMSDataStore.detachFieldReportFromIncident`. """ - await self._setIncidentReportAttribute( - self.query.attachIncidentReportToIncident.text, + await self._setFieldReportAttribute( + self.query.attachFieldReportToIncident.text, eventID, - incidentReportNumber, + fieldReportNumber, "incident_number", None, author, diff --git a/src/ims/store/_exceptions.py b/src/ims/store/_exceptions.py index 8fde76063..d03d79c41 100644 --- a/src/ims/store/_exceptions.py +++ b/src/ims/store/_exceptions.py @@ -41,7 +41,7 @@ class NoSuchIncidentError(StorageError): @mutable -class NoSuchIncidentReportError(StorageError): +class NoSuchFieldReportError(StorageError): """ - No such incident. + No such field report. """ diff --git a/src/ims/store/export/_json.py b/src/ims/store/export/_json.py index 21f047c90..cd23a133b 100644 --- a/src/ims/store/export/_json.py +++ b/src/ims/store/export/_json.py @@ -108,7 +108,7 @@ async def _eventData(self, event: Event) -> EventData: concentricStreets = await self.store.concentricStreets(event.id) incidents = await self.store.incidents(event.id) - incidentReports = await self.store.incidentReports(event.id) + incidentReports = await self.store.fieldReports(event.id) return EventData( event=event, @@ -227,7 +227,7 @@ async def _storeIncidentReports(self, eventData: EventData) -> None: existingIncidentReportNumbers = frozenset( incidentReport.number - for incidentReport in await store.incidentReports(eventData.event.id) + for incidentReport in await store.fieldReports(eventData.event.id) ) for incidentReport in eventData.incidentReports: @@ -239,7 +239,7 @@ async def _storeIncidentReports(self, eventData: EventData) -> None: number=incidentReport.number, ) else: - await store.importIncidentReport(incidentReport) + await store.importFieldReport(incidentReport) async def storeData(self) -> None: store = self.store diff --git a/src/ims/store/export/test/test_json.py b/src/ims/store/export/test/test_json.py index ea12b206d..a52c55710 100644 --- a/src/ims/store/export/test/test_json.py +++ b/src/ims/store/export/test/test_json.py @@ -217,7 +217,7 @@ def test_storeData(self, imsDataIn: IMSData) -> None: ), concentricStreets=resultOf(store.concentricStreets(event.id)), incidents=resultOf(store.incidents(event.id)), - incidentReports=resultOf(store.incidentReports(event.id)), + incidentReports=resultOf(store.fieldReports(event.id)), ) for event in resultOf(store.events()) ), diff --git a/src/ims/store/mysql/_queries.py b/src/ims/store/mysql/_queries.py index ab8ca3487..46058b9c1 100644 --- a/src/ims/store/mysql/_queries.py +++ b/src/ims/store/mysql/_queries.py @@ -389,14 +389,14 @@ EVENT = ({query_eventID}) and INCIDENT_NUMBER = %(incidentNumber)s """, ), - incidentReport=Query( + fieldReport=Query( "look up incident report", f""" select CREATED, SUMMARY, INCIDENT_NUMBER from INCIDENT_REPORT where EVENT = ({query_eventID}) and NUMBER = %(incidentReportNumber)s """, ), - incidentReport_reportEntries=Query( + fieldReport_reportEntries=Query( "look up report entries for incident report", f""" select AUTHOR, TEXT, CREATED, GENERATED from REPORT_ENTRY @@ -408,20 +408,20 @@ ) """, ), - incidentReportNumbers=Query( + fieldReportNumbers=Query( "look up incident report numbers for event", f""" select NUMBER from INCIDENT_REPORT where EVENT = ({query_eventID}) """, ), - maxIncidentReportNumber=Query( + maxFieldReportNumber=Query( "look up maximum incident report number", """ select max(NUMBER) from INCIDENT_REPORT """, ), - incidentReports=Query( + fieldReports=Query( "look up all incident reports for an event", f""" select @@ -435,7 +435,7 @@ EVENT = ({query_eventID}) """, ), - incidentReports_reportEntries=Query( + fieldReports_reportEntries=Query( "look up all incident report report entries for an event", f""" select @@ -454,7 +454,7 @@ and re.GENERATED <= %(generatedLTE)s """, ), - createIncidentReport=Query( + createFieldReport=Query( "create incident report", f""" insert into INCIDENT_REPORT ( @@ -469,7 +469,7 @@ ) """, ), - attachReportEntryToIncidentReport=Query( + attachReportEntryToFieldReport=Query( "add report entry to incident report", f""" insert into INCIDENT_REPORT__REPORT_ENTRY ( @@ -478,22 +478,22 @@ values (({query_eventID}), %(incidentReportNumber)s, %(reportEntryID)s) """, ), - setIncidentReport_summary=Query( + setFieldReport_summary=Query( "set incident report summary", template_setIncidentReportAttribute.format(column="SUMMARY"), ), - attachIncidentReportToIncident=Query( + attachFieldReportToIncident=Query( "attach incident report to incident", template_setIncidentReportAttribute.format(column="INCIDENT_NUMBER"), ), - detachedIncidentReportNumbers=Query( + detachedFieldReportNumbers=Query( "look up detached incident report numbers", f""" select NUMBER from INCIDENT_REPORT where EVENT = ({query_eventID}) and INCIDENT_NUMBER is null """, ), - attachedIncidentReportNumbers=Query( + attachedFieldReportNumbers=Query( "look up attached incident report numbers", f""" select NUMBER from INCIDENT_REPORT diff --git a/src/ims/store/mysql/test/test_store.py b/src/ims/store/mysql/test/test_store.py index 5beecfb86..8ad0f3798 100644 --- a/src/ims/store/mysql/test/test_store.py +++ b/src/ims/store/mysql/test/test_store.py @@ -30,7 +30,7 @@ DataStoreIncidentTests as SuperDataStoreIncidentTests, ) from ...test.report import ( - DataStoreIncidentReportTests as SuperDataStoreIncidentReportTests, + DataStoreFieldReportTests as SuperDataStoreIncidentReportTests, ) from ...test.street import ( DataStoreConcentricStreetTests as SuperDataStoreConcentricStreetTests, diff --git a/src/ims/store/sqlite/_queries.py b/src/ims/store/sqlite/_queries.py index d0adb6af2..2e0267fe2 100644 --- a/src/ims/store/sqlite/_queries.py +++ b/src/ims/store/sqlite/_queries.py @@ -381,14 +381,14 @@ where EVENT = ({query_eventID}) and INCIDENT_NUMBER = :incidentNumber """, ), - incidentReport=Query( + fieldReport=Query( "look up incident report", f""" select CREATED, SUMMARY, INCIDENT_NUMBER from INCIDENT_REPORT where EVENT = ({query_eventID}) and NUMBER = :incidentReportNumber """, ), - incidentReport_reportEntries=Query( + fieldReport_reportEntries=Query( "look up report entries for incident report", f""" select AUTHOR, TEXT, CREATED, GENERATED from REPORT_ENTRY @@ -400,20 +400,20 @@ ) """, ), - incidentReportNumbers=Query( + fieldReportNumbers=Query( "look up incident report numbers for event", f""" select NUMBER from INCIDENT_REPORT where EVENT = ({query_eventID}) """, ), - maxIncidentReportNumber=Query( + maxFieldReportNumber=Query( "look up maximum incident report number", """ select max(NUMBER) from INCIDENT_REPORT """, ), - incidentReports=Query( + fieldReports=Query( "look up all incident reports for an event", f""" select @@ -427,7 +427,7 @@ EVENT = ({query_eventID}) """, ), - incidentReports_reportEntries=Query( + fieldReports_reportEntries=Query( "look up all incident report report entries for an event", f""" select @@ -446,7 +446,7 @@ and re.GENERATED <= :generatedLTE """, ), - createIncidentReport=Query( + createFieldReport=Query( "create incident report", f""" insert into INCIDENT_REPORT ( @@ -461,7 +461,7 @@ ) """, ), - attachReportEntryToIncidentReport=Query( + attachReportEntryToFieldReport=Query( "add report entry to incident report", f""" insert into INCIDENT_REPORT__REPORT_ENTRY ( @@ -470,22 +470,22 @@ values (({query_eventID}), :incidentReportNumber, :reportEntryID) """, ), - setIncidentReport_summary=Query( + setFieldReport_summary=Query( "set incident report summary", template_setIncidentReportAttribute.format(column="SUMMARY"), ), - attachIncidentReportToIncident=Query( + attachFieldReportToIncident=Query( "attach incident report to incident", template_setIncidentReportAttribute.format(column="INCIDENT_NUMBER"), ), - detachedIncidentReportNumbers=Query( + detachedFieldReportNumbers=Query( "look up detached incident report numbers", f""" select NUMBER from INCIDENT_REPORT where EVENT = ({query_eventID}) and INCIDENT_NUMBER is null """, ), - attachedIncidentReportNumbers=Query( + attachedFieldReportNumbers=Query( "look up attached incident report numbers", f""" select NUMBER from INCIDENT_REPORT diff --git a/src/ims/store/sqlite/test/test_store.py b/src/ims/store/sqlite/test/test_store.py index 4964b3f22..3147d2604 100644 --- a/src/ims/store/sqlite/test/test_store.py +++ b/src/ims/store/sqlite/test/test_store.py @@ -28,7 +28,7 @@ DataStoreIncidentTests as SuperDataStoreIncidentTests, ) from ...test.report import ( - DataStoreIncidentReportTests as SuperDataStoreIncidentReportTests, + DataStoreFieldReportTests as SuperDataStoreIncidentReportTests, ) from ...test.street import ( DataStoreConcentricStreetTests as SuperDataStoreConcentricStreetTests, diff --git a/src/ims/store/sqlite/test/test_store_core.py b/src/ims/store/sqlite/test/test_store_core.py index c943b81f0..2ae922822 100644 --- a/src/ims/store/sqlite/test/test_store_core.py +++ b/src/ims/store/sqlite/test/test_store_core.py @@ -167,7 +167,7 @@ def test_printQueries(self) -> None: r" \[None,None\] You did not supply a value for binding" r".*\.", # * because Py <3.10: "1"; Py 3.10: "parameter :eventID" r"", - r"attachIncidentReportToIncident:", + r"attachFieldReportToIncident:", r"", r" -- query --", r"", diff --git a/src/ims/store/test/base.py b/src/ims/store/test/base.py index a3108a9b5..fbf985f0d 100644 --- a/src/ims/store/test/base.py +++ b/src/ims/store/test/base.py @@ -70,9 +70,9 @@ async def storeIncident(self, incident: Incident) -> None: """ @abstractmethod - async def storeIncidentReport(self, incidentReport: IncidentReport) -> None: + async def storeFieldReport(self, fieldReport: IncidentReport) -> None: """ - Store the given incident report in the test store. + Store the given field report in the test store. """ @abstractmethod diff --git a/src/ims/store/test/database.py b/src/ims/store/test/database.py index 895db4e4d..038f30f08 100644 --- a/src/ims/store/test/database.py +++ b/src/ims/store/test/database.py @@ -186,7 +186,7 @@ def _storeIncidentReport( ) txn.execute( - store.query.createIncidentReport.text, + store.query.createFieldReport.text, { "eventID": incidentReport.eventID, "incidentReportNumber": incidentReport.number, @@ -207,14 +207,14 @@ def _storeIncidentReport( }, ) txn.execute( - store.query.attachReportEntryToIncidentReport.text, + store.query.attachReportEntryToFieldReport.text, { "incidentReportNumber": incidentReport.number, "reportEntryID": txn.lastrowid, }, ) - async def storeIncidentReport(self, incidentReport: IncidentReport) -> None: + async def storeFieldReport(self, incidentReport: IncidentReport) -> None: """ Store the given incident report in the test store. """ diff --git a/src/ims/store/test/report.py b/src/ims/store/test/report.py index 106da29f7..dc3524c34 100644 --- a/src/ims/store/test/report.py +++ b/src/ims/store/test/report.py @@ -29,7 +29,7 @@ from ims.ext.trial import asyncAsDeferred from ims.model import Event, IncidentReport, ReportEntry -from .._exceptions import NoSuchIncidentReportError, StorageError +from .._exceptions import NoSuchFieldReportError, StorageError from .base import DataStoreTests, TestDataStoreABC from .incident import anEvent, anIncident1, aReportEntry @@ -41,7 +41,7 @@ # don't have timestamps that are within the time resolution of some back-end # data stores. -aNewIncidentReport = IncidentReport( +aNewFieldReport = IncidentReport( eventID=anEvent.id, number=0, created=DateTime.now(TimeZone.utc) + TimeDelta(seconds=1), @@ -50,7 +50,7 @@ reportEntries=(), ) -anIncidentReport1 = IncidentReport( +aFieldReport1 = IncidentReport( eventID=anEvent.id, number=1, created=DateTime.now(TimeZone.utc) + TimeDelta(seconds=2), @@ -59,7 +59,7 @@ reportEntries=(), ) -anIncidentReport2 = IncidentReport( +aFieldReport2 = IncidentReport( eventID=anEvent.id, number=2, created=DateTime.now(TimeZone.utc) + TimeDelta(seconds=3), @@ -83,147 +83,145 @@ ) -class DataStoreIncidentReportTests(DataStoreTests): +class DataStoreFieldReportTests(DataStoreTests): """ - Tests for :class:`DataStore` incident report access. + Tests for :class:`DataStore` field report access. """ @asyncAsDeferred - async def test_incidentReports(self) -> None: + async def test_fieldReports(self) -> None: """ - :meth:`DataStore.incidentReports` returns all incident reports attached + :meth:`DataStore.fieldReports` returns all field reports attached to an incident in an event. """ - for _incidentReports in ( + for _fieldReports in ( (), - (anIncidentReport1,), - (anIncidentReport1, anIncidentReport2), + (aFieldReport1,), + (aFieldReport1, aFieldReport2), ): - incidentReports = cast(Iterable[IncidentReport], _incidentReports) - incidentReportsByNumber = { + fieldReports = cast(Iterable[IncidentReport], _fieldReports) + fieldReportsByNumber = { r.number: r.replace(incidentNumber=anIncident1.number) - for r in incidentReports + for r in fieldReports } store = await self.store() await store.storeIncident(anIncident1) - for incidentReport in incidentReports: - await store.storeIncidentReport(incidentReport) - await store.attachIncidentReportToIncident( - incidentReport.number, + for fieldReport in fieldReports: + await store.storeFieldReport(fieldReport) + await store.attachFieldReportToIncident( + fieldReport.number, anIncident1.eventID, anIncident1.number, "HubCap", ) found: set[int] = set() - for retrieved in await store.incidentReports(anIncident1.eventID): - self.assertIn(retrieved.number, incidentReportsByNumber) - self.assertIncidentReportsEqual( + for retrieved in await store.fieldReports(anIncident1.eventID): + self.assertIn(retrieved.number, fieldReportsByNumber) + self.assertFieldReportsEqual( store, retrieved, - incidentReportsByNumber[retrieved.number], + fieldReportsByNumber[retrieved.number], ignoreAutomatic=True, ) found.add(retrieved.number) - self.assertEqual(found, {r.number for r in incidentReports}) + self.assertEqual(found, {r.number for r in fieldReports}) @asyncAsDeferred - async def test_incidentReports_error(self) -> None: + async def test_fieldReports_error(self) -> None: """ - :meth:`DataStore.incidentReports` raises :exc:`StorageError` when + :meth:`DataStore.fieldReports` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() store.bringThePain() try: - await store.incidentReports(anEvent.id) + await store.fieldReports(anEvent.id) except StorageError as e: self.assertEqual(str(e), store.exceptionMessage) else: self.fail("StorageError not raised") @asyncAsDeferred - async def test_incidentReportWithNumber(self) -> None: + async def test_fieldReportWithNumber(self) -> None: """ - :meth:`DataStore.incidentReportWithNumber` returns the specified - incident report. + :meth:`DataStore.fieldReportWithNumber` returns the specified + field report. """ - for incidentReport in (anIncidentReport1, anIncidentReport2): + for fieldReport in (aFieldReport1, aFieldReport2): store = await self.store() - await store.storeIncidentReport(incidentReport) + await store.storeFieldReport(fieldReport) - retrieved = await store.incidentReportWithNumber( - anEvent.id, incidentReport.number + retrieved = await store.fieldReportWithNumber( + anEvent.id, fieldReport.number ) - self.assertIncidentReportsEqual(store, retrieved, incidentReport) + self.assertFieldReportsEqual(store, retrieved, fieldReport) @asyncAsDeferred - async def test_incidentReportWithNumber_notFound(self) -> None: + async def test_fieldReportWithNumber_notFound(self) -> None: """ - :meth:`DataStore.incidentReportWithNumber` raises - :exc:`NoSuchIncidentReportError` when the given incident report number + :meth:`DataStore.fieldReportWithNumber` raises + :exc:`NoSuchFieldReportError` when the given field report number is not found. """ store = await self.store() try: - await store.incidentReportWithNumber(anEvent.id, 1) - except NoSuchIncidentReportError: + await store.fieldReportWithNumber(anEvent.id, 1) + except NoSuchFieldReportError: pass else: - self.fail("NoSuchIncidentReportError not raised") + self.fail("NoSuchFieldReportError not raised") @asyncAsDeferred - async def test_incidentReportWithNumber_tooBig(self) -> None: + async def test_fieldReportWithNumber_tooBig(self) -> None: """ - :meth:`DataStore.incidentReportWithNumber` raises - :exc:`NoSuchIncidentReportError` when the given incident report number + :meth:`DataStore.fieldReportWithNumber` raises + :exc:`NoSuchFieldReportError` when the given field report number is too large for the database. """ store = await self.store() try: - await store.incidentReportWithNumber( - anEvent.id, store.maxIncidentNumber + 1 - ) - except NoSuchIncidentReportError: + await store.fieldReportWithNumber(anEvent.id, store.maxIncidentNumber + 1) + except NoSuchFieldReportError: pass else: - self.fail("NoSuchIncidentReportError not raised") + self.fail("NoSuchFieldReportError not raised") @asyncAsDeferred - async def test_incidentReportWithNumber_error(self) -> None: + async def test_fieldReportWithNumber_error(self) -> None: """ - :meth:`DataStore.incidentReportWithNumber` raises :exc:`StorageError` - when the given incident report number is too large for the database. + :meth:`DataStore.fieldReportWithNumber` raises :exc:`StorageError` + when the given field report number is too large for the database. """ store = await self.store() store.bringThePain() try: - await store.incidentReportWithNumber(anEvent.id, 1) + await store.fieldReportWithNumber(anEvent.id, 1) except StorageError as e: self.assertEqual(str(e), store.exceptionMessage) else: self.fail("StorageError not raised") @asyncAsDeferred - async def test_createIncidentReport(self) -> None: + async def test_createFieldReport(self) -> None: """ - :meth:`DataStore.createIncidentReport` creates the given incident + :meth:`DataStore.createFieldReport` creates the given field report. """ for _data in ( (), - ((anIncidentReport1.replace(number=0), "Hubcap"),), + ((aFieldReport1.replace(number=0), "Hubcap"),), ( - (anIncidentReport1.replace(number=0), "Hubcap"), - (anIncidentReport2.replace(number=0), "Bucket"), + (aFieldReport1.replace(number=0), "Hubcap"), + (aFieldReport2.replace(number=0), "Bucket"), ), ): data = cast(Iterable[tuple[IncidentReport, str]], _data) @@ -231,68 +229,66 @@ async def test_createIncidentReport(self) -> None: store = await self.store() await store.createEvent(anEvent) - expectedStoredIncidentReports: set[IncidentReport] = set() + expectedStoredFieldReports: set[IncidentReport] = set() nextNumber = 1 - for incidentReport, author in data: - retrieved = await store.createIncidentReport( - incidentReport=incidentReport, author=author + for fieldReport, author in data: + retrieved = await store.createFieldReport( + fieldReport=fieldReport, author=author ) - expected = incidentReport.replace(number=nextNumber) + expected = fieldReport.replace(number=nextNumber) - self.assertIncidentReportsEqual( + self.assertFieldReportsEqual( store, retrieved, expected, ignoreAutomatic=True ) - expectedStoredIncidentReports.add(expected) + expectedStoredFieldReports.add(expected) nextNumber += 1 - storedIncidentReports = sorted(await store.incidentReports(anEvent.id)) + storedFieldReports = sorted(await store.fieldReports(anEvent.id)) - self.assertEqual( - len(storedIncidentReports), len(expectedStoredIncidentReports) - ) + self.assertEqual(len(storedFieldReports), len(expectedStoredFieldReports)) for stored, expected in zip( - storedIncidentReports, - sorted(expectedStoredIncidentReports), + storedFieldReports, + sorted(expectedStoredFieldReports), strict=True, ): - self.assertIncidentReportsEqual( + self.assertFieldReportsEqual( store, stored, expected, ignoreAutomatic=True ) @asyncAsDeferred - async def test_createIncidentReport_error(self) -> None: + async def test_createFieldReport_error(self) -> None: """ - :meth:`DataStore.createIncidentReport` raises :exc:`StorageError` when + :meth:`DataStore.createFieldReport` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() - await store.createEvent(Event(id=aNewIncidentReport.eventID)) + await store.createEvent(Event(id=aNewFieldReport.eventID)) store.bringThePain() try: - await store.createIncidentReport(aNewIncidentReport, "Hubcap") + await store.createFieldReport(aNewFieldReport, "Hubcap") except StorageError as e: self.assertEqual(str(e), store.exceptionMessage) else: self.fail("StorageError not raised") @asyncAsDeferred - async def test_setIncidentReport_summary_error(self) -> None: + async def test_setFieldReport_summary_error(self) -> None: """ - :meth:`DataStore.setIncident_summary` raises :exc:`StorageError` when + :meth:`DataStore.setFieldReport_summary` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) store.bringThePain() try: - await store.setIncidentReport_summary( - anIncidentReport1.eventID, - anIncidentReport1.number, + await store.setFieldReport_summary( + aFieldReport1.eventID, + aFieldReport1.number, "Never mind", "Bucket", ) @@ -301,64 +297,64 @@ async def test_setIncidentReport_summary_error(self) -> None: else: self.fail("StorageError not raised") - async def _test_setIncidentReportAttribute( + async def _test_setFieldReportAttribute( self, - incidentReport: IncidentReport, + fieldReport: IncidentReport, methodName: str, attributeName: str, value: Any, ) -> None: store = await self.store() - await store.storeIncidentReport(incidentReport) + await store.storeFieldReport(fieldReport) setter = cast( Callable[[str, int, str, str], Awaitable[None]], getattr(store, methodName), ) - await setter(incidentReport.eventID, incidentReport.number, value, "Hubcap") + await setter(fieldReport.eventID, fieldReport.number, value, "Hubcap") - retrieved = await store.incidentReportWithNumber( - incidentReport.eventID, incidentReport.number + retrieved = await store.fieldReportWithNumber( + fieldReport.eventID, fieldReport.number ) - # Replace the specified incident attribute with the given value. + # Replace the specified field report attribute with the given value. # This is a bit complex because we're recursing into sub-attributes. attrPath = attributeName.split(".") - values = [incidentReport] + values = [fieldReport] for a in attrPath[:-1]: values.append(getattr(values[-1], a)) values.append(value) for a in reversed(attrPath): v = values.pop() values[-1] = values[-1].replace(**{a: v}) - incidentReport = values[0] + fieldReport = values[0] - self.assertIncidentReportsEqual( - store, retrieved, incidentReport, ignoreAutomatic=True + self.assertFieldReportsEqual( + store, retrieved, fieldReport, ignoreAutomatic=True ) @asyncAsDeferred - async def test_setIncidentReport_summary(self) -> None: + async def test_setFieldReport_summary(self) -> None: """ - :meth:`DataStore.setIncidentReport_summary` updates the summary for the - given incident report in the data store. + :meth:`DataStore.setFieldReport_summary` updates the summary for the + given field report in the data store. """ - for incidentReport, summary in ( - (anIncidentReport1, "foo bar"), - (anIncidentReport2, ""), + for fieldReport, summary in ( + (aFieldReport1, "foo bar"), + (aFieldReport2, ""), ): - await self._test_setIncidentReportAttribute( - incidentReport, "setIncidentReport_summary", "summary", summary + await self._test_setFieldReportAttribute( + fieldReport, "setFieldReport_summary", "summary", summary ) @asyncAsDeferred - async def test_addReportEntriesToIncidentReport(self) -> None: + async def test_addReportEntriesToFieldReport(self) -> None: """ - :meth:`DataStore.addReportEntriesToIncidentReport` adds the given - report entries to the given incident report in the data store. + :meth:`DataStore.addReportEntriesToFieldReport` adds the given + report entries to the given field report in the data store. """ - incidentReport = anIncidentReport1 + fieldReport = aFieldReport1 author = "Bucket" for reportEntries in ( @@ -375,23 +371,21 @@ async def test_addReportEntriesToIncidentReport(self) -> None: # Store test data store = await self.store() - await store.storeIncidentReport(incidentReport) + await store.storeFieldReport(fieldReport) - # Fetch incident report back so we have the version from the DB - incidentReport = await store.incidentReportWithNumber( - anEvent.id, incidentReport.number + # Fetch field report back so we have the version from the DB + fieldReport = await store.fieldReportWithNumber( + anEvent.id, fieldReport.number ) - originalEntries = frozenset(incidentReport.reportEntries) + originalEntries = frozenset(fieldReport.reportEntries) # Add report entries - await store.addReportEntriesToIncidentReport( - anEvent.id, incidentReport.number, reportEntries, author + await store.addReportEntriesToFieldReport( + anEvent.id, fieldReport.number, reportEntries, author ) - # Get the updated incident report with the new report entries - updated = await store.incidentReportWithNumber( - anEvent.id, incidentReport.number - ) + # Get the updated field report with the new report entries + updated = await store.fieldReportWithNumber(anEvent.id, fieldReport.number) updatedEntries = frozenset(updated.reportEntries) # Updated entries minus the original entries == the added entries @@ -403,20 +397,20 @@ async def test_addReportEntriesToIncidentReport(self) -> None: ) @asyncAsDeferred - async def test_addReportEntriesToIncidentReport_automatic(self) -> None: + async def test_addReportEntriesToFieldReport_automatic(self) -> None: """ - :meth:`DataStore.addReportEntriesToIncidentReport` raises + :meth:`DataStore.addReportEntriesToFieldReport` raises :exc:`ValueError` when given automatic report entries. """ store = await self.store() - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) reportEntry = aReportEntry.replace(automatic=True) try: - await store.addReportEntriesToIncidentReport( - anIncidentReport1.eventID, - anIncidentReport1.number, + await store.addReportEntriesToFieldReport( + aFieldReport1.eventID, + aFieldReport1.number, (reportEntry,), reportEntry.author, ) @@ -426,21 +420,21 @@ async def test_addReportEntriesToIncidentReport_automatic(self) -> None: self.fail("ValueError not raised") @asyncAsDeferred - async def test_addReportEntriesToIncidentReport_wrongAuthor(self) -> None: + async def test_addReportEntriesToFieldReport_wrongAuthor(self) -> None: """ - :meth:`DataStore.addReportEntriesToIncidentReport` raises + :meth:`DataStore.addReportEntriesToFieldReport` raises :exc:`ValueError` when given report entries with an author that does not match the author that is adding the entries. """ store = await self.store() - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) otherAuthor = f"not{aReportEntry.author}" try: - await store.addReportEntriesToIncidentReport( - anIncidentReport1.eventID, - anIncidentReport1.number, + await store.addReportEntriesToFieldReport( + aFieldReport1.eventID, + aFieldReport1.number, (aReportEntry,), otherAuthor, ) @@ -450,19 +444,19 @@ async def test_addReportEntriesToIncidentReport_wrongAuthor(self) -> None: self.fail("ValueError not raised") @asyncAsDeferred - async def test_addReportEntriesToIncidentReport_error(self) -> None: + async def test_addReportEntriesToFieldReport_error(self) -> None: """ - :meth:`DataStore.addReportEntriesToIncidentReport` raises + :meth:`DataStore.addReportEntriesToFieldReport` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) store.bringThePain() try: - await store.addReportEntriesToIncidentReport( - anIncidentReport1.eventID, - anIncidentReport1.number, + await store.addReportEntriesToFieldReport( + aFieldReport1.eventID, + aFieldReport1.number, (aReportEntry,), aReportEntry.author, ) @@ -472,18 +466,18 @@ async def test_addReportEntriesToIncidentReport_error(self) -> None: self.fail("StorageError not raised") @asyncAsDeferred - async def test_incidentReportsAttachedToIncident_error(self) -> None: + async def test_fieldReportsAttachedToIncident_error(self) -> None: """ - :meth:`DataStore.incidentReportsAttachedToIncident` raises + :meth:`DataStore.fieldReportsAttachedToIncident` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) store.bringThePain() try: - await store.incidentReportsAttachedToIncident( - anIncidentReport1.eventID, anIncidentReport1.number + await store.fieldReportsAttachedToIncident( + aFieldReport1.eventID, aFieldReport1.number ) except StorageError as e: self.assertEqual(str(e), store.exceptionMessage) @@ -491,19 +485,19 @@ async def test_incidentReportsAttachedToIncident_error(self) -> None: self.fail("StorageError not raised") @asyncAsDeferred - async def test_attachIncidentReportToIncident_error(self) -> None: + async def test_attachFieldReportToIncident_error(self) -> None: """ - :meth:`DataStore.attachIncidentReportToIncident` raises + :meth:`DataStore.attachFieldReportToIncident` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() await store.storeIncident(anIncident1) - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) store.bringThePain() try: - await store.attachIncidentReportToIncident( - anIncidentReport1.number, + await store.attachFieldReportToIncident( + aFieldReport1.number, anIncident1.eventID, anIncident1.number, "Hubcap", @@ -514,17 +508,17 @@ async def test_attachIncidentReportToIncident_error(self) -> None: self.fail("StorageError not raised") @asyncAsDeferred - async def test_detachIncidentReportFromIncident_error(self) -> None: + async def test_detachFieldReportFromIncident_error(self) -> None: """ - :meth:`DataStore.detachIncidentReportFromIncident` raises + :meth:`DataStore.detachFieldReportFromIncident` raises :exc:`StorageError` when the database raises an exception. """ store = await self.store() await store.storeIncident(anIncident1) - await store.storeIncidentReport(anIncidentReport1) + await store.storeFieldReport(aFieldReport1) - await store.attachIncidentReportToIncident( - anIncidentReport1.number, + await store.attachFieldReportToIncident( + aFieldReport1.number, anIncident1.eventID, anIncident1.number, "Hubcap", @@ -532,8 +526,8 @@ async def test_detachIncidentReportFromIncident_error(self) -> None: store.bringThePain() try: - await store.detachIncidentReportFromIncident( - anIncidentReport1.number, + await store.detachFieldReportFromIncident( + aFieldReport1.number, anIncident1.eventID, anIncident1.number, "Hubcap", @@ -543,7 +537,7 @@ async def test_detachIncidentReportFromIncident_error(self) -> None: else: self.fail("StorageError not raised") - def assertMultipleIncidentReportsEqual( + def assertMultipleFieldReportsEqual( self, store: TestDataStoreABC, groupA: Sequence[IncidentReport], @@ -556,22 +550,22 @@ def assertMultipleIncidentReportsEqual( for a in groupA: self.assertIn(a.number, bByNumber) - self.assertIncidentReportsEqual(store, a, bByNumber[a.number]) + self.assertFieldReportsEqual(store, a, bByNumber[a.number]) - def assertIncidentReportsEqual( + def assertFieldReportsEqual( self, store: TestDataStoreABC, - incidentReportA: IncidentReport, - incidentReportB: IncidentReport, + fieldReportA: IncidentReport, + fieldReportB: IncidentReport, ignoreAutomatic: bool = False, ) -> None: - if incidentReportA != incidentReportB: + if fieldReportA != fieldReportB: messages = [] for attribute in attrsFields(IncidentReport): name = attribute.name - valueA = getattr(incidentReportA, name) - valueB = getattr(incidentReportB, name) + valueA = getattr(fieldReportA, name) + valueB = getattr(fieldReportB, name) if name == "created": if store.dateTimesEqual(valueA, valueB): @@ -585,4 +579,4 @@ def assertIncidentReportsEqual( messages.append(f"{name} {valueA!r} != {valueB!r}") if messages: - self.fail("incident reports do not match:\n" + "\n".join(messages)) + self.fail("field reports do not match:\n" + "\n".join(messages))