MarkyMark1000
MarkyMark1000

Reputation: 437

I can't get private class decorator to work

I am trying to get a private class decorator, or at least a decorator that is associated with the class working. I have been trying to use an inline class, but I am currently getting the following problem when I run it:

click_ImportToDB() missing 1 required positional argument: 'self'

If you could take a look at the following, which I have cut down quite a bit and let me know what I am doing wrong, that would be great:

class frmApplication(tk.Frame):

    # THIS IS THE PRIVATE INLINE CLASS DECORATOR THAT I AM TRYING TO BUILD.
    class _form_validation():
        def __init__(self, func):
            self.func = func
        
        def __call__(self):
            # Wrap the function up in error trapping and form validation
            try:
                # Ensure the strDBPath and config file have the same value for the database
                # Ensure the database actually exists
                # Now call the function
                self.func()
            except Exception as Ex:
                strReport=Ex.args[0]
                messagebox.showerror("Error",strReport)


    #BACK TO THE frmApplication CLASS
    def __init__(self,FormParent=tk.NONE):
        .....

    def __populateDatabaseFrame(self):
        ....

    def __populateButtonFrame(self):
        #First, add a frame
        self.__objButtonFrame=ttk.Frame(self.master,border=2,relief=tk.SUNKEN)
        self.__objButtonFrame.grid(row=1,column=0,columnspan=1,sticky=tk.N+tk.S+tk.E+tk.W,padx=10,pady=10)
        F=self.__objButtonFrame
        #Configure the columns of the frame
        F.grid_columnconfigure(0,weight=1)
        #Now add an upload to database button into the frame

        # THERE IS A CALL TO click_ImportToDB HERE !!!!!
        F.__btnImportToDB=ttk.Button(F,text="Upload to Database",command=self.click_ImportToDB)
        F.__btnImportToDB.grid(row=0,column=0,sticky=tk.E+tk.W,padx=10,pady=10)



        #Now add a button to show the data
        .....

    def __createDatabase(self):
        ...

    def __selectDatabase(self):
        ...
    
    #Public Functions.
    def click_SelectDB(self):
        ...

    def click_NewDB(self):
        ....

    @_form_validation
    def click_ImportToDB(self):
        frmTop=tk.Toplevel(self.master)
        frmTop.transient(frmTop.master)
        frmTop.focus_set()
        frmTop.grab_set()
        objFrm=vID.frmImportToDB(frmTop)
        frmTop.wait_window()
        del objFrm

Many thanks

Mark

Upvotes: 0

Views: 43

Answers (2)

MarkyMark1000
MarkyMark1000

Reputation: 437

Thanks for your help. The first suggestion didn't really work. I have edited the code to try to help explain what the problem is:

class Test():

    def __init__(self):
        self.name = 'test class'

    class _decorator(object):

        def __init__(self, arg):
            self.name = '_decorator class'
            self._arg = arg
            

        def __call__(self):
            print("\n---- BEFORE FUNCTION EXECUTION ----\n")
            print('self points to: "' + self.name + '"')
            print('However I really need it to point to the test class to perform validation etc.')
            self._arg(self)
            print("\n---- AFTER FUNCTION EXECUTION ----\n")


    @_decorator
    def test_method(self):
        print('self points to: "' + self.name + '"')
        print("**** Executing test_method ****")
        

if __name__ == '__main__':
    test = Test()
    test.test_method()

The output looks like this:

---- BEFORE FUNCTION EXECUTION ----

self points to: "_decorator class"
However I really need it to point to the test class to perform validation etc.
self points to: "_decorator class"
**** Executing test_method ****

---- AFTER FUNCTION EXECUTION ----

I did try adjusting the second option as follow's:

from functools import wraps

class Test():

    def __init__(self):
        self.name = 'Test Class'

    def _decorator(f):
        @wraps(f)
        def wrapped(inst, *args, **kwargs):

            print('inst points to: '+inst.name)

            print("EXECUTING BEFORE FUNC")
            return_val = f(inst, *args, **kwargs)
            print("EXECUTING AFTER FUNC")

            return return_val

        return wrapped


    @_decorator
    def test_method(self):
        print("EXECUTING FUNC")
        return "RETURN FROM TEST_METHOD"

if __name__ == '__main__':
    test = Test()
    print(test.test_method())

The output looks like this:

inst points to: Test Class
EXECUTING BEFORE FUNC
EXECUTING FUNC
EXECUTING AFTER FUNC
RETURN FROM TEST_METHOD

This looks much more like what I was after. The only downside is that the linter now reports the following problem, however it doesn't stop it from working:

Method '_decorator' should have 'self' as first argument.Python(no-self-argument)

Upvotes: 0

Nivardo Albuquerque
Nivardo Albuquerque

Reputation: 536

i'm not sure you're passing the argument from the decorator to the func.

class Test():
    class _decorator(object):

        def __init__(self, arg):
            self._arg = arg
            print(self._arg)
        def __call__(self):
            print("DOING SOMETHING HERE BEFORE THE FUNC EXEC")
            retval = self._arg(self)
            print("DOING SOMETHING HERE AFTER THE FUNC EXEC")

            return retval


    @_decorator
    def test_method(self):
        print("EXECUTING FUNC")
        return "RETURN FROM TEST_METHOD"

if __name__ == '__main__':
    test = Test()
    print(test.test_method())

I tried to create this code to replicate your doubt.

try, passing self to your function.

If that does not work. You cant try to use noraml decorators instead of class decoratos, here is the same example.

from functools import wraps


class Test():

    def _decorator(f):
        @wraps(f)
        def wrapped(inst, *args, **kwargs):
            print("EXECUTING BEFORE FUNC")

            return_val = f(inst, *args, **kwargs)
            print("EXECUTING AFTER FUNC")
            return return_val
        return wrapped


    @_decorator
    def test_method(self):
        print("EXECUTING FUNC")
        return "RETURN FROM TEST_METHOD"

if __name__ == '__main__':
    test = Test()
    print(test.test_method())

hope it helps.

Upvotes: 1

Related Questions