diff --git a/CHANGES.rst b/CHANGES.rst index 614d3d14..d954600a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -40,6 +40,7 @@ Fixes ----- * Application new_bluesky_environment would crash when requests package not found. +* Function newSpecFile did not update scan_id in some cases. Maintenance ------------ diff --git a/bluesky/instrument/callbacks/spec_data_file_writer.py b/bluesky/instrument/callbacks/spec_data_file_writer.py index bee25d77..aec72866 100644 --- a/bluesky/instrument/callbacks/spec_data_file_writer.py +++ b/bluesky/instrument/callbacks/spec_data_file_writer.py @@ -48,6 +48,32 @@ def spec_comment(comment, doc=None): APS_fw.spec_comment(comment, doc, specwriter) +def RE_finder(): + """ + Find RunEngine object from namespaces of the stack. + + * Search backwards through each frame in the stack. + * Find any RunEngine objects. + * Prefer one with name 'RE'. + * Otherwise, pick the first one. + * Otherwise, return 'None'. + + suggestion: Hoist to apstools.utils + """ + import inspect + from bluesky import RunEngine + + fallback = None + for fr in inspect.stack(): # Walk backwards from this frame. + static = fr.frame.f_locals.copy() # f_locals will change during 'for'. + for k, v in static.items(): + if isinstance(v, RunEngine): # Got one! + if k == "RE": + return v # Ideal selection. + fallback = fallback or None + return fallback + + def newSpecFile(title, scan_id=None, RE=None): """ User choice of the SPEC file name. @@ -59,6 +85,7 @@ def newSpecFile(title, scan_id=None, RE=None): ``RE.md["scan_id"]`` is set to the last scan number in the file. """ kwargs = {} + RE = RE or RE_finder() # Search for a defined RunEngine instance if RE is not None: kwargs["RE"] = RE diff --git a/bluesky/tests/test_newSpecFile.py b/bluesky/tests/test_newSpecFile.py index dd6f0c60..6b0ba153 100644 --- a/bluesky/tests/test_newSpecFile.py +++ b/bluesky/tests/test_newSpecFile.py @@ -6,10 +6,13 @@ import pytest from apstools.utils import cleanupText -from instrument.callbacks.spec_data_file_writer import newSpecFile, specwriter -from spec2nexus.spec import SpecDataFile, is_spec_file +from spec2nexus.spec import SpecDataFile +from spec2nexus.spec import is_spec_file from bluesky.run_engine import RunEngine +from instrument.callbacks.spec_data_file_writer import RE_finder +from instrument.callbacks.spec_data_file_writer import newSpecFile +from instrument.callbacks.spec_data_file_writer import specwriter INITIAL_SCAN_ID = 1234 RE = RunEngine(dict(scan_id=INITIAL_SCAN_ID)) @@ -61,6 +64,37 @@ def test_newSpecFile(title, scan_id, tempdir): assert isinstance(specwriter.spec_filename, pathlib.Path) +@pytest.mark.parametrize("scan_id", [1, 2, 3, INITIAL_SCAN_ID - 1, INITIAL_SCAN_ID + 1]) +def test_issue_271(scan_id, tempdir): + """newSpecFile("title", scan_id=1) did not reset scan id.""" + found_RE = RE_finder() # Won't find the one in this module's globals() + assert found_RE is None + + RE = RunEngine() # define for this function, so it can be found + found_RE = RE_finder() + assert found_RE is not None + assert found_RE == RE + + assert isinstance(scan_id, int) + assert tempdir.exists() + os.chdir(tempdir) # newSpecFile() works in pwd + + RE.md["scan_id"] = INITIAL_SCAN_ID + assert RE.md["scan_id"] == INITIAL_SCAN_ID + + newSpecFile("title", scan_id=scan_id, RE=RE) + assert RE.md["scan_id"] != INITIAL_SCAN_ID + assert RE.md["scan_id"] == scan_id + + # repeat, without passing RE as parameter + RE.md["scan_id"] = INITIAL_SCAN_ID + assert RE.md["scan_id"] == INITIAL_SCAN_ID + + newSpecFile("title", scan_id=scan_id) + assert RE.md["scan_id"] != INITIAL_SCAN_ID + assert RE.md["scan_id"] == scan_id + + def test_newSpecFile_with_existing(tempdir): assert tempdir.exists() os.chdir(tempdir) # newSpecFile() works in pwd