Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Getting the parent of a dynamically method as a function
-
@mikael , hmmm. I am having a bad day today. Nothing is working 😱
You can see below I have to
from peak.util.proxies import ObjectWrapper
I can't
from proxy import ObjectWrapperBut I am getting a no module named 'pkg_resources' error. Are you on v2 beta?
Could be something with the beta# coding: utf-8 import ui #from proxy import ObjectWrapper from peak.util.proxies import ObjectWrapper class DefaultStyle(ObjectWrapper): def __init__(self, obj): ObjectWrapper.__init__(self, obj) self.background_color = '#fff7ee' self.tint_color = 'black' class HighlightStyle(DefaultStyle): def __init__(self, obj): DefaultStyle.__init__(self, obj) self.tint_color = 'red' button = HighlightStyle(ui.Button()) button.title = 'Styled button' button.present()
-
Sorry, I have the proxies code in a different place, but it is the same code. And yes, I am on v2 beta.
-
@mikael said:
Sorry, I have the proxies code in a different place, but it is the same code. And yes, I am on v2 beta.
If you get time could you please show me the dir structure you have, I have tried modifying it, I just get the same error. I just tried moving the proxies.py up one level. As the init files in both dirs contain the same statement
import('pkg_resources').declare_namespace(name)
But still same error. -
I have proxies.py in the same directory, it does not need anything else. Could of course also be in site packages.
I guess this is partly why it would be so nice to have it "built in".
-
@mikael , mine is in site-packages. I installed it with stash. It's ok, I don't know what's going on. I will look into it. As I say, nothing is going smoothly today 😜
Have completely shut down my ipad. Didn't help -
Wrappers also chain nicely, so you might have e.g. styles in a different inheritance hierarchy and extra functionality in other.
The only thing that is not chained is subject, so it probably makes sense inherit from a common base class and include a method that does the chaining, as follows:
# coding: utf-8 import ui from proxy import ObjectWrapper class ChainableWrapper(ObjectWrapper): def __init__(self, obj): ObjectWrapper.__init__(self, obj) # Follow the chain of __subject__'s, if needed def get_subject(self): subject = self.__subject__ while isinstance(subject, ObjectWrapper): subject = subject.__subject__ return subject class DefaultStyle(ChainableWrapper): def __init__(self, obj): super(DefaultStyle, self).__init__(obj) self.background_color = '#fff7ee' self.tint_color = 'black' class HighlightStyle(DefaultStyle): def __init__(self, obj): super(HighlightStyle, self).__init__(obj) self.tint_color = 'red' class ToggleButton(ChainableWrapper): def __init__(self, obj): super(ToggleButton, self).__init__(obj) def extra_functionality(self): pass button = ToggleButton(HighlightStyle(ui.Button())) button.title = 'Styled button' button.present() # Get the wrapped ui.* instance some_other_view.add_subview(button.get_subject())
-
@omz, would there be any chance to have add_subview accept proxies in addition to direct view objects? And have ObjCInstance recognize proxies and use the underlying reference instead?
-
@mikael , just let you know, I got it working again. For some reason my version of ProxyTypes was corrupt 😂
-
I wrote the below on another thread. I think it would help a lot without having to change much. Not sure what you think. Was not sure you seen it or not.
Phuket2 posted a day ago reply quote 0
I am not sure if being able to subclass ui compents is in the wind or will ever be. But I thought of something that may be quite useful, at least in my mind.
The simplest form of my idea is that you could call a ui method that sets a callback function that is called upon creation of any ui component. We could be passed the object type, or worse case check with type.
A little more refined would be able to also set different callbacks for the different types of ui components.That would offer a lot of flexibility and possibilities. Also, especially when testing would be so easy just to style the objects. I know, you can still just have a make button function anyway. But this just seems so much cleaner.
Not sure the impact this would have on performance or threading issues etc. I assume threading type problems would not be an issue as it would only be called on creation.
-
@Phuket2, thanks, I did see it, and read it with interest.
In the end I think I did not yet fully grasp the intended use case and value when compared to the effort:
- Styling objects has not been a major source of pain for me
- It feels like it could actually be a bit of effort for omz to implement in the UI module
- I try to avoid callbacks as long as I can
- Could lead to some really hard-to-debug situations when you take someone else's code and you both have used these callbacks
You could try it out by making a function that you use to wrap every object creation and call the callback(s).
-
I have revisited this topic and this whole thread, and have decided to turn my coat on the proxy thing. Now I think that with a simple utility function, derived from what @Phuket2 presented earlier in the thread, I can have the same clean, inheritance-friendly extension mechanism, which will also play nice with add_subview and ObjCInstance.
In concrete terms, here is the utility function:
import types def extend(this, with_that): items = [(i, getattr(with_that, i)) for i in dir(with_that) if not i.startswith('__')] for k, v in items: if callable(v): setattr(this, k, types.MethodType(v.__func__, this)) else: setattr(this, k, v) return this
With that, we can have just as clean custom component code as with the proxies, with the added benefit of the object still being the original ui component. (Different ways to set style properties used for demonstration.)
# coding: utf-8 import ui class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() self.tint_color = 'red' view = ui.View() button = extend(ui.Button(), HighlightStyle()) button.title = 'Styled button' view.add_subview(button) view.present() button.frame = view.bounds.inset(300, 100)
Note that new-style classes are needed, i.e. must inherit from
object
. -
@mikael , this looks good. I think I am doing something stupid through. I can not get the button to appear in the ui.View
I changed things around a little, just to help me test.I am setting the buttons x,y, attrs in different places. I was just trying to get it to work. When I do a print dir(button) it looks fine. I can see the added attrs I threw in the extend func are there.
I am sure I am missing something very stupid. Over thinking this now.
import types import ui def extend(this, with_that): items = [(i, getattr(with_that, i)) for i in dir(with_that) if not i.startswith('__')] for k, v in items: if callable(v): setattr(this, k, types.MethodType(v.__func__, this)) else: setattr(this, k, v) # add some attrs this.my_center = (0,0) this.xxx = 0 return this class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() #DefaultStyle.__init__(self) self.tint_color = 'red' self.border_width = 1 self.bg_color = 'yellow' self.x = 10 self.y = 10 if __name__ == '__main__': f = (0,0,500,500) v = ui.View(frame = f) v.bg_color = 'white' button = extend(ui.Button(name = 'test'), HighlightStyle()) print dir(button) button.title = 'Styled button' button.x = 10 button.y = 10 button.border_width = .5 v.add_subview(button) print button.superview v.present('sheet')
-
I think the button has to have a size before it is visible, not just location. E.g. with
size_to_fit()
. -
@mikael , thanks. That was it. I wrote a 3 a4 page rant, not to you , but about being frustrated. But I decided to delete it 😱😬
Also not to to @omz. Ok, I will shut up now.
But again thanks. I have to also think about every I was trying to squeeze out of this idea and see if it carries through.One thought, I have never used ... As a param before. But if the -
def extend(this, with_that): Could be written something like
def extend(this, ...):
So you could pass x number of classes to extend to function could be a good idea. Eg, I added some attrs in the extend function, just as a test. But if I had a class MyCustomAttrs
I could possibly write-
def extend(this, with_style, with_attrs) etc...
As I say, I have never used ... As a param. I just assume that's the idea of it. Sure, you still have to do the right thing in the extend function. So you could pass it a list of objects to iterate over. This just seems nicer -
That sounds interesting and certainly feasible. Could you please open up a bit more what you would want to happen?
- Expand several target instances with one "expander"
- Expand one target instance with several "expanders"
- Apply additional attributes to the target instance
- Only apply the listed attributes from the "expander"
- Something else
-
@mikael , honestly, I am not sure yet. But I can see if you do
button = extend(ui.Button(), HighlightStyle())
Then have to do
button = extend(ui.Button(), MyExtraAttrs())Just saying if you can pass in multiple classes , it would be nicer in my mind. Ok, have a precedence issue. But it makes sense that the last class passed would take precedence as it would if you did it line by line.
Again, I am not really sure the best way. I just think a solution like this has to be very compelling to make it worth wild
-
@Phuket2, that sounds like a wothwhile convenience feature. Here is a version that let's you apply several "expanders" with one call:
# coding: utf-8 import types # Extend the instance given as the first argument with the methods and properties of the instances given as the second and subsequent arguments def extend(target, *args): for source in args: for key in dir(source): if key.startswith('__'): continue value = getattr(source, key) if callable(value): setattr(target, key, types.MethodType(value.__func__, target)) else: setattr(target, key, value) return target
-
And sample usage/test case:
# coding: utf-8 import ui from extend import extend class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() self.tint_color = 'red' class ClickHandler(object): def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color view = ui.View() button = extend(ui.Button(), HighlightStyle(), ClickHandler()) button.title = 'Styled button' view.add_subview(button) view.present() button.frame = view.bounds.inset(300, 100)
-
@mikael nice. Yes. I just tested also.
All my idea is that it needs to be very compelling and super useful to make sense that other will use it.
Getting late here now, I will try and push it more tomorrow.import types import ui # Extend the instance given as the first argument with the methods and properties of the instances given as the second and subsequent arguments def extend(target, *args): for source in args: for key in dir(source): if key.startswith('__'): continue value = getattr(source, key) if callable(value): setattr(target, key, types.MethodType(value.__func__, target)) else: setattr(target, key, value) return target class DefaultStyle(object): background_color = '#fff7ee' tint_color = 'black' def __init__(self): self.action = self.click_handler self.click_color = 'blue' def click_handler(self, sender): self.tint_color = self.click_color class HighlightStyle(DefaultStyle): def __init__(self): super(HighlightStyle, self).__init__() #DefaultStyle.__init__(self) self.tint_color = 'red' self.border_width = 1 self.bg_color = 'yellow' self.x = 10 self.y = 10 class MyCustomAttrs(object): def __init__(self): self.xzy = 666 if __name__ == '__main__': f = (0,0,500,500) v = ui.View(frame = f) v.bg_color = 'white' button = extend(ui.Button(name = 'test'), HighlightStyle(), MyCustomAttrs()) print dir(button) button.title = 'Styled button' button.size_to_fit() button.x = 10 button.y = 10 button.border_width = .5 v.add_subview(button) print button.superview v.present('sheet')
-
@mikael , but again for the idea to be relevant it has to compete with something like this. Looks like more code then we have been doing. But when you look at the one off code it's not.
Just saying that's how I am trying to evaluate if it's worth while or not# coding: utf-8 import ui def MyMother(sender): print 'I love my Mums cooking' _default_btn_style = \ { 'border_width' : .5, 'corner_radius' : 3, 'bg_color' : 'teal', 'tint_color' : 'white', 'action' : MyMother, } _btn_style = _default_btn_style _extra_attrs = \ { 'myx' : 0, 'myy' : 0, } def make_button(style = _btn_style, ext_attrs = _extra_attrs , *args, **kwargs): btn = ui.Button() # process the normal atttrs for k,v in kwargs.iteritems(): if hasattr(btn, k): setattr(btn, k, v) # process a style dict for k,v in style.iteritems(): if hasattr(btn, k): setattr(btn, k, v) # process addtional attrs... for k,v in ext_attrs.iteritems(): setattr(btn, k, v) # if kwargs has a parent key, then we add the subview to the parent if kwargs.has_key('parent'): kwargs['parent'].add_subview(btn) btn.size_to_fit() # size to fit is too tight btn.frame = ui.Rect(*btn.bounds).inset(0, -5) return btn if __name__ == '__main__': f = (0,0,500,500) v = ui.View(frame = f) btn = make_button(title = 'What do I Like', parent = v) v.present('sheet')