In a project I am doing, I have to perform a lot of repetitive checks at the beginning of each API end point. As the amount of duplicate code started to grow, I thought of using a decorator to wrap all of my api endpoint view functions, and to perform the checks there. This approach actually turned out to be useful for a while. but then again as the time passed, the number of checks growed, and the end points diverged in the tests that they needed. Now, I have ended up with a decorator that is quite long, and which takes a variety of parameters, and handles different responsibilities. To me, it smells of bad design, but I can't understand how I can fix it. I thought about adding other decorators but that doesn't seem to be applicable here, as I want my tests to run prior to the execution of the view it self, (the code after the view execution is fixed). But as far as I know, multiple decorators each wrap the other one. Would you mind providing me with any tips on the matter please?
-
1From a software engineering point, my best recommendation here is that you show someone else the code (maybe on codereview.stackexchange). IMHO your question contains barely enough information to give you some more specific adivice.– Doc BrownJan 8 at 8:21
-
hmmm.. Thanks for the recommendation. I might just do so. I was hoping for general guidelines.– FarzadJan 8 at 8:38
-
1Can you break down you decorator into smaller classes that do checks with a more narrow focus (that may or may not be decorators themselves, up to you), and then combine them in various ways (perhaps using the Composite pattern) into a single object that serves as the decorator? Or perhaps make it a chain (or just a list) - could be similar to how middleware works in various web frameworks. Then you can have different combinations for different endpoints, and you can write simpler tests for the individual decorators.– Filip MilovanovićJan 8 at 22:28
-
"But as far as I know, multiple decorators each wrap the other one" - yeah, you can place them on top of each other, and this means (assuming each has an operation that executes before the wrapped operation) that the outermost one executes first, then the next one in, then the next one in, etc. So, everything executes in a sequence, before finally getting to the innermost wrapee. (And then, if you have an after-operation, those get executed in reverse order). The problem with that is that it can get unwieldy if you have a lot of levels.– Filip MilovanovićJan 8 at 22:35
-
@FilipMilovanović, Thanks for the detailed comments, that gave me a good deal of ideas.– FarzadJan 10 at 6:21
1 Answer
Composition is a powerful tool.
Here is one way to define a flask endpoint.
@lru_cache
@log_web_args
@require_creds
@report_elapsed_time
def report( ... ):
You can freely mix and match such decorators. They offer great economy of expression.
If you find your business use case for say, credentials, or for logging, becomes quite complex, then step back and re-evaluate. Perhaps you need more than one credentials decorator.
There are several tools in your toolbox. Find the right one for the task at hand.