def fromJSON(cls, store: IMSDataStore, json: Mapping[str, Any]) -> "JSONImporter": """ Import JSON. """ cls._log.info("Reading from JSON objects...") imsData = modelObjectFromJSONObject(json, IMSData) return cls(store=store, imsData=imsData)
def loadFromEventJSON( self, event: Event, path: Path, trialRun: bool = False ) -> None: """ Load event data from a file containing JSON. """ with path.open() as fileHandle: eventJSON = objectFromJSONBytesIO(fileHandle) self._log.info("Creating event: {event}", event=event) self.createEvent(event) # Load incidents for incidentJSON in eventJSON: try: eventID = incidentJSON.get(IncidentJSONKey.event.value) if eventID is None: incidentJSON[IncidentJSONKey.event.value] = event.id else: if eventID != event.id: raise ValueError( f"Event ID {eventID} != {event.id}" ) incident = modelObjectFromJSONObject( incidentJSON, Incident ) except ValueError as e: if trialRun: number = incidentJSON.get(IncidentJSONKey.number.value) self._log.critical( "Unable to load incident #{number}: {error}", number=number, error=e, ) else: raise for incidentType in incident.incidentTypes: self.createIncidentType(incidentType, hidden=True) self._log.info( "Creating incident in {event}: {incident}", event=event, incident=incident ) if not trialRun: self.importIncident(incident)
def loadFromEventJSON(self, event: Event, path: Path, trialRun: bool = False) -> None: """ Load event data from a file containing JSON. """ with path.open() as fileHandle: eventJSON = objectFromJSONBytesIO(fileHandle) self._log.info("Creating event: {event}", event=event) self.createEvent(event) # Load incidents for incidentJSON in eventJSON: try: eventID = incidentJSON.get(IncidentJSONKey.event.value) if eventID is None: incidentJSON[IncidentJSONKey.event.value] = event.id else: if eventID != event.id: raise ValueError( f"Event ID {eventID} != {event.id}") incident = modelObjectFromJSONObject( incidentJSON, Incident) except ValueError as e: if trialRun: number = incidentJSON.get(IncidentJSONKey.number.value) self._log.critical( "Unable to load incident #{number}: {error}", number=number, error=e, ) else: raise for incidentType in incident.incidentTypes: self.createIncidentType(incidentType, hidden=True) self._log.info("Creating incident in {event}: {incident}", event=event, incident=incident) if not trialRun: self.importIncident(incident)
async def newIncidentReportResource( self, request: IRequest, eventID: str ) -> KleinRenderable: """ New incident report endpoint. """ event = Event(id=eventID) del eventID await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidentReports ) try: json = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) if json.get(IncidentReportJSONKey.event.value, event.id) != event.id: return badRequestResponse( "Event ID mismatch: " f"{json[IncidentReportJSONKey.event.value]} != {event.id}" ) if json.get(IncidentReportJSONKey.incidentNumber.value): return badRequestResponse( "New incident report may not be attached to an incident: " f"{json[IncidentReportJSONKey.incidentNumber.value]}" ) author = request.user.shortNames[0] now = DateTime.now(TimeZone.utc) jsonNow = jsonObjectFromModelObject(now) # Set JSON event id # Set JSON incident report number to 0 # Set JSON incident report created time to now for incidentReportKey in ( IncidentReportJSONKey.number, IncidentReportJSONKey.created, ): if incidentReportKey.value in json: return badRequestResponse( request, f"New incident report may not specify " f"{incidentReportKey.value}", ) json[IncidentReportJSONKey.event.value] = event.id json[IncidentReportJSONKey.number.value] = 0 json[IncidentReportJSONKey.created.value] = jsonNow # If not provided, set JSON report entries to an empty list if IncidentReportJSONKey.reportEntries.value not in json: json[IncidentReportJSONKey.reportEntries.value] = [] # Set JSON report entry created time to now # Set JSON report entry author # Set JSON report entry automatic=False for entryJSON in json[IncidentReportJSONKey.reportEntries.value]: for reportEntryKey in ( ReportEntryJSONKey.created, ReportEntryJSONKey.author, ReportEntryJSONKey.automatic, ): if reportEntryKey.value in entryJSON: return badRequestResponse( request, f"New report entry may not specify " f"{reportEntryKey.value}", ) entryJSON[ReportEntryJSONKey.created.value] = jsonNow entryJSON[ReportEntryJSONKey.author.value] = author entryJSON[ReportEntryJSONKey.automatic.value] = False # Deserialize JSON incident report try: incidentReport = modelObjectFromJSONObject(json, IncidentReport) except JSONCodecError as e: return badRequestResponse(request, str(e)) # Store the incident report incidentReport = await self.config.store.createIncidentReport( incidentReport, author ) self._log.info( "User {author} created new incident report " "#{incidentReport.number} via JSON", author=author, incidentReport=incidentReport, ) self._log.debug( "New incident report: {json}", json=jsonObjectFromModelObject(incidentReport), ) request.setHeader("Incident-Report-Number", str(incidentReport.number)) request.setHeader( HeaderName.location.value, f"{URLs.incidentNumber.asText()}/{incidentReport.number}", ) return noContentResponse(request)
async def editIncidentResource( self, request: IRequest, eventID: str, number: str ) -> KleinRenderable: """ Incident edit endpoint. """ event = Event(id=eventID) del eventID await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidents ) author = request.user.shortNames[0] try: incidentNumber = int(number) except ValueError: return notFoundResponse(request) del number # # Get the edits requested by the client # try: edits = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) if not isinstance(edits, dict): return badRequestResponse( request, "JSON incident must be a dictionary" ) if ( edits.get(IncidentJSONKey.number.value, incidentNumber) != incidentNumber ): return badRequestResponse( request, "Incident number may not be modified" ) UNSET = object() created = edits.get(IncidentJSONKey.created.value, UNSET) if created is not UNSET: return badRequestResponse( request, "Incident created time may not be modified" ) IncidentAttributeSetter = Callable[ [Event, int, Any, str], Awaitable[None] ] async def applyEdit( json: Mapping[str, Any], key: Enum, setter: IncidentAttributeSetter, cast: Optional[Callable[[Any], Any]] = None, ) -> None: _cast: Callable[[Any], Any] if cast is None: def _cast(obj: Any) -> Any: return obj else: _cast = cast value = json.get(key.value, UNSET) if value is not UNSET: await setter(event, incidentNumber, _cast(value), author) store = self.config.store try: await applyEdit( edits, IncidentJSONKey.priority, store.setIncident_priority, lambda json: modelObjectFromJSONObject(json, IncidentPriority), ) await applyEdit( edits, IncidentJSONKey.state, store.setIncident_state, lambda json: modelObjectFromJSONObject(json, IncidentState), ) except JSONCodecError as e: return badRequestResponse(request, str(e)) await applyEdit( edits, IncidentJSONKey.summary, store.setIncident_summary ) await applyEdit( edits, IncidentJSONKey.rangerHandles, store.setIncident_rangers ) await applyEdit( edits, IncidentJSONKey.incidentTypes, store.setIncident_incidentTypes, ) location = edits.get(IncidentJSONKey.location.value, UNSET) if location is not UNSET: if location is None: for setter in ( store.setIncident_locationName, store.setIncident_locationConcentricStreet, store.setIncident_locationRadialHour, store.setIncident_locationRadialMinute, store.setIncident_locationDescription, ): cast(IncidentAttributeSetter, setter)( event, incidentNumber, None, author ) else: await applyEdit( location, LocationJSONKey.name, store.setIncident_locationName, ) await applyEdit( location, RodGarettAddressJSONKey.concentric, store.setIncident_locationConcentricStreet, ) await applyEdit( location, RodGarettAddressJSONKey.radialHour, store.setIncident_locationRadialHour, ) await applyEdit( location, RodGarettAddressJSONKey.radialMinute, store.setIncident_locationRadialMinute, ) await applyEdit( location, RodGarettAddressJSONKey.description, store.setIncident_locationDescription, ) jsonEntries = edits.get(IncidentJSONKey.reportEntries.value, UNSET) if jsonEntries is not UNSET: now = DateTime.now(TimeZone.utc) entries = ( ReportEntry( author=author, text=jsonEntry[ReportEntryJSONKey.text.value], created=now, automatic=False, ) for jsonEntry in jsonEntries ) await store.addReportEntriesToIncident( event, incidentNumber, entries, author ) return noContentResponse(request)
async def newIncidentResource( self, request: IRequest, eventID: str ) -> KleinRenderable: """ New incident endpoint. """ event = Event(id=eventID) del eventID await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidents ) try: json = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) author = request.user.shortNames[0] now = DateTime.now(TimeZone.utc) jsonNow = jsonObjectFromModelObject(now) # Set JSON incident number to 0 # Set JSON incident created time to now for incidentKey in ( IncidentJSONKey.number, IncidentJSONKey.created, ): if incidentKey.value in json: return badRequestResponse( request, f"New incident may not specify {incidentKey.value}" ) json[IncidentJSONKey.number.value] = 0 json[IncidentJSONKey.created.value] = jsonNow # If not provided, set JSON event, state to new, priority to normal if IncidentJSONKey.event.value not in json: json[IncidentJSONKey.event.value] = event.id if IncidentJSONKey.state.value not in json: json[IncidentJSONKey.state.value] = IncidentStateJSONValue.new.value if IncidentJSONKey.priority.value not in json: json[ IncidentJSONKey.priority.value ] = IncidentPriorityJSONValue.normal.value # If not provided, set JSON handles, types, entries, # incident report numbers to an empty list for incidentKey in ( IncidentJSONKey.rangerHandles, IncidentJSONKey.incidentTypes, IncidentJSONKey.reportEntries, IncidentJSONKey.incidentReportNumbers, ): if incidentKey.value not in json: json[incidentKey.value] = [] # Set JSON report entry created time to now # Set JSON report entry author # Set JSON report entry automatic=False for entryJSON in json[IncidentJSONKey.reportEntries.value]: for reportEntryKey in ( ReportEntryJSONKey.created, ReportEntryJSONKey.author, ReportEntryJSONKey.automatic, ): if reportEntryKey.value in entryJSON: return badRequestResponse( request, f"New report entry may not specify " f"{reportEntryKey.value}", ) entryJSON[ReportEntryJSONKey.created.value] = jsonNow entryJSON[ReportEntryJSONKey.author.value] = author entryJSON[ReportEntryJSONKey.automatic.value] = False # Deserialize JSON incident try: incident = modelObjectFromJSONObject(json, Incident) except JSONCodecError as e: return badRequestResponse(request, str(e)) # Validate data if incident.event != event: return badRequestResponse( request, f"Incident's event {incident.event} does not match event in " f"URL {event}", ) # Store the incident incident = await self.config.store.createIncident(incident, author) self._log.info( "User {author} created new incident #{incident.number} via JSON", author=author, incident=incident, ) self._log.debug( "New incident: {json}", json=jsonObjectFromModelObject(incident) ) request.setHeader("Incident-Number", str(incident.number)) request.setHeader( HeaderName.location.value, f"{URLs.incidentNumber.asText()}/{incident.number}", ) return noContentResponse(request)
async def newIncidentReportResource( self, request: IRequest ) -> KleinRenderable: """ New incident report endpoint. """ await self.config.authProvider.authorizeRequest( request, None, Authorization.writeIncidentReports ) try: json = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) author = request.user.shortNames[0] now = DateTime.now(TimeZone.utc) jsonNow = jsonObjectFromModelObject(now) # Set JSON incident report number to 0 # Set JSON incident report created time to now for incidentReportKey in ( IncidentReportJSONKey.number, IncidentReportJSONKey.created, ): if incidentReportKey.value in json: return badRequestResponse( request, f"New incident report may not specify " f"{incidentReportKey.value}" ) json[IncidentReportJSONKey.number.value] = 0 json[IncidentReportJSONKey.created.value] = jsonNow # If not provided, set JSON report entries to an empty list if IncidentReportJSONKey.reportEntries.value not in json: json[IncidentReportJSONKey.reportEntries.value] = [] # Set JSON report entry created time to now # Set JSON report entry author # Set JSON report entry automatic=False for entryJSON in json[IncidentReportJSONKey.reportEntries.value]: for reportEntryKey in ( ReportEntryJSONKey.created, ReportEntryJSONKey.author, ReportEntryJSONKey.automatic, ): if reportEntryKey.value in entryJSON: return badRequestResponse( request, f"New report entry may not specify " f"{reportEntryKey.value}" ) entryJSON[ReportEntryJSONKey.created.value] = jsonNow entryJSON[ReportEntryJSONKey.author.value] = author entryJSON[ReportEntryJSONKey.automatic.value] = False # Deserialize JSON incident report try: incidentReport = modelObjectFromJSONObject(json, IncidentReport) except JSONCodecError as e: return badRequestResponse(request, str(e)) # Store the incident report incidentReport = await self.config.store.createIncidentReport( incidentReport, author ) self._log.info( "User {author} created new incident report " "#{incidentReport.number} via JSON", author=author, incidentReport=incidentReport ) self._log.debug( "New incident report: {json}", json=jsonObjectFromModelObject(incidentReport), ) request.setHeader("Incident-Report-Number", incidentReport.number) request.setHeader( HeaderName.location.value, f"{URLs.incidentNumber.asText()}/{incidentReport.number}" ) return noContentResponse(request)
async def editIncidentResource( self, request: IRequest, eventID: str, number: int ) -> KleinRenderable: """ Incident edit endpoint. """ event = Event(id=eventID) await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidents ) author = request.user.shortNames[0] try: number = int(number) except ValueError: return notFoundResponse(request) # # Get the edits requested by the client # try: edits = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) if not isinstance(edits, dict): return badRequestResponse( request, "JSON incident must be a dictionary" ) if edits.get(IncidentJSONKey.number.value, number) != number: return badRequestResponse( request, "Incident number may not be modified" ) UNSET = object() created = edits.get(IncidentJSONKey.created.value, UNSET) if created is not UNSET: return badRequestResponse( request, "Incident created time may not be modified" ) IncidentAttributeSetter = ( Callable[[Event, int, Any, str], Awaitable[None]] ) async def applyEdit( json: Mapping[str, Any], key: Enum, setter: IncidentAttributeSetter, cast: Optional[Callable[[Any], Any]] = None ) -> None: _cast: Callable[[Any], Any] if cast is None: def _cast(obj: Any) -> Any: return obj else: _cast = cast value = json.get(key.value, UNSET) if value is not UNSET: await setter(event, number, _cast(value), author) store = self.config.store try: await applyEdit( edits, IncidentJSONKey.priority, store.setIncident_priority, lambda json: modelObjectFromJSONObject(json, IncidentPriority), ) await applyEdit( edits, IncidentJSONKey.state, store.setIncident_state, lambda json: modelObjectFromJSONObject(json, IncidentState), ) except JSONCodecError as e: return badRequestResponse(request, str(e)) await applyEdit( edits, IncidentJSONKey.summary, store.setIncident_summary ) await applyEdit( edits, IncidentJSONKey.rangerHandles, store.setIncident_rangers ) await applyEdit( edits, IncidentJSONKey.incidentTypes, store.setIncident_incidentTypes, ) location = edits.get(IncidentJSONKey.location.value, UNSET) if location is not UNSET: if location is None: for setter in ( store.setIncident_locationName, store.setIncident_locationConcentricStreet, store.setIncident_locationRadialHour, store.setIncident_locationRadialMinute, store.setIncident_locationDescription, ): cast(IncidentAttributeSetter, setter)( event, number, None, author ) else: await applyEdit( location, LocationJSONKey.name, store.setIncident_locationName ) await applyEdit( location, RodGarettAddressJSONKey.concentric, store.setIncident_locationConcentricStreet ) await applyEdit( location, RodGarettAddressJSONKey.radialHour, store.setIncident_locationRadialHour ) await applyEdit( location, RodGarettAddressJSONKey.radialMinute, store.setIncident_locationRadialMinute ) await applyEdit( location, RodGarettAddressJSONKey.description, store.setIncident_locationDescription ) jsonEntries = edits.get(IncidentJSONKey.reportEntries.value, UNSET) if jsonEntries is not UNSET: now = DateTime.now(TimeZone.utc) entries = ( ReportEntry( author=author, text=jsonEntry[ReportEntryJSONKey.text.value], created=now, automatic=False, ) for jsonEntry in jsonEntries ) await store.addReportEntriesToIncident( event, number, entries, author ) return noContentResponse(request)
async def newIncidentResource( self, request: IRequest, eventID: str ) -> KleinRenderable: """ New incident endpoint. """ event = Event(id=eventID) await self.config.authProvider.authorizeRequest( request, event, Authorization.writeIncidents ) try: json = objectFromJSONBytesIO(request.content) except JSONDecodeError as e: return invalidJSONResponse(request, e) author = request.user.shortNames[0] now = DateTime.now(TimeZone.utc) jsonNow = jsonObjectFromModelObject(now) # Set JSON incident number to 0 # Set JSON incident created time to now for incidentKey in ( IncidentJSONKey.number, IncidentJSONKey.created, ): if incidentKey.value in json: return badRequestResponse( request, f"New incident may not specify {incidentKey.value}" ) json[IncidentJSONKey.number.value] = 0 json[IncidentJSONKey.created.value] = jsonNow # If not provided, set JSON event, state to new, priority to normal if IncidentJSONKey.event.value not in json: json[IncidentJSONKey.event.value] = event.id if IncidentJSONKey.state.value not in json: json[IncidentJSONKey.state.value] = ( IncidentStateJSONValue.new.value ) if IncidentJSONKey.priority.value not in json: json[IncidentJSONKey.priority.value] = ( IncidentPriorityJSONValue.normal.value ) # If not provided, set JSON handles, types, entries, # incident report numbers to an empty list for incidentKey in ( IncidentJSONKey.rangerHandles, IncidentJSONKey.incidentTypes, IncidentJSONKey.reportEntries, IncidentJSONKey.incidentReportNumbers, ): if incidentKey.value not in json: json[incidentKey.value] = [] # Set JSON report entry created time to now # Set JSON report entry author # Set JSON report entry automatic=False for entryJSON in json[IncidentJSONKey.reportEntries.value]: for reportEntryKey in ( ReportEntryJSONKey.created, ReportEntryJSONKey.author, ReportEntryJSONKey.automatic, ): if reportEntryKey.value in entryJSON: return badRequestResponse( request, f"New report entry may not specify " f"{reportEntryKey.value}" ) entryJSON[ReportEntryJSONKey.created.value] = jsonNow entryJSON[ReportEntryJSONKey.author.value] = author entryJSON[ReportEntryJSONKey.automatic.value] = False # Deserialize JSON incident try: incident = modelObjectFromJSONObject(json, Incident) except JSONCodecError as e: return badRequestResponse(request, str(e)) # Validate data if incident.event != event: return badRequestResponse( request, f"Incident's event {incident.event} does not match event in " f"URL {event}" ) # Store the incident incident = await self.config.store.createIncident(incident, author) self._log.info( "User {author} created new incident #{incident.number} via JSON", author=author, incident=incident ) self._log.debug( "New incident: {json}", json=jsonObjectFromModelObject(incident) ) request.setHeader("Incident-Number", incident.number) request.setHeader( HeaderName.location.value, f"{URLs.incidentNumber.asText()}/{incident.number}" ) return noContentResponse(request)