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

Add node_value & get_node_value to TaskSocket #299

Merged
merged 4 commits into from
Sep 13, 2024

Conversation

GeigerJ2
Copy link
Contributor

@GeigerJ2 GeigerJ2 commented Sep 9, 2024

This is basically just syntactic sugar to directly access the associated Python variable of a Socket, avoiding .value.value constructs.

Fixes #293.

A few notes regarding the implementation:

  • I'm checking first the nested case, as the non-nested one should always match, and, in the case of an AiiDA ORM type, return the AiiDA ORM variable, not its value
  • I implemented it in TaskProperty not SocketAny, as all sockets inherit from TaskProperty but not SocketAny
  • In the future, when we (re-)introduce the SocketAiiDADict, we can overwrite the method in this specific class, using get_dict rather than .value.value
  • Do we even need to provide a property? We can just provide the get_node_value method? The property is useful for caching, though.
  • One could use try-except to avoid writing the attribute that is being checked as string which can be a pain for later refactoring. However, for now I think the hasattr call reads cleaner
  • In the parametrization of the tests, passing AiiDA ORM types would require loading a profile (passing the aiida_profile fixture doesn't work), so I'm just type-hinting AiiDA ORM types, but passing the Python types, which still creates the correct sockets, but maybe there's a better solution?

This is basically just syntactic sugar to directly access the associated
Python variable of a `Socket`, avoiding `.value.value` constructs.

A few notes regarding the implementation:
- I'm checking first the nested case, as the non-nested one should
  always match, and, in the case of an AiiDA ORM type, return the AiiDA
  ORM variable, not its value
- I implemented it in `TaskProperty` not `SocketAny`, as all sockets inherit from `TaskProperty` but not `SocketAny`
- In the future, when we (re-)introduce the `SocketAiiDADict`, we can overwrite the method in this specific class, using
  `get_dict` rather than `.value.value`
- Do we even need to provide a `property`? We can just provide the
  `get_node_value` method? The `property` is useful for caching, though.
- One could use try-except to avoid writing the attribute that is being
  checked as string which can be a pain for later refactoring. However,
  for now I think the `hasattr` call reads cleaner
- In the parametrization of the tests, passing AiiDA ORM types would
  require loading a profile (passing the `aiida_profile` fixture)
  doesn't work, so I'm just type-hinting AiiDA ORM types, but passing
  the Python types, which still creates the correct sockets, but maybe
  there's a better solution?
@superstar54
Copy link
Member

Hi @GeigerJ2 , thanks for the work!

I implemented it in TaskProperty not SocketAny

I think you mean TaskSocket.

Do we even need to provide a property?

I think we need it, because it's convenient. socket.node_value is short compared to socket.get_node_value().

maybe there's a better solution?

I don't have a solution either 😢 . However, since the value will be converted to an orm.Data object inside the socket, the test successfully achieves its intended goal.

I also have a few comments on the code:

  • _node_value is not needed, because we only need to read the raw value, not modify it. There is also no need for caching, because it's not a heavy calculaiton.
  • I understand that you intend to handle as many socket types within the get_node_value method, but this introduces many logic (if..else) into the method. I would prefer keepting the get_node_value method in the TaskSocket unimplemented, and raising NotImplemented if the user attempt to use it. Then implement the specific method in each Socket type. For the moment, you can implement the one for the SocketAny.

aiida_workgraph/socket.py Outdated Show resolved Hide resolved
aiida_workgraph/socket.py Outdated Show resolved Hide resolved
@GeigerJ2
Copy link
Contributor Author

Hi @GeigerJ2 , thanks for the work!

Cheers!

I implemented it in TaskProperty not SocketAny

I think you mean TaskSocket.

That was indeed a spelling mistake, my bad!

Do we even need to provide a property?

I think we need it, because it's convenient. socket.node_value is short compared to socket.get_node_value().

Yeah, apart from the shorter name, the idea was also to store it in the private _node_value, so node_value doesn't have to call get_node_value() every time. Though, one can just assign that to a variable when calling it anyway, and it's not a heavy calculation, as you say below, so fine for me!

maybe there's a better solution?

I don't have a solution either 😢 . However, since the value will be converted to an orm.Data object inside the socket, the test successfully achieves its intended goal.

That's not possible when only implemented in SocketAny, see below.

I also have a few comments on the code:

  • _node_value is not needed, because we only need to read the raw value, not modify it. There is also no need for caching, because it's not a heavy calculaiton.

OK!

  • I understand that you intend to handle as many socket types within the get_node_value method, but this introduces many logic (if..else) into the method. I would prefer keepting the get_node_value method in the TaskSocket unimplemented, and raising NotImplemented if the user attempt to use it. Then implement the specific method in each Socket type. For the moment, you can implement the one for the SocketAny.

The thing is, if implementing only in SocketAny for now, the testing I wrote becomes a bit useless, as providing any type hinting will give the specific Socket(AiiDA)[Int|Float|Str|Bool], rather than SocketAny. Then, in addition, as one cannot pass instantiated AiiDA ORM types, neither type hints as just mentioned, we cannot test at all for any SocketAny that has an AiiDA ORM attached.

In my last commit, the implementation is as you propose. For me, that's fine to get merged.

Locally, I have another version where I define a SocketBase intermediate class that inherits from TaskSocket and implements the get_node_value method. Then the other sockets, SocketAny, and the more specific Socket(AiiDA)[Int|Float|Str|Bool] inherit from that, rather than TaskSocket:

from aiida import orm


class SocketBase(TaskSocket):
    def get_node_value(self):
        "Obtain the value of the associated data of a `Socket`."

        if isinstance(self.value, orm.Data):
            if hasattr(self.value, "value"):
                return self.value.value
            else:
                raise ValueError(
                    "Data node does not have a value attribute. We do not know how to extract the raw Python value."
                )
        else:
            return self.value


class SocketAny(SocketBase, SerializePickle):
    """Any socket."""
	...
  
class SocketFloat(SocketBase, SerializeJson):
    """Float socket."""
	...

class SocketAiiDAFloat(SocketBase, SerializeJson):
    """AiiDAFloat socket."""
	...

Though, not sure if that's useful (beyond the current case) or even desired design, so I didn't commit it.

@superstar54
Copy link
Member

The thing is, if implementing only in SocketAny for now, the testing I wrote becomes a bit useless ...
Locally, I have another version where I define a SocketBase intermediate class

I see. Let's make it simple by moving the get_node_value method from SocketAny to the TaskSocket.

@GeigerJ2 GeigerJ2 force-pushed the feature/node-value branch 3 times, most recently from cbdefd5 to 849032b Compare September 13, 2024 13:29
@GeigerJ2
Copy link
Contributor Author

I see. Let's make it simple by moving the get_node_value method from SocketAny to the TaskSocket.

My saying :D OK, I reverted the latest commit, and updated the implementation. Now it should be ready to merge. Could you please give it a final review?

@GeigerJ2 GeigerJ2 force-pushed the feature/node-value branch 2 times, most recently from a0f64e1 to 6394819 Compare September 13, 2024 13:46
@superstar54 superstar54 self-requested a review September 13, 2024 19:36
Copy link
Member

@superstar54 superstar54 left a comment

Choose a reason for hiding this comment

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

LGTM!

@superstar54 superstar54 merged commit a531907 into aiidateam:main Sep 13, 2024
7 of 8 checks passed
agoscinski pushed a commit that referenced this pull request Sep 19, 2024
Add `node_value` & `get_node_value` to `TaskSocket`. This is basically just syntactic sugar to directly access the associated
raw Python value of a `Socket` which has a orm.Data as its value. Therefore, we can avoid, e.g., `.value.value`.
@GeigerJ2 GeigerJ2 deleted the feature/node-value branch September 19, 2024 10:52
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.

Changing the name of .value for SocketAny to obtain AiiDA Node
2 participants