Reputation: 2336
Some background:
I am trying to write a decorator for logging. Specifically, I work with arcpy
a lot and the way to get messages back from tools is to use arcpy.GetMessages()
. However, this is kind of an annoying function because it only holds the most recent message and must be called after every tool. A pseudo-code example
import arcpy
import logging
log = logging.getLogger(__name__)
def test_function(in_data):
out_data = 'C:/new_path/out_data'
arcpy.MakeFeatureLayer_management(in_data, out_data)
log.info(arcpy.GetMessages())
arcpy.Delete_management(in_data)
log.info(arcpy.GetMessages())
# If you did log.info(arcpy.GetMessages()) again here you'd just get
# the message from the Delete tool again
It would be much better to write a decorator that could identify any time an arcpy
function was called and log it. Like:
def log_the_arcpy(fn):
@functools.wraps(fn)
def inner(*args, **kwargs):
result = fn(*args, **kwargs)
# Some magic happens here?!
if module_parent == arcpy: #module_parent is obviously fake, is there a real attribute?
log.info(arcpy.GetMessages())
return result
return inner
However, I am quite stuck in two places: (1) how to identify the "arcpy-ness" (or whatever package) of an individual function, and (2) the overall approach to dig inside of a function with a decorator and determine the package membership of potentially many function calls.
Bits and pieces that have seemed useful are:
hasattr
(but doesn't seem quite right/maybe very slow?)inspect
and/or a generator to pause executionNone of these are ideas that are very well fleshed out - this because many of these topics are quite new to me. I would appreciate any direction - I'm trying to ask early so I don't get locked into asking a bunch of XY Problem questions later.
Upvotes: 1
Views: 3497
Reputation: 25799
If you're going to be calling methods directly on arcpy
, wrapping the module would probably be the easiest and least performance-affecting approach:
# arcpy_proxy.py
import arcpy as _arcpy
import logging
class _proxy(object):
def __getattr__(self, item):
ref = getattr(_arcpy, item)
if callable(ref): # wrap only function calls
return self._wrap(ref)
return ref
@classmethod
def _wrap(cls, func):
def inner(*args, **kwargs):
val = func(*args, **kwargs)
logging.info(_arcpy.GetMessages()) # log the messages
return val
return inner
arcpy = _proxy()
Then you can just do from arcpy_proxy import arcpy
as a drop-in replacement. You can even add sys.modules["arcpy"] = arcpy
in your main script (after the import of course) so you don't have to replace it anywhere else to have it proxied.
Upvotes: 2
Reputation: 85492
This should work:
if hasattr(fn, '__module__') and getattr(fn, '__module__') == 'arcpy':
log.info(arcpy.GetMessages())
Full function:
def log_the_arcpy(fn):
@functools.wraps(fn)
def inner(*args, **kwargs):
result = fn(*args, **kwargs)
if hasattr(fn, '__module__') and getattr(fn, '__module__') == 'arcpy':
log.info(arcpy.GetMessages())
return result
return inner
Upvotes: 0