-
Notifications
You must be signed in to change notification settings - Fork 45
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
Decoding qol updates #1198
Decoding qol updates #1198
Conversation
Is this a draft or is it ready for review? |
Still a draft. I wanted to put in some other helper fixes |
src/spyglass/utils/dj_helper_fn.py
Outdated
@@ -580,3 +581,42 @@ def str_to_bool(value) -> bool: | |||
if not value: | |||
return False | |||
return str(value).lower() in ("y", "yes", "t", "true", "on", "1") | |||
|
|||
|
|||
def full_key_decorator(required_keys: list[str] = 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.
Hi @samuelbray32 - It's not clear to me what motivated using a decorator over adding a helper method to the mixin. A decorator might grant you this functionality to a non-table method, or classes that don't inherit the mixin, but it seems like that's the case here. It's also not clear to me why the methods you use it on require classmethod
. Are there pieces of the funcs above that access the uninstanced class?
As a general rule, I'd like to be able to accept string restrictions anywhere we might accept a key as a dict, but I understand its more hoops
class SpyglassMixin:
...
def get_single_entry_key(
self, key: dict = None, required_fields: list[str] = None
) -> dict:
if key is None:
return dict() # I like explicit dict() over {}, bc {} could be set
required_fields = required_fields or self.primary_key
if isinstance(key, (str, dict)): # check is either keys or substrings
if not all(field in key for field in required_fields):
raise ValueError(
f"Key must contain all required fields: {required_fields}"
)
if len(query := self & key) == 1:
return query.fetch("KEY", as_dict=True)[0]
raise ValueError(
f"Key must identify exactly one entry in {self.camel_name}: {key}"
)
class SortedSpikesDecodingV1(SpyglassMixin, dj.Computed):
...
def fetch_environments(self, key):
key = self.get_single_entry_key(key, ["decoding_param_name"])
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.
Thanks Chris:
-
I wrote it as a decorator because my original plan was that it would require no additional arguments and simply fetch the fully key every time before running the function. However, several of the cases where it would be useful need to work prior to key insertion in the calling table, requiring a check if the existing key is sufficient before calling fetch. I agree that the decorator isn't as clean anymore and can move it into a mixin method.
-
With respect to the use of
classmethod
, I did that for functions that were previouslystaticmethod
. I was trying to make least amount of change to the existing while still letting me add the utility. For those methods, I'm assuming they were made static because they are used in the make function before the key is inserted into the table. -
For string restrictions, several of these functions already assume that the passed key is a dict. This utility method could handle case of strings by fetching the key from the table, which would generalize the accepted restrictions of the existing functions
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.
Makes sense. To keep down the complexity of the codebase overall, I think I'd avoid decorators until we find a case where there's no other way around it
I do wish we had a better pattern for when we do and don't use classmethod
s. As is, it seems pretty unpredictable for the end user whether to use Table.method(arg)
or Table().method(arg)
. I've tried to default to the latter whenever possible, without classmethod
, and reserve the latter for only when the method is doing something at the DJ class level. No reason to change an existing method. Is 'before insertion' the convention you've used?
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.
That's the convention I inferred from the code. Particularly in cases where the function is interacting with upstream tables or in the make function, since those are things that could be called in any future new version of the table
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.
Small edit to the error message, but functionally looks good
Description
Resolves #1197
DecodingParameters
fetch
andfetch1
to accept heading argumentsImplements
full_key_decorator
fetch_spike_data(key)
withoutkey
containing all primary key elements as long askey
specifies a unique database entryChange previously
staticmethods
toclassmethods
in clusterless piplelineSortedSpikesDecodingV1
ClusterlessDecodingV1
SortedSpikesGroup
make
functionChecklist:
CITATION.cff
alter
snippet for release notes.CHANGELOG.md
with PR number and description.