Skip to content

Allow for passing through windows drive letter to NSLS2PathProvider - #231

Merged
Thomas Hopkins (thopkins32) merged 11 commits into
NSLS2:mainfrom
jwlodek:thread-through-drive-letter
Sep 2, 2026
Merged

Allow for passing through windows drive letter to NSLS2PathProvider#231
Thomas Hopkins (thopkins32) merged 11 commits into
NSLS2:mainfrom
jwlodek:thread-through-drive-letter

Conversation

@jwlodek

@jwlodek Jakub Wlodek (jwlodek) commented Sep 17, 2025

Copy link
Copy Markdown
Contributor

Updates the NSLS2PathProvider to support passing in a separate write directory. Also simplifies the logic for the base directory setup. Removes the AcqModeFilenameProvider, replaced instead by the REMetadataFilenameProvider that allows for creating filenames from formatted RE metadata.

Copilot AI lite review requested due to automatic review settings August 31, 2026 19:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the nslsii.ophyd_async providers to better support Windows-style paths by allowing a Windows drive letter to be passed through to NSLS2PathProvider, and it tightens typing in the associated tests.

Changes:

  • Add windows_drive_letter support to NSLS2PathProvider and refactor path generation to use PurePath semantics.
  • Introduce new filename providers (TimestampFilenameProvider, REMetadataFilenameProvider) and a RunEngineMetadata type alias.
  • Add type annotations to the test_nsls2_path_provider test function parameters.
File summaries
File Description
nslsii/ophyd_async/providers.py Adds Windows drive-letter support, refactors directory/URI generation, and introduces timestamp/metadata-based filename providers.
nslsii/tests/ophyd_async/test_ophyd_async_providers.py Adds type annotations to test parameters (no behavioral change).
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 6
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread nslsii/ophyd_async/providers.py Outdated
Comment thread nslsii/ophyd_async/providers.py
Comment thread nslsii/ophyd_async/providers.py
Comment thread nslsii/ophyd_async/providers.py
Comment thread nslsii/ophyd_async/providers.py Outdated
Comment thread nslsii/tests/ophyd_async/test_ophyd_async_providers.py
Copilot AI review requested due to automatic review settings August 31, 2026 19:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Review details

Suppressed comments (8)

Previously missed (3) — in code that hasn't changed since the last review.

nslsii/ophyd_async/providers.py:4

  • Path is imported but never used in this module, which can trip linters and adds noise. Remove the unused import.
from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath

nslsii/ophyd_async/providers.py:236

  • New behavior (windows_drive_letter and the directory_uri field) is introduced here, but the existing tests only assert the POSIX-style directory_path. Add a unit test covering a Windows drive letter input and assert the resulting directory_path + directory_uri formatting.
        windows_drive_letter: str | None = None,

nslsii/ophyd_async/providers.py:236

  • windows_drive_letter is now part of the NSLS2PathProvider public constructor, but it is not documented in the class docstring's Parameters section. This makes the new API hard to discover for users.
        windows_drive_letter: str | None = None,

nslsii/ophyd_async/providers.py:139

  • The class docstring mentions an include_datakey_name parameter, but TimestampFilenameProvider has no constructor argument for that. This is misleading API documentation.
    include_datakey_name : bool, default False
        Whether to include the datakey name in the filename. If True, the datakey name will be prefixed to the filename.
    """

nslsii/ophyd_async/providers.py:170

  • The REMetadataFilenameProvider docstring documents metadata_dict but not the required format_string argument, and it also mentions include_datakey_name even though no such parameter exists. This makes the public API confusing.
    metadata_dict : dict
        Typically `RE.md`. Used for dynamic save path generation from sync-d experiment
    include_datakey_name : bool, default False
        Whether to include the datakey name in the filename. If True, the datakey name will be prefixed to the filename.
    """

nslsii/ophyd_async/providers.py:249

  • windows_drive_letter is interpolated directly into the Windows path. If a caller passes 'C:' (instead of 'C'), this will produce an invalid path like C::\\proposals. Consider normalizing/validating the input and reusing a single computed base dir for read/write.
        self._base_read_directory = (
            self.get_beamline_proposals_dir(beamline_tla=beamline_tla, beamline_tla_suffix=beamline_tla_suffix)
            if not windows_drive_letter
            else PureWindowsPath(f"{windows_drive_letter}:\\proposals")
        )

nslsii/ophyd_async/providers.py:361

  • When windows_drive_letter is used, full_read_path.as_posix() yields something like C:/... (no leading /). Passing that directly to urlunparse with netloc='localhost' produces an invalid file URI. Ensure the URL path starts with / for Windows drive paths.
            directory_uri=urlunparse(
                (
                    "file",
                    "localhost",
                    f"{full_read_path.as_posix()}/",

nslsii/ophyd_async/providers.py:158

  • TimestampFilenameProvider uses date.today() with a format string that includes hours/minutes/seconds. date has no time component, so this will always render 00 for %H%M%S, which breaks the intent of a timestamp-based filename.
        timestamp = date.today().strftime("%Y%m%d_%H%M%S")
        if datakey_name is not None:
            return f"{datakey_name}_{timestamp}"
        return timestamp
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings August 31, 2026 19:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (7)

Previously missed (2) — in code that hasn't changed since the last review.

nslsii/ophyd_async/providers.py:4

  • Path is imported but not used anywhere in this module. This can fail linting and makes the import list misleading; remove it.

This issue also appears in the following locations of the same file:

  • line 234
  • line 292
from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath

nslsii/ophyd_async/providers.py:368

  • directory_uri is intended to be POSIX, but when separator="\\" the YMD portion is generated with backslashes and can leak into the URI path. Normalize to forward slashes when building the URI to avoid invalid file:// URIs.
            directory_uri=urlunparse(
                (
                    "file",
                    "localhost",
                    f"{full_read_path.as_posix()}/",

nslsii/ophyd_async/providers.py:169

  • The REMetadataFilenameProvider docstring mentions include_datakey_name, but the class does not accept such a parameter (it inherits the prefixing behavior from TimestampFilenameProvider). The docstring also omits the format_string/timestamp_format parameters that are required by __init__.
    """Filename provider that generates filenames based on the current timestamp and the RE metadata.

    Parameters
    ----------
    metadata_dict : dict

nslsii/ophyd_async/providers.py:294

  • In generate_directory_path the docstring describes datakey_name as required (str), but the signature allows None. Mark it optional to match the function signature and behavior.
        datakey_name : str
            The name of the datakey to include in the path.
            If provided, the datakey name will be used as a prefix to the YMD portion of the path.

nslsii/ophyd_async/providers.py:238

  • windows_drive_letter changes the meaning/type of directory_path (Windows drive path vs /nsls2/data/...), but this new parameter is not documented in the NSLS2PathProvider docstring parameter list. Add it so callers understand the read/write split.

    def __init__(
        self,
        metadata_dict: RunEngineMetadata,
        filename_provider: FilenameProvider = UUIDFilenameProvider(),

nslsii/ophyd_async/providers.py:151

  • The TimestampFilenameProvider.__call__ docstring mentions include_datakey_name, but there is no such flag; the implementation prefixes whenever datakey_name is provided. Update the docstring line to match behavior.
        datakey_name : str, optional
            The name of the datakey to include in the filename. Only used if include_datakey_name is True.

nslsii/tests/ophyd_async/test_ophyd_async_providers.py:107

  • The test states the read URI always uses POSIX paths, but it doesn't assert that no backslashes appear in the URI (which can happen when separator="\\"). Adding a regression assertion here will catch URI formatting issues for Windows-style separators.
    # Read URI always uses POSIX paths
    assert info.directory_uri.startswith(
        f"file://localhost/nsls2/data/{tla_full}/proposals/2024-3/pass-000000/assets/test"
    )

  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread nslsii/ophyd_async/providers.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 1, 2026 00:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (5)

Previously missed (3) — in code that hasn't changed since the last review.

nslsii/ophyd_async/providers.py:4

  • Path is imported from pathlib but never used in this module, which will trigger unused-import linting and adds noise.
from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath

nslsii/ophyd_async/providers.py:174

  • This class docstring lists include_datakey_name, but the constructor takes format_string, timestamp_format, and metadata_dict. The parameters section should reflect the actual signature to avoid confusing users.
    metadata_dict : dict
        Typically `RE.md`. Used for dynamic save path generation from sync-d experiment
    include_datakey_name : bool, default False
        Whether to include the datakey name in the filename. If True, the datakey name will be prefixed to the filename.

nslsii/ophyd_async/providers.py:240

  • windows_drive_letter is a new public initializer parameter but it is not documented in the class docstring’s Parameters section. Please add it so users know how write-path vs read-URI behavior changes when it is set.
        windows_drive_letter: str | None = None,

nslsii/ophyd_async/providers.py:256

  • windows_drive_letter is interpolated directly into a Windows path. If a caller passes an empty string or something other than a single A–Z letter, this will silently create invalid paths. Consider validating/normalizing it (and uppercasing) before constructing PureWindowsPath.
        self._base_write_directory = (
            self._base_read_directory
            if not windows_drive_letter
            else PureWindowsPath(f"{windows_drive_letter}:\\proposals")
        )

nslsii/ophyd_async/providers.py:192

  • The docstring references include_datakey_name, but REMetadataFilenameProvider.__call__ always prefixes datakey_name when provided (via the base class). This should be updated to match behavior.
            The name of the datakey to include in the filename. Only used if include_datakey_name is True.
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread nslsii/ophyd_async/providers.py
Copilot AI review requested due to automatic review settings September 2, 2026 14:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

It introduces a likely breaking public API change in NSLS2PathProvider (removed/renamed constructor parameters) and contains docstring inaccuracies that should be corrected before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

nslsii/ophyd_async/providers.py:58

  • The TimestampFilenameProvider docstring mentions an include_datakey_name flag that doesn't exist; the behavior is simply that datakey_name is optionally prefixed to the timestamp.
        datakey_name : str, optional
            The name of the datakey to include in the filename. Only used if include_datakey_name is True.
  • Files reviewed: 4/4 changed files
  • Comments generated: 4
  • Review effort level: Lite

Comment thread nslsii/ophyd_async/providers.py
Comment thread nslsii/ophyd_async/providers.py
Comment thread nslsii/ophyd_async/providers.py Outdated
Comment thread nslsii/ophyd_async/providers.py Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 2, 2026 15:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

There are concrete correctness/documentation/test issues in the changed code (notably drive-letter parsing permissiveness and mismatched docstrings/test assertions) that should be addressed before approval.

Review details

Suppressed comments (7)

Previously missed (4) — in code that hasn't changed since the last review.

nslsii/tests/ophyd_async/test_ophyd_async_providers.py:76

  • This parametrization no longer includes a non-Windows case that passes a custom separator, so the POSIX-path behavior for custom separators isn’t exercised by tests.
    nslsii/tests/ophyd_async/test_ophyd_async_providers.py:126
  • For POSIX paths, effective_sep is currently forced to os.path.sep, which makes it impossible to assert behavior when a custom separator is provided. Use ymd_separator or os.path.sep so assertions match the actual configured separator.
    nslsii/ophyd_async/providers.py:58
  • Docstring refers to include_datakey_name, but this provider always prefixes datakey_name when it is provided (there is no include_datakey_name flag). Update the docstring to match the actual behavior to avoid confusing API consumers.

This issue also appears on line 177 of the same file.
nslsii/ophyd_async/providers.py:136

  • The helper returns a Windows drive root path (e.g. "C:\"), not just a string that "ends with a colon". Updating the docstring wording will make the intent clearer.

This issue also appears on line 149 of the same file.

nslsii/ophyd_async/providers.py:186

  • windows_drive_letter is part of the public initializer signature but is not documented in the class docstring parameters, and the docstring doesn’t mention that YMDGranularity.none is supported. This makes the API harder to discover and can lead to misuse.
    beamline_data_dirname : str, optional
        Name of the beamline data directory to use in the path, typically lowercase beamline TLA.
        If not provided, the name will be determined from the ENDSTATION_ACRONYM or
        BEAMLINE_ACRONYM environment variables.
    separator : str, optional
        Separator to use in YMD portion of the path. Defaults to default path separator given write directory semantics.
    granularity : YMDGranularity | str, default YMDGranularity.day
        Granularity of the YMD portion of the path. If set to YMDGranularity.day, the path will include year, month,
        and day directories. If set to YMDGranularity.month, the path will include year and month directories.
        If set to YMDGranularity.year, the path will include only the year directory.

nslsii/ophyd_async/providers.py:153

  • The current normalization silently truncates anything after "C:" (e.g. passing "C:\data" becomes just "C"), which can hide caller mistakes. Consider only accepting "C", "C:", or "C:\" and raising ValueError for any longer/other forms.
    # If more than one character is provided and the second character is a colon,
    # take only the first character
    if len(drive_letter) > 1 and drive_letter[1] == ":":
        drive_letter = drive_letter[0]

nslsii/ophyd_async/providers.py:114

  • Docstring refers to include_datakey_name, but the actual behavior is controlled by prepend_datakey_name. This mismatch can mislead users of the API; update the parameter description accordingly.
        """Generates a filename based on the current timestamp and the RE metadata.
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Just want to check one assumption about Windows drive letter and how it maps to a directory in practice.

Comment thread nslsii/ophyd_async/providers.py Outdated
@thopkins32
Thomas Hopkins (thopkins32) merged commit 93ef2e4 into NSLS2:main Sep 2, 2026
3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are correctness and robustness issues in the new provider logic (year granularity formatting, granularity error handling, base dir input normalization) plus test environment leakage that should be addressed before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

nslsii/ophyd_async/providers.py:183

  • NSLS2PathProvider accepts granularity as a string, but invalid values currently raise a KeyError from enum lookup; this should be a ValueError with a clear message.
    nslsii/ophyd_async/providers.py:243
  • For YMDGranularity.year, the date template includes the separator (e.g. "%Y_"), which produces directory names like "2026_" when separator="_"; year-only directories should not include a trailing separator.

nslsii/ophyd_async/providers.py:58

  • The TimestampFilenameProvider docstring mentions an include_datakey_name flag that doesn't exist; this is misleading for callers reading the API docs.
        Parameters
        ----------
        datakey_name : str, optional
            The name of the datakey to include in the filename. Only used if include_datakey_name is True.

nslsii/ophyd_async/providers.py:120

  • The REMetadataFilenameProvider call docstring also references an include_datakey_name option that is not part of this class; update the parameter description to match actual behavior (prepend_datakey_name).
        Parameters
        ----------
        datakey_name : str, optional
            The name of the datakey to include in the filename. Only used if include_datakey_name is True.

  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment on lines +187 to +200
if not base_data_dir:
tla = os.getenv("ENDSTATION_ACRONYM", os.getenv("BEAMLINE_ACRONYM", "")).lower()
if not tla:
raise ValueError(
"Neither ENDSTATION_ACRONYM nor BEAMLINE_ACRONYM environment variables are set. "
"Please set one of these environment variables or provide a base_data_dir."
)
self._base_data_dir = PurePosixPath(f"/nsls2/data/{tla}/proposals")
else:
self._base_data_dir = base_data_dir

self._base_write_dir = self._base_data_dir if not base_write_dir else base_write_dir

self._ymd_separator = separator or ("\\" if isinstance(self._base_write_dir, PureWindowsPath) else "/")
Comment on lines 67 to 77
os.environ["BEAMLINE_ACRONYM"] = "tst"

pp = NSLS2PathProvider(
dummy_re_md_dict,
filename_provider=static_fp,
beamline_tla=tla_override,
beamline_tla_suffix="-new" if with_suffix else None,
base_data_dir=base_data_dir,
granularity=ymd_granularity,
separator=ymd_separator,
include_scan_id_dir=include_scan_id_dir,
base_write_dir=base_write_dir,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants