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-27689: extend static type checking and fix errors #441
Conversation
09e8743
to
e23528a
Compare
A single __getattr__ replaces a lot of "type: ignore".
f3fd1c9
to
3a1fca0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. Standard comment about error messages on asserts 😄
@@ -105,14 +107,14 @@ class PurgeWithoutUnstorePruneCollectionsError(PruneCollectionsArgsError): | |||
purge is True but unstore is False. | |||
""" | |||
|
|||
def __init__(self): | |||
def __init__(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come this return type is needed is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, MyPy special-cases __init__
with type-annotated arguments to not need a return type annotation, but it needs the annotation here to be able to tell whether it should treat this as a typed method it should look at more closely or one it should ignore. And in strict mode it demands that everything be a typed method.
python/lsst/daf/butler/_butler.py
Outdated
|
||
# Keep track of whether an item is associated with multiple | ||
# dimensions. | ||
counter = Counter() | ||
assigned: dict[Any, Set[str]] = defaultdict(set) | ||
counter: Any = Counter() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this type is doing. We know it's not Any
-- it should be Counter
shouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know why I assumed typing.Counter
didn't exist, but of course it does (first I tried Dict
, but that failed because most_common
isn't a member, and then I got lazy). Fixed.
@@ -778,7 +783,11 @@ def quote(s): | |||
f" records when constrained by {values}") | |||
|
|||
# Get the primary key from the real dimension object | |||
dimension = self.registry.dimensions[dimensionName] | |||
dimension = self.registry.dimensions.getStaticDimensions()[dimensionName] | |||
if not isinstance(dimension, Dimension): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this ever trigger given that the lookup above would KeyError if it wasn't. Is this just a mypy hint?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, just a mypy hint - indexing DimensionUniverse
can return Dimension
or DimensionElement
, so it was this or an assert isinstance
check.
python/lsst/daf/butler/_butler.py
Outdated
@@ -1367,6 +1376,7 @@ def pruneDatasets(self, refs: Iterable[DatasetRef], *, | |||
if purge: | |||
self.registry.removeDatasets(refs) | |||
elif disassociate: | |||
assert tags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I have an error text as well please?
@@ -1613,14 +1623,14 @@ def import_(self, *, directory: Optional[str] = None, | |||
if filename is None: | |||
raise TypeError("At least one of 'filename' or 'format' must be provided.") | |||
else: | |||
_, format = os.path.splitext(filename) | |||
_, format = os.path.splitext(filename) # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I want filename
to be a ButlerURI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll let you take care of that one 🙂
@@ -329,6 +329,8 @@ def session(self) -> requests.Session: | |||
if isWebdavEndpoint(baseURL): | |||
log.debug("%s looks like a Webdav endpoint.", baseURL) | |||
s = getHttpSession() | |||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bgounon I'm not entirely sure what you wanted to happen in this else -- can you confirm this is okay?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TallJimbo I'm fine with this merging as is -- it's clearly fixing a problem and if @bgounon comes back with a different fix that can be a different ticket.
@@ -137,6 +137,8 @@ def shouldBeDisassembled(self, entity: Union[DatasetRef, DatasetType, StorageCla | |||
matchName = key | |||
break | |||
|
|||
assert isinstance(disassemble, bool) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message? Would this be needed if we declared the type at line 132 above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should probably just upgrade this to a real exception because it's essentially external config validation. Of course, it's only the tip of that iceberg, but I figure every little bit helps.
@@ -873,7 +875,9 @@ def dumpToUri(self, uri: Union[ButlerURI, str], updateFile: bool = True, | |||
ext = uri.getExtension() | |||
format = ext[1:].lower() | |||
|
|||
uri.write(self.dump(format=format).encode(), overwrite=overwrite) | |||
output = self.dump(format=format) | |||
assert output is not None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message? 😄
@@ -178,8 +180,7 @@ def read(self, component: Optional[str] = None) -> Any: | |||
|
|||
return loader.read(**self.fileDescriptor.parameters) | |||
|
|||
def write(self, inMemoryDataset: Any) -> str: | |||
def write(self, inMemoryDataset: Any) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about that one. I thought I had caught them all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one wasn't being type-checked before, so you're absolved 🙂
python/lsst/daf/butler/_butler.py
Outdated
@@ -600,8 +605,8 @@ def _findDatasetRef(self, datasetRefOrType: Union[DatasetRef, DatasetType, str], | |||
|
|||
# Process dimension records that are using record information | |||
# rather than ids | |||
newDataId: dict[Any, Any] = {} | |||
byRecord: dict[Any, dict[str, Any]] = defaultdict(dict) | |||
newDataId: Dict[Any, Any] = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I think you mentioned something about proper types for data Id keys and values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a bit trickier than I expected, due to the messiness of Mapping[Union[str, Dimension], ...]
vs. Union[Mapping[str, ...], Mapping[Dimension, ...]]
. Rather than fight that I just made these dicts use str
keys and coerced the one place where they might have been given Dimension
instances.
At least a couple of real bugs in this bunch, as well as a lot of annotation problems and missing annotations.
3a1fca0
to
5bc0660
Compare
MyPy didn't spot these, but the pylance language server in VSCode did. These are the ones where it's pretty clearly correct and the fix is simple. There were many more where the change wasn't a clear improvement (I'm not sure it's more strict or less intelligent than MyPy, or just differently strict and intelligent).
5bc0660
to
556b985
Compare
No description provided.