Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
210cb06
impl: add mask workspace to diffcal
walshmm Oct 8, 2025
7d0a0a6
squash
walshmm Oct 8, 2025
71a2b7d
wip commit to squash later
walshmm Nov 24, 2025
77c403d
enable masking in diffcal workflow, including fully masked groups
walshmm Dec 5, 2025
86b67eb
Merge branch 'next' of github.com:neutrons/SNAPRed into ewm3645_mask_…
walshmm Dec 5, 2025
20474a7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 5, 2025
f1b8bb1
update pixi lock file
walshmm Dec 5, 2025
c3a2129
update tests
walshmm Dec 9, 2025
9abb5f9
fix maskless flow of calibration workflow
walshmm Dec 9, 2025
a35ecd9
update pixi lockfile
walshmm Dec 9, 2025
b79efe2
add request validation for ingredients and groceries so state can init
walshmm Dec 9, 2025
e0e5e50
fix some other maskworkspace handling
walshmm Dec 9, 2025
9a7d97f
fix integration tests
walshmm Dec 9, 2025
a6b9904
fix existing unit tests
walshmm Dec 10, 2025
958f62a
refactor diffcal happy path integration tests, add one for user mask
walshmm Dec 11, 2025
d0ce8b6
update pixi lock file
walshmm Dec 11, 2025
19fc749
move combine mask logic to grocery service, make some updates accordi…
walshmm Dec 17, 2025
62711f2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 17, 2025
e28fb28
fix segfaults in test_reductionservice tests
walshmm Dec 17, 2025
ad8d71a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 17, 2025
a00a324
fix last failing test, make more updates as per comments
walshmm Dec 17, 2025
6499fcb
fix issue with mismatched peaklists at assessment time
walshmm Dec 17, 2025
b720b8c
update pixi lockfile
walshmm Dec 17, 2025
a87bd8f
move back pin on ruamel.yaml, seems to be causing doc issues
walshmm Dec 17, 2025
9a79dc5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 17, 2025
ce8f52a
syntax error
walshmm Dec 17, 2025
a46a16d
fix precommit checks
walshmm Dec 17, 2025
9e9c8ba
add workaround for ConjoinWorkspaces mantid defect. require outputws…
walshmm Dec 18, 2025
02fdbfe
fix up 2 failining unit tests
walshmm Dec 18, 2025
6fc6cf3
move some mocks to patches
walshmm Dec 18, 2025
3a23790
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ build:
# Install the package in editable mode using pixi
- $HOME/.pixi/bin/pixi run --environment docs pip install -e .
# Install docs dependencies in the RTD Python environment too
- pip install erdantic versioningit sphinx_rtd_theme sphinxcontrib-mermaid types-pyyaml h5py numpy matplotlib ruamel.yaml
- pip install erdantic versioningit sphinx_rtd_theme sphinxcontrib-mermaid types-pyyaml h5py numpy matplotlib ruamel.yaml==0.18.16

sphinx:
builder: html
Expand Down
2,057 changes: 945 additions & 1,112 deletions pixi.lock

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class CalibrationAssessmentRequest(BaseModel):
default_factory=lambda: Pair.model_validate(Config["calibration.parameters.default.FWHMMultiplier"])
)
maxChiSq: float = Field(default_factory=lambda: Config["constants.GroupDiffractionCalibration.MaxChiSq"])
combinedPixelMask: WorkspaceName | None = None

@field_validator("fwhmMultipliers", mode="before")
@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class DiffractionCalibrationRequest(BaseModel):
maxChiSq: float = Field(default_factory=lambda: Config["constants.GroupDiffractionCalibration.MaxChiSq"])
removeBackground: bool = False
pixelMasks: List[WorkspaceName] = []
combinedPixelMask: WorkspaceName | None = None

continueFlags: Optional[ContinueWarning.Type] = ContinueWarning.Type.UNSET

Expand Down
10 changes: 6 additions & 4 deletions src/snapred/backend/dao/request/FocusSpectraRequest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from typing import Optional

from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict

from snapred.backend.dao.state.FocusGroup import FocusGroup
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName


class FocusSpectraRequest(BaseModel):
Expand All @@ -13,4 +12,7 @@ class FocusSpectraRequest(BaseModel):

inputWorkspace: str
groupingWorkspace: str
outputWorkspace: Optional[str] = None
maskWorkspace: WorkspaceName | None = None
outputWorkspace: WorkspaceName | None = None

model_config = ConfigDict(arbitrary_types_allowed=True)
27 changes: 27 additions & 0 deletions src/snapred/backend/data/GroceryService.py
Original file line number Diff line number Diff line change
Expand Up @@ -1515,6 +1515,33 @@ def fetchGroceryList(self, groceryList: Iterable[GroceryListItem]) -> List[Works

return groceries

def combinePixelMasks(self, outputMaskWsName: WorkspaceName, masks2Combine: List[WorkspaceName]):
startingIndex = 0
if len(masks2Combine) <= 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pythonically, this would be if not masks2Combine:, and if it were < 0 something would be really messed up with len. :)

raise ValueError("Lists of masks to combine is empty")

if not self.mantidSnapper.mtd.doesExist(outputMaskWsName):
self.renameWorkspace(masks2Combine[0], outputMaskWsName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I don't think we can assume its OK to overwrite the input mask in this manner! Here we can assume that we "own" the outputMaskWsName, but we need to leave the input workspaces alone. SO, I think that means in most cases you need to use fetchCompatiblePixelMask to create the output mask workspace, like before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to avoid using fetchCompatiblePixelMask inside combinePixelMasks due to it requiring the addition of runnumber and useLiteMode.

I can probably just remove this case and require that outputMaskWs exists. Current consumers of this method already call fetchCompatiblePixelMask to generate outputMaskWs beforehand.

startingIndex = 1

for maskWsName in masks2Combine[startingIndex:]:
if maskWsName == outputMaskWsName:
continue
if not self.mantidSnapper.mtd.doesExist(maskWsName):
raise ValueError(
f"Mask {maskWsName} of mask set {masks2Combine} does not exist, cannot combine into pixel mask."
)
self.mantidSnapper.BinaryOperateMasks(
f"combine from pixel mask: '{maskWsName}'...",
InputWorkspace1=outputMaskWsName,
InputWorkspace2=maskWsName,
OperationType="OR",
OutputWorkspace=outputMaskWsName,
)

self.mantidSnapper.executeQueue()
return outputMaskWsName

def fetchGroceryDict(self, groceryDict: Dict[str, GroceryListItem], **kwargs) -> Dict[str, WorkspaceName]:
"""
This is the primary method you should use for fetching groceries, in almost all cases.
Expand Down
5 changes: 1 addition & 4 deletions src/snapred/backend/recipe/CalculateDiffCalResidualRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def queueAlgos(self):
# Step 2: Check for overlapping spectra and manage them
fitPeaksWorkspace = self.mantidSnapper.mtd[self.fitPeaksDiagnosticWorkspaceName]
numHistograms = fitPeaksWorkspace.getNumberHistograms()
processedSpectra = []
spectrumDict = {}

for i in range(numHistograms):
Expand Down Expand Up @@ -102,9 +101,7 @@ def queueAlgos(self):

spectrumDict[spectrumId] = singleSpectrumName

# Append the processed spectrum to the list
processedSpectra.append(singleSpectrumName)

processedSpectra = list(spectrumDict.values())
# Step 3: Combine all processed spectra into a single workspace
combinedWorkspace = processedSpectra[0]
for spectrum in processedSpectra[1:]:
Expand Down
2 changes: 1 addition & 1 deletion src/snapred/backend/recipe/GroupDiffCalRecipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def validateInputs(self, ingredients: Ingredients, groceries: Dict[str, Workspac
)

diffocWS = self.mantidSnapper.mtd[groceries["groupingWorkspace"]]
if groupIDs != diffocWS.getGroupIDs().tolist():
if not set(groupIDs).issubset(set(diffocWS.getGroupIDs().tolist())):
raise RuntimeError(
f"Group IDs do not match between peak list and focus WS: '{groupIDs}' vs. '{diffocWS.getGroupIDs()}'"
)
Expand Down
27 changes: 21 additions & 6 deletions src/snapred/backend/recipe/PixelDiffCalRecipe.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from typing import Any, Dict, List, Set

import numpy as np
from pydantic import BaseModel
from pydantic import BaseModel, ConfigDict

from snapred.backend.dao.ingredients import DiffractionCalibrationIngredients as Ingredients
from snapred.backend.log.logger import snapredLogger
from snapred.backend.profiling.ProgressRecorder import ComputationalOrder, WallClockTime
from snapred.backend.recipe.algorithm.Utensils import Utensils
from snapred.backend.recipe.Recipe import Recipe, WorkspaceName
from snapred.backend.recipe.Recipe import Recipe
from snapred.meta.Config import Config
from snapred.meta.decorators.classproperty import classproperty
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceName
from snapred.meta.mantid.WorkspaceNameGenerator import WorkspaceNameGenerator as wng

logger = snapredLogger.getLogger(__name__)
Expand All @@ -18,10 +19,12 @@
class PixelDiffCalServing(BaseModel):
result: bool
medianOffsets: List[float]
maskWorkspace: str
maskWorkspace: WorkspaceName | None = None
calibrationTable: str
outputWorkspace: str

model_config = ConfigDict(arbitrary_types_allowed=True)


# Decorating with the `WallClockTime` profiler here somewhat duplicates the objective of the decoration
# at `CalibrationService.pixelCalibration`. However, by default, there will be no logging output from this instance.
Expand Down Expand Up @@ -101,11 +104,20 @@ def unbagGroceries(self, groceries: Dict[str, WorkspaceName]) -> None: # noqa A
"""
self.wsTOF = groceries["inputWorkspace"]
self.groupingWS = groceries["groupingWorkspace"]
self.maskWS = groceries["maskWorkspace"]
self.maskWS = groceries.get("maskWorkspace")
# the name of the output calibration table
self.DIFCpixel = groceries["calibrationTable"]
self.DIFCprev = groceries.get("previousCalibration", "")
# the input data converted to d-spacing

if self.maskWS and self.mantidSnapper.mtd.doesExist(self.maskWS):
# if user supplied mask exists, apply it
# A user mask may not exist, but the maskws name is used to store
# the mask generated as part of PixelDiffCalRecipe
self.mantidSnapper.MaskDetectors(
"applying user generated mask", Workspace=self.wsTOF, MaskedWorkspace=self.maskWS
)

self.wsDSP = wng.diffCalInputDSP().runNumber(self.runNumber).build()
self.convertUnitsAndRebin(self.wsTOF, self.wsDSP)
self.mantidSnapper.CloneWorkspace(
Expand Down Expand Up @@ -215,11 +227,14 @@ def queueAlgos(self):
wscc: str = f"__{self.runNumber}_tmp_group_CC_{self._counts}"

for i, (groupID, workspaceIndices) in enumerate(self.groupWorkspaceIndices.items()):
if groupID not in self.maxDSpaceShifts:
# group has been fully masked.
continue
workspaceIndices = list(workspaceIndices)
refID: int = self.getRefID(workspaceIndices)

self.mantidSnapper.CrossCorrelate(
f"Cross-Correlating spectra for {wscc}",
f"Cross-Correlating spectra for {wscc}, subgroup {groupID}",
InputWorkspace=self.wsDSP,
OutputWorkspace=wscc + f"_group{groupID}",
ReferenceSpectra=refID,
Expand All @@ -230,7 +245,7 @@ def queueAlgos(self):
)

self.mantidSnapper.GetDetectorOffsets(
f"Calculate offset workspace {wsoff}",
f"Calculate offset workspace {wsoff}, group {groupID}",
InputWorkspace=wscc + f"_group{groupID}",
OutputWorkspace=wsoff,
MaskWorkspace=self.maskWS,
Expand Down
13 changes: 13 additions & 0 deletions src/snapred/backend/recipe/algorithm/FitMultiplePeaksAlgorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,20 @@ def PyExec(self):
self.chopIngredients(reducedPeakList)
self.unbagGroceries()

numHisto = self.mantidSnapper.mtd[self.inputWorkspaceName].getNumberHistograms()
if numHisto < len(self.groupIDs):
raise ValueError(
f"Number of histograms and number of GroupPeakLists do not match: {numHisto} vs {len(self.groupIDs)}"
)

for index, groupID in enumerate(self.groupIDs):
spectrumInfo = self.mantidSnapper.mtd[self.inputWorkspaceName].spectrumInfo()
numHisto = self.mantidSnapper.mtd[self.inputWorkspaceName].getNumberHistograms()
if spectrumInfo.detectorCount() == 0:
raise ValueError(
f"Spectrum with NO DETECTORS encountered on {self.inputWorkspaceName} at index {index}"
)

tmpSpecName = mtd.unique_name(prefix=f"__tmp_fitspec_{index}_")
outputNameTmp = mtd.unique_name(prefix=f"__tmp_fitdiag_{index}_")
outputNamesTmp = {x: f"{outputNameTmp}{self.outputSuffix[x]}_{index}" for x in FitOutputEnum}
Expand Down
53 changes: 30 additions & 23 deletions src/snapred/backend/service/CalibrationService.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def _calibration_N_ref(self, request: DiffractionCalibrationRequest) -> float |
def prepDiffractionCalibrationIngredients(
self, request: DiffractionCalibrationRequest
) -> DiffractionCalibrationIngredients:
self.validateRequest(request)
# fetch the ingredients needed to focus and plot the peaks
cifPath = self.dataFactoryService.getCifFilePath(Path(request.calibrantSamplePath).stem)
state, _ = self.dataFactoryService.constructStateId(request.runNumber)
Expand All @@ -147,15 +148,15 @@ def prepDiffractionCalibrationIngredients(
maxChiSq=request.maxChiSq,
state=state,
)
ingredients = self.sousChef.prepDiffractionCalibrationIngredients(farmFresh)
ingredients = self.sousChef.prepDiffractionCalibrationIngredients(farmFresh, request.combinedPixelMask)
ingredients.removeBackground = request.removeBackground
return ingredients

@FromString
@Register("groceries")
def fetchDiffractionCalibrationGroceries(self, request: DiffractionCalibrationRequest) -> Dict[str, str]:
# groceries

self.validateRequest(request)
# TODO: It would be nice for groceryclerk to be smart enough to flatten versions
# However I will save that scope for another time
if request.startingTableVersion == VersionState.DEFAULT:
Expand Down Expand Up @@ -193,23 +194,21 @@ def fetchDiffractionCalibrationGroceries(self, request: DiffractionCalibrationRe
maskWorkspace=combinedMask,
)

if self.groceryService.workspaceDoesExist(calibrationMaskName):
self.groceryService.renameWorkspace(calibrationMaskName, combinedMask)
elif request.pixelMasks:
self.groceryService.getCloneOfWorkspace(request.pixelMasks[0], combinedMask)
allMasks = []

if self.groceryService.workspaceDoesExist(calibrationMaskName):
allMasks.append(calibrationMaskName)
if request.pixelMasks:
for mask in request.pixelMasks:
if not self.groceryService.workspaceDoesExist(mask):
raise RuntimeError([f"Pixel mask workspace '{mask}' does not exist"])
self.mantidSnapper.BinaryOperateMasks(
f"Combine pixel mask workspace {mask} with existing masks",
InputWorkspace1=combinedMask,
InputWorkspace2=mask,
OutputWorkspace=combinedMask,
OperationType="OR",
)
self.mantidSnapper.executeQueue()
allMasks.extend(request.pixelMasks)

allMasks.append(
self.groceryService.fetchCompatiblePixelMask(combinedMask, request.runNumber, request.useLiteMode)
)

if len(allMasks) > 0:
combinedMask = self.groceryService.combinePixelMasks(combinedMask, allMasks)
else:
combinedMask = ""

return groceryDict

Expand All @@ -222,11 +221,13 @@ def diffractionCalibration(self, request: DiffractionCalibrationRequest) -> Dict
# Profiling note: none of the following should be marked as sub-steps:
# their service methods are decorated separately.

groceries = self.fetchDiffractionCalibrationGroceries(request)
if not request.combinedPixelMask:
request.combinedPixelMask = groceries.get("maskWorkspace")
payload = SimpleDiffCalRequest(
ingredients=self.prepDiffractionCalibrationIngredients(request),
groceries=self.fetchDiffractionCalibrationGroceries(request),
groceries=groceries,
)

pixelRes = self.pixelCalibration(payload)
if not pixelRes.result:
raise RuntimeError("Pixel Calibration failed")
Expand Down Expand Up @@ -275,9 +276,14 @@ def _calibration_substep_N_ref(self, request: SimpleDiffCalRequest) -> float | N
@Register("pixel")
def pixelCalibration(self, request: SimpleDiffCalRequest) -> PixelDiffCalServing:
# cook recipe
userMaskWs = request.groceries.get("maskWorkspace")
userMaskWs = self.groceryService.getWorkspaceForName(userMaskWs)
numMaskedBeforePixelCal = 0
if userMaskWs:
numMaskedBeforePixelCal = userMaskWs.getNumberMasked()
res = PixelDiffCalRecipe().cook(request.ingredients, request.groceries)
maskWS = self.groceryService.getWorkspaceForName(res.maskWorkspace)
percentMasked = maskWS.getNumberMasked() / maskWS.getNumberHistograms()
percentMasked = (maskWS.getNumberMasked() - numMaskedBeforePixelCal) / maskWS.getNumberHistograms()
threshold = Config["constants.maskedPixelThreshold"]
if percentMasked > threshold:
res.result = False
Expand Down Expand Up @@ -311,6 +317,7 @@ def validateRequest(self, request: DiffractionCalibrationRequest):
runNumber=request.runNumber, continueFlags=request.continueFlags
)
self.validateWritePermissions(permissionsRequest)
self.sousChef.verifyCalibrationExists(request.runNumber, request.useLiteMode)

@Register("validateWritePermissions")
def validateWritePermissions(self, request: CalibrationWritePermissionsRequest):
Expand Down Expand Up @@ -348,7 +355,7 @@ def focusSpectra(self, request: FocusSpectraRequest):
farmFresh = FarmFreshIngredients(
runNumber=request.runNumber, useLiteMode=request.useLiteMode, focusGroups=[request.focusGroup], state=state
)
pixelGroup = self.sousChef.prepPixelGroup(farmFresh)
pixelGroup = self.sousChef.prepPixelGroup(farmFresh, pixelMask=request.maskWorkspace)
# fetch the grouping workspace
self.groceryClerk.grouping(request.focusGroup.name).fromRun(request.runNumber).useLiteMode(request.useLiteMode)
groupingWorkspace = self.groceryService.fetchGroupingDefinition(self.groceryClerk.build())["workspace"]
Expand Down Expand Up @@ -681,8 +688,8 @@ def assessQuality(self, request: CalibrationAssessmentRequest):
maxChiSq=request.maxChiSq,
state=state,
)
pixelGroup = self.sousChef.prepPixelGroup(farmFresh)
detectorPeaks = self.sousChef.prepDetectorPeaks(farmFresh)
pixelGroup = self.sousChef.prepPixelGroup(farmFresh, pixelMask=request.combinedPixelMask)
detectorPeaks = self.sousChef.prepDetectorPeaks(farmFresh, pixelMask=request.combinedPixelMask)

# TODO: We Need to Fit the Data
fitResults = FitMultiplePeaksRecipe().executeRecipe(
Expand Down
29 changes: 13 additions & 16 deletions src/snapred/backend/service/ReductionService.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,25 +429,22 @@ def prepCombinedMask(self, request: ReductionRequest) -> WorkspaceName:
raise RuntimeError(
f"reduction pixel mask '{mask}' has unexpected workspace-type '{mask.tokens('workspaceType')}'" # noqa: E501
)

# Load all pixel masks
allMasks = self.groceryService.fetchGroceryDict(
self.groceryClerk.buildDict(),
**residentMasks,
allMasks = list(
self.groceryService.fetchGroceryDict(
self.groceryClerk.buildDict(),
**residentMasks,
).values()
)
# purge empty string, diffcalmask comes back as one if it doesnt exist
allMasks = [m for m in allMasks if m]
allMasks.append(self.groceryService.fetchCompatiblePixelMask(combinedMask, runNumber, useLiteMode))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in the comment at GroceryService: I'd prefer if it creates a new output workspace each time. The idea of combining to the first workspace in the list is "interesting", but it's not idiomatic.

if len(allMasks) > 0:
combinedMask = self.groceryService.combinePixelMasks(combinedMask, allMasks)
else:
combinedMask = ""

self.groceryService.fetchCompatiblePixelMask(combinedMask, runNumber, useLiteMode)
for mask in allMasks.values():
# If there is no mask corresponding to the diffraction calibration, it will be set to the empty string.
if bool(mask):
self.mantidSnapper.BinaryOperateMasks(
f"combine from pixel mask: '{mask}'...",
InputWorkspace1=combinedMask,
InputWorkspace2=mask,
OperationType="OR",
OutputWorkspace=combinedMask,
)

self.mantidSnapper.executeQueue()
return combinedMask

@FromString
Expand Down
9 changes: 5 additions & 4 deletions src/snapred/backend/service/SousChef.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,15 @@ def prepNormalizationIngredients(
)

def prepDiffractionCalibrationIngredients(
self, ingredients: FarmFreshIngredients
self, ingredients: FarmFreshIngredients, combinedPixelMask: Optional[WorkspaceName] = None
) -> DiffractionCalibrationIngredients:
self.verifyCalibrationExists(ingredients.runNumber, ingredients.useLiteMode)

pixelGroup = self.prepPixelGroup(ingredients, pixelMask=combinedPixelMask)
groupedPeakLists = self.prepDetectorPeaks(ingredients, pixelMask=combinedPixelMask)
return DiffractionCalibrationIngredients(
runConfig=self.prepRunConfig(ingredients.runNumber),
pixelGroup=self.prepPixelGroup(ingredients),
groupedPeakLists=self.prepDetectorPeaks(ingredients),
pixelGroup=pixelGroup,
groupedPeakLists=groupedPeakLists,
peakFunction=ingredients.peakFunction,
convergenceThreshold=ingredients.convergenceThreshold,
maxOffset=ingredients.maxOffset,
Expand Down
2 changes: 1 addition & 1 deletion src/snapred/ui/threading/worker_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def run(self):
results = SNAPResponse(code=ResponseCode.RECOVERABLE, message=e.model.json())
except Exception as e: # noqa: BLE001
logger.error(e)
if logger.isEnabledFor(logging.DEBUG):
if logger.isEnabledFor(logging.DEBUG) or True:
# print stacktrace
import traceback

Expand Down
Loading