ronak
ronak

Reputation: 1778

How do I write a decorator and return an authenticated object

I am writing automation scripts for adding some bugs to Jira via the jira python library.

I have my username and password in a file and I want to make sure that everytime a function is being called (which adds/modifies a bug) is authenticated.

Basically I want to write a decorator. But this decorator will return a jira object which shall be used by every function. Below is the function I would write.

def return_authed_jira():
    options = {'server': 'https://jira.xxxxyyzzz.com'}
    authed_jira = JIRA(options,  basic_auth=('my_uname', my_passwd))
    return authed_jira

Is there a way I can do this using a decorator. Again, I know I could do it differently without using a decorator, but I want to do this with a decorator.

I have tried writing it myself, but most of the examples that I have seen do not return an object.

This is what I have tried.

def authenticate(func):

    def authenticate_and_call(*args, **kwargs):

        options = {'server': 'https://jira.xxxxxxx.com', 'verify': False}
        jira = JIRA(options)
        if not jira.current_user() == 'uname':
            options = {'server': 'https://jira.xxxxxx.com', 'verify': False}
            jira = JIRA(options, basic_auth=('uname', passwd))
            args.append(jira)
        return func(*args, **kwargs)
    return authenticate_and_call

Here I am trying to append the jira object, but that did not work.

This is something that I desire. Adding the jira object to the args does not make it easy for a function to access. This is similar to the login decorator where if you try to access a page which needs the user to login, you add the decorator to the function serving the page.

@authenticate
def create_new_jira_bug(jira_fields):

    new_issue = jira.create_issue(jira_fields)
    print new_issue.id

Below is the new code after Blckknght answered the question and made some modifications.

def authenticate(func):

    def authenticate_and_call(*args, **kwargs):

        options = {'server': 'https://jira.xyz.com', 'verify': False}
        jira = JIRA(options)
        if not jira.current_user() == 'uname':
            options = {'server': 'https://jira.xyz.com', 'verify': False}
            jira = JIRA(options, basic_auth=(uname, password))
        return func(*args, jira=jira, **kwargs)
    return authenticate_and_call

@authenticate
def list_projects():

    my_issue = jira.issue('MYKEY-1289')
    print (my_issue.raw.get('fields'))

list_projects()

Below is the error I get when I dont add jira to the list_projects function

Traceback (most recent call last):
  File "C:/testjira.py", line 44, in <module>
    list_projects()
  File "C:testjira.py", line 33, in authenticate_and_call
    return func(*args, jira=jira, **kwargs)
TypeError: list_projects() takes no arguments (1 given)

My question is that I have to pass the jira object to the function before which the decorator is called, otherwise I get this error. Is there an alternative to this? It works fine with the below code. I am trying to get an explanation on why do I have to pass the object when defining the function when I dont have to pass that object when calling the function.

@authenticate
def list_projects(jira):

    my_issue = jira.issue('MYKEY-1289')
    print (my_issue.raw.get('fields'))

list_projects()

Upvotes: 1

Views: 848

Answers (1)

Blckknght
Blckknght

Reputation: 104762

You can't append to args because it's a tuple, not a list. To add a value, you'll need to make a new tuple with the value concatenated:

args = args + (jira,)

Note the comma at the end of (jira,), it's necessary to make it a 1-tuple, rather than a simple parenthesized expression.

You also need to set up the function to be decorated to expect the jira argument:

@authenticate
def create_new_jira_bug(jira_fields, jira): # expects jira arg from decorator
    new_issue = jira.create_issue(jira_fields)
    print new_issue.id

Note that I haven't provided a default value for jira. Your current code only adds the jira argument to args some of the time, which won't work if jira is a required argument. It might make more sense to always pass it in from the decorator.

It might be more natural to add a keyword argument to the call made by the decorator, rather than an extra positional argument. This will require all the functions that you're decorating to use the same name for the jira object the decorator passes in, but it won't interfere with their handling of other keyword arguments. Adding a positional argument to the end of the existing (as you're currently doing) may break calls using keyword arguments. Here's what the keyword-using decorator might look like:

def authenticate(func):
    def authenticate_and_call(*args, **kwargs):
        options = {'server': 'https://jira.xxxxxxx.com', 'verify': False}

        jira = JIRA(options)
        if not jira.current_user() == 'uname':
            jira = JIRA(options, basic_auth=('uname', passwd))

        return func(*args, jira=jira, **kwargs)

    return authenticate_and_call

This will work with the function above, but it won't interfere with a call like:

 create_new_jira_bug(jira_fields={"foo": "bar"})

Upvotes: 2

Related Questions