-
Notifications
You must be signed in to change notification settings - Fork 2
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
Extensions: decorators #97
Conversation
@extend.node_attribute(obj=my_fwtw, attribute_name="pull_distributed") | ||
def new_distributed(pull_distributed, vqip): | ||
"""pull_distributed with the tag 'FWTW'.""" | ||
return pull_distributed(vqip, tag="FWTW") | ||
|
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.
Due to my lack of knowledge on @extend
, how does it call the new_distributed
function? @barneydobson
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.
extend
is just our import name for the new wsimod.extensions.extensions.py
module.
what happens here is this node_attribute
is being used to wrap the existing pull_distributed
- the @
syntax above our new function applies this wrapping
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.
ok, then what would be an example for replacing the attribute with a scalar value - just type the value below @extend
or need a lambda
function?
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.
Aren't we just using apply_overrides
for that?
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.
To be flexible enough, we need something a little bit more elaborate. See my comments.
|
||
@extend.node_attribute(obj=my_fwtw, attribute_name="pull_distributed") |
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 afraid things need to be a bit more elaborate than this since for this approach to work, you need to already have access to the formed objects. This is the case in this tutorial since you are creating all the components programmatically so you can pass the object, but it will not be true when including the extensions in an extension file. Indeed, in this case it is not necessary to use decorators as you can simply do: my_fwtw.pull_distributed = new_distributed
, and that's all.
I suggest an alternative below.
""" | ||
|
||
|
||
def node_attribute(obj, attribute_name: str): |
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 function should accept a string indicating the node name to update, not a fully formed object. Of course, there has to be a way for the function to get the right object. I would suggest to create a singleton class - a sort of global variable, but a bit tidier -, from where this one pulls the information it needs. A minimal implementation would be something like:
class ExtensionsHandler:
__slots__ = "_model"
_instance: Optional["ExtensionsHandler"] = None
def __new__(cls, model: Optional[Model] = None):
if not cls._instance:
if not model:
raise ValueError("The extension handler needs to be initialised with a model.")
cls._instance = super().__new__(cls)
cls._instance._model = model
return cls._instance
def __init__(self, model: Optional[Model] = None):
# Do not assign `model` here.
self._model: Model
def get_node(self, name: str) -> Node:
return self._model.nodes[name]
Now, a few more things:
In the __init__
method of Model
class we initialise the handler as the very first thing to do. You do not need to assign it to anything, as it is just a question of making the extension handler aware of what model is to be extended:
class Model(...):
def __init__(self, ...):
ExtensionsHandler(self)
And then, within the decorators, now taking a string as input, we do:
def node_attribute(name: str, attribute_name: str):
def decorator(...):
obj = ExtensionsHandler().get_node(name)
...
You can implement a more complex logic, add handlers for arcs, etc, but this would be the basic idea. In summary:
- You register your model with the extension handler.
- You use the extension handler in the decorators to pull the relevant node/arc other from the model you are running.
Does any of this makes sense?
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.
It does make sense - but what is wrong with just decorating the instatiated objects? the model.load
can just instantiate the model
as normal and then apply the decorators?
For me the slight benefit of having decorators that take objects is (as in the notebook example) they can be applied in a wider variety of situations than just model.load
. (Or at least to facilitate this could we have them take a str
or obj
?)
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.
As I explained above, if you take as input instantiated objects, you obviously need to already have access to the formed objects in order to use the decorator. That is the case in the tutorial since you are creating all the components programmatically, so you can pass the object directly, but it will not be true when including the extensions in an extension file. Loading that file in order to apply the extensions will fail as the instantiated object is not available in the module when loading it. And it needs to be if it is an input to the decorator itself.
Just try to put your decorated function using instantiated objects in a separate file and load that file as a module. You will get something like NameError: name "my_fwtw" is not defined
.
Supersedded by #101 |
Description
My demonstration of decorators in the extensions file.
I have implemented it on one of the example tutorials.
If we're happy with it I think we will have two further issues:
extend.surface
, etc.)I think we will also want a notebook for extensions in general, but for me it makes sense to do this after we support subclasses via it.