Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DM-29073: Make ButlerURI immutable #485

Merged
merged 18 commits into from
Mar 5, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 38 additions & 18 deletions python/lsst/daf/butler/core/_butlerUri/_butlerUri.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class ButlerURI:
"""True if path-like elements modifying a URI should be quoted.

All non-schemeless URIs have to internally use quoted paths. Therefore
if a new file name is given (e.g. to updateFile or join) a decision must
if a new file name is given (e.g. to updatedFile or join) a decision must
be made whether to quote it to be consistent.
"""

Expand Down Expand Up @@ -373,46 +373,60 @@ def parent(self) -> ButlerURI:
# regardless of the presence of a trailing separator
originalPath = self._pathLib(self.path)
parentPath = originalPath.parent
parentURI = self._uri._replace(path=str(parentPath))
return self.replace(path=str(parentPath), forceDirectory=True)

return ButlerURI(parentURI, forceDirectory=True)

def replace(self, **kwargs: Any) -> ButlerURI:
def replace(self, forceDirectory: bool = False, **kwargs: Any) -> ButlerURI:
"""Replace components in a URI with new values and return a new
instance.

Parameters
----------
forceDirectory : `bool`
Parameter passed to ButlerURI constructor to force this
new URI to be dir-like.
timj marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
new : `ButlerURI`
New `ButlerURI` object with updated values.

Notes
-----
Does not, for now, allow a change in URI scheme.
"""
return self.__class__(self._uri._replace(**kwargs))
# Disallow a change in scheme
if "scheme" in kwargs:
raise ValueError(f"Can not use replace() method to change URI scheme for {self}")
return self.__class__(self._uri._replace(**kwargs), forceDirectory=forceDirectory)

def updateFile(self, newfile: str) -> None:
"""Update in place the final component of the path with the supplied
file name.
def updatedFile(self, newfile: str) -> ButlerURI:
"""Return new URI with an updated final component of the path.

Parameters
----------
newfile : `str`
File name with no path component.

Returns
-------
updated : `ButlerURI`

Notes
-----
Updates the URI in place.
Updates the ButlerURI.dirLike attribute. The new file path will
be quoted if necessary.
Forces the ButlerURI.dirLike attribute to be false. The new file path
will be quoted if necessary.
"""
if self.quotePaths:
newfile = urllib.parse.quote(newfile)
dir, _ = self._pathModule.split(self.path)
newpath = self._pathModule.join(dir, newfile)

self.dirLike = False
self._uri = self._uri._replace(path=newpath)
updated = self.replace(path=newpath)
updated.dirLike = False
return updated

def updateExtension(self, ext: Optional[str]) -> None:
"""Update the file extension associated with this `ButlerURI` in place.
def updatedExtension(self, ext: Optional[str]) -> ButlerURI:
"""Return a new `ButlerURI` with updated file extension.

All file extensions are replaced.

Expand All @@ -421,9 +435,15 @@ def updateExtension(self, ext: Optional[str]) -> None:
ext : `str` or `None`
New extension. If an empty string is given any extension will
be removed. If `None` is given there will be no change.

Returns
-------
updated : `ButlerURI`
URI with the specified extension. Can return itself if
no extension was specified.
Copy link
Member

Choose a reason for hiding this comment

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

Would it be worthwhile to optimize the case where the extension is already the given one, too?

"""
if ext is None:
return
return self

# Get the extension and remove it from the path if one is found
# .fits.gz counts as one extension do not use os.path.splitext
Expand All @@ -437,7 +457,7 @@ def updateExtension(self, ext: Optional[str]) -> None:
if ext and not ext.startswith("."):
ext = "." + ext

self._uri = self._uri._replace(path=path + ext)
return self.replace(path=path + ext)

def getExtension(self) -> str:
"""Return the file extension(s) associated with this URI path.
Expand Down