1

This is going to be a kind of long question so I'll try to break it down as much as I can so please bear with me. My idea is to create an jupyter notebook app using jupyter widgets. My app will get some information using an API but it needs some inputs first which are time and entity name that also have some metadata. Because I have different ipywidgets incorporated here (SelectMultiple,2 Date pickers widgets (start, end) and button that would finally will load the function to get the data from the database.)

Currently my widgets look like this:

enter image description here

I'm able to obtain multiple entities ('Wells') (with its own metadata and assign an start and end date and save it in a dictionary). I have created a button but I'm having a hard time to be able to link the button to the already saved data to run my function everytime I run click in 'run'. Here's my code:

#I created a class to add multiple widgets
class SelectMutipleInteract(widgets.HBox):
        
    def __init__(self,api_obj):
        #In my init function I'm obtaining all the entities to be stored in the Selectmultiple widget
        env='prod'
        pre_df = api_obj.search_sites_by_name('',env).set_index('site_id').sort_index(ascending=True)
        pre_df = pre_df[pre_df['db'].str.contains('jon')]
        self.pre_df = pre_df
        
        self.w = widgets.SelectMultiple(
            options = pre_df.name.values,
            rows = len(pre_df),
            description = 'Wells',
            disabled = False)
        #From that same data I get initial start and end dates (which I'll be able to change based on the time I want)
        try:
            self.start_date = datetime.strptime(self.pre_df.sort_values(by='date_created')['date_created'].values[0],'%Y-%m-%dT%H:%M:%SZ')
            self.start_date = pd.Series(self.start_date).dt.round('H')[0]
        except:
            'TypeError'
        
        try:
            self.start_date = datetime.strptime(self.pre_df.sort_values(by='date_created')['date_created'].values[0],'%Y-%m-%dT%H:%M:%S.%fZ') 
            self.start_date = pd.Series(self.start_date).dt.round('H')[0]
        except:
            'TypeError'
        
        try:
            self.end_date = datetime.strptime(self.pre_df.sort_values(by='last_transmission',ascending=False)['last_transmission'].values[0],'%Y-%m-%dT%H:%M:%SZ')
            self.end_date = pd.Series(self.end_date).dt.round('H')[0]
        except:
            'TypeError'
            
        try:
            self.end_date = datetime.strptime(self.pre_df.sort_values(by='last_transmission',ascending=False)['last_transmission'].values[0],'%Y-%m-%dT%H:%M:%S.%fZ')
            self.end_date = pd.Series(self.end_date).dt.round('H')[0]
        except:
            'TypeError'

        # I created the rest of the widgets
        self.w1 = widgets.DatePicker(
                    description='Pick Start Date',
                    value = self.start_date)
        
        self.w2 = widgets.DatePicker(
                    description='Pick End Date',
                    value=self.end_date)
        
        self.w3 = widgets.Button(
                    description='Run',
                    button_style='info')
        
        self.selectors = [self.w,self.w1,self.w2,self.w3]
        
        #I think this as a super class and then each object to be assigned together
        super().__init__(children=self.selectors)
        self._set_observes()
        
    def _set_observes(self):
        self.selectors1 = self.selectors[:3]
        for widg in self.selectors1:
            widg.observe(self._observed_function_widgets1, names='value')
            
    #Observed function will work as the place to store the metadata        
    def _observed_function_widgets1(self,widg):
        self.dict = {}
        self.list_a = []
        
        for widg in self.selectors1:
            if type(widg.get_interact_value())==tuple:
                self.list_a.append(widg.get_interact_value())
            else:
                self.list_a.append(datetime.strftime(widg.get_interact_value(), '%Y-%m-%d %H:%M:%S'))
                
        for well in self.list_a[0]:
            a = self.pre_df.loc[self.pre_df['name']==well,:]
            self.dict[well]= {'domain':a['db'],'site_slug':a['slug'],'start_date':self.list_a[1],
                                'end_date':self.list_a[2]}
        
        print(self.dict)
        
        #This is where I put the connection of the button but I'm getting an error
        self.w3.on_click(self._on_button_clicked(self.dict))
        
        
    def _on_button_clicked(self,dict1):
        #Here I should run the final function
        print(1)
        print(dict1)
        
        
SelectMutipleInteract(api_obj)

The error that I get when I press the button (after I already selected entities and times) is

{'SHB 67-34': {'domain': site_id
183    jonah-energy
Name: db, dtype: object, 'site_slug': site_id
183    10004-24
Name: slug, dtype: object, 'start_date': '2019-09-26 15:00:00', 'end_date': '2020-12-17 21:00:00'}, 'SHB 75-34': {'domain': site_id
184    jonah-energy
Name: db, dtype: object, 'site_slug': site_id
184    10005-40
Name: slug, dtype: object, 'start_date': '2019-09-26 15:00:00', 'end_date': '2020-12-17 21:00:00'}, 'SHB 77-34': {'domain': site_id
185    jonah-energy
Name: db, dtype: object, 'site_slug': site_id
185    10006-46
Name: slug, dtype: object, 'start_date': '2019-09-26 15:00:00', 'end_date': '2020-12-17 21:00:00'}}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
TypeError: 'NoneType' object is not callable

The whole idea is to (once the data has been selected and stored) then run the final function but I'm having a hard time conceptualize the creation on the button give that you need to run the several widgets first to get the data and store it and then click the button to run the function with that data they way I'm creating the widgets layout together.

Any help would really appreciate it.

1 Answer 1

1

The issue here is really subtle.

You need to pass the callable self._on_button_clicked to self.w3.on_click. Your current code calls the self._on_button_clicked function, and passes the return value of the function, which is indeed None. Then when you click your button, the on_click tries to call None, hence your error.

Try thinking about this code:

value = self._on_button_clicked(self.dict)
self.w3.on_click(value)

This is exactly the same implementation as the current code, but hopefully it shows why what you have isn't working. value is None, and then you're telling the button to call None when you click it.

To avoid this, make these changes:

  1. self.w3.on_click(self._on_button_clicked) # pass the function itself, not the result of a call to that function.

  2. Change your observed function code to grab the variables you need in the body.

    def _on_button_clicked(self, button):  # the button arg gets passed, but you don't need it.
        #Here I should run the final function
        print(1)
        print(self.dict) # grab the instance variables you want

In most instances it's better to grab the variables you need inside your observed function, rather than try to pass them at the point you make the on_click assignment. If you REALLY can't grab the variables you need in the function (e.g. the variables are out of scope), then you can use a functools.partial.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.