Search code examples
pythonflaskpymongodecoratorpython-decorators

Looking to open pymongo database connection with decorator


@api.route('/api/get_contacts')
def get_contacts():
with pymongo.MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@dev.glstt.mongodb.net/xxxx?retryWrites=true&w=majority") as document:

This is too much to look at. I am trying to make it into:

@api.route('/api/get_contacts')
@mongo
def get_contacts():

The problem is, I don't really understand decorators. So, this is what I have done so far:


def mongo(f):
    @wraps(f)
    def wrap (*args,**kwargs):
        with pymongo.MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@dev.glstt.mongodb.net/xxx?retryWrites=true&w=majority") as document:

            return f(document)
        return wrap

Not sure what I need to do to get the decorator to prepend the code with the 'with operator', and pass x, then the document back to the route function.


Solution

  • Decorators works basically by replacing your function, with decorator(function). For instance:

    @decorator
    def my_function():
        pass
    

    Equals to

    def my_function():
        pass
    
    my_function = decorator(my_function)
    

    It means that you can return a callable function from the decorator, that every time you call it with arguments it will call the decorated function with the particular arguments.

    For example:

    import functools
    
    def decorator(decorated_function):
        @functools.wraps(decorated_function) # This means that the function that returns from this decorator, "wrapper", will keep the decorated_function's name,  `__doc__` argument and more.
        def wrapper(*args, **kwargs):
            """
            Every time you will call the function that returned from the decorator, this function will be called with the particular arguments in args and kwargs.
            """
            return decorated_function(*args, **kwargs) + 10
        return wrapper
    
    
    @decorator
    def func(n):
        return n * 2
    
    result = func(2) # here we called the function `wrapper`, the result is func(2) + 10, as we did when we called the function.
    
    print(result) # 14
    

    We can also print(func) and the result will be something like <function func at 0x7f5eb16b0040>, and if we didn't use functools.wraps: <function decorator.<locals>.wrapper at 0x7fba9ba0f040>.

    Now for your question, when you stack decorators,

    @c
    @b
    @a
    def func(): pass
    

    the order is c(b(a(func))) And hence, your function named mongo should take the argument "request" from api.route('/api/get_contacts'). (I don't know what framework this is, so I can't predict whether this framework supplies the request as a function argument).

    if the framework does not pass the request as an argument:

    def mongo(f):
        @functools.wraps(f)
        def wrap():
            with pymongo.MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@dev.glstt.mongodb.net/xxx?retryWrites=true&w=majority") as document:
                return f(document)
        return wrap
    

    and if it does:

    def mongo(f):
        @functools.wraps(f)
        def wrap(request):
            # do something with the request
            with pymongo.MongoClient(f"mongodb+srv://{mongo_username}:{mongo_password}@dev.glstt.mongodb.net/xxx?retryWrites=true&w=majority") as document:
                return f(document)
        return wrap
    
    

    And then it allows you to do this:

    @api.route('/api/get_contacts')
    @mongo
    def get_contacts(document):
        pass