def arts_agenda(func): """ Parse python method as ARTS agenda This decorator can be used to define ARTS agendas using python function syntax. The function should have one arguments which is assumed to be a Workspace instance. All expressions inside the function must be calls to ARTS WSMs. The result is an Agenda object that can be used to copied into a named ARTS agenda Example: >>> @arts_agenda >>> def inversion_iterate_agenda(ws): >>> ws.x2artsStandard() >>> ws.atmfields_checkedCalc() >>> ws.atmgeom_checkedCalc() >>> ws.yCalc() >>> ws.VectorAddVector(ws.yf, ws.y, ws.y_baseline) >>> ws.jacobianAdjustAfterIteration() >>> >>> ws.Copy(ws.inversion_iterate_agenda, inversion_iterate_agenda) """ source = getsource(func) ast = parse(source) func_ast = ast.body[0] if not type(func_ast) == FunctionDef: raise Exception( "ARTS agenda definition can only decorate function definiitons.") args = func_ast.args.args try: arg_name = func_ast.args.args[0].arg except: raise Exception("Agenda definition needs workspace arguments.") ws = Workspace() context = func.__globals__ context[arg_name] == ws # Create agenda a_ptr = arts_api.create_agenda(func.__name__.encode()) agenda = Agenda(a_ptr) for e in func_ast.body: if not type(e.value) == Call: raise Exception("Agendas may only contain call expressions.") # Extract workspace object. try: call = e.value att = call.func.value if not att.id == arg_name: raise (Exception( "Agenda definition may only contain call to WSMs of the " + "workspace argument " + arg_name + ".")) except: raise (Exception( "Agenda definition may only contain call to WSMs of the " + "workspace argument " + arg_name + ".")) # Extract method name. try: name = call.func.attr m = workspace_methods[name] if not type(m) == WorkspaceMethod: raise Exception(name + " is not a known WSM.") except: raise Exception(name + " is not a known WSM.") # Extract positional arguments args = [ws, m] for a in call.args: args.append( eval(compile(Expression(a), "<unknown>", 'eval'), context)) # Extract keyword arguments kwargs = dict() for k in call.keywords: kwargs[k.arg] = eval( compile(Expression(k.value), "<unknown>", 'eval'), context) # Add function to agenda agenda.add_method(*args, **kwargs) return agenda
def arts_agenda(func): """ Parse python method as ARTS agenda This decorator can be used to define ARTS agendas using python function syntax. The function should have one arguments which is assumed to be a Workspace instance. All expressions inside the function must be calls to ARTS WSMs. The result is an Agenda object that can be used to copied into a named ARTS agenda Example: >>> @arts_agenda >>> def inversion_iterate_agenda(ws): >>> ws.x2artsStandard() >>> ws.atmfields_checkedCalc() >>> ws.atmgeom_checkedCalc() >>> ws.yCalc() >>> ws.VectorAddVector(ws.yf, ws.y, ws.y_baseline) >>> ws.jacobianAdjustAfterIteration() >>> >>> ws.Copy(ws.inversion_iterate_agenda, inversion_iterate_agenda) """ source = getsource(func) source = unindent(source) ast = parse(source) func_ast = ast.body[0] if not type(func_ast) == FunctionDef: raise Exception("ARTS agenda definition can only decorate function definiitons.") args = func_ast.args.args try: arg_name = func_ast.args.args[0].arg except: raise Exception("Agenda definition needs workspace arguments.") ws = Workspace(0) context = copy(func.__globals__) context.update({arg_name : ws}) # Add resolved non-local variables from closure. nls, _, _, _ = getclosurevars(func) context.update(nls) # # Helper functions # callback_body = [] def callback_make_fun(body): """ Helper function that creates a wrapper function around python code to be executed withing an ARTS agenda. """ m = Module(body) def callback(ptr): try: context[arg_name].ptr = ptr eval(compile(m , "<unknown>", 'exec'), context) except Exception as e: logger.error(r"Exception in Python callback:\n", e) context[arg_name].ptr = None callback_body = [] return callback def eval_argument(expr): """ Evaluate argument of workspace method call. """ if not hasattr(expr, "lineno"): setattr(expr, "lineno", 0) return eval(compile(Expression(expr), "<unknown>", 'eval'), context) # Create agenda a_ptr = arts_api.create_agenda(func.__name__.encode()) agenda = Agenda(a_ptr) illegal_statement_exception = Exception( "Agenda definitions may only contain calls to WSMs of the" "workspace argument " + arg_name + " or INCLUDE statements.") # # Here the body of the function definition is traversed. Cases # that are treated specieal are INCLUDE statements and calls # of workspace methods. Remaining statements are accumulated # in callback_body and then added to the agenda as a single callback. # for e in func_ast.body: if not isinstance(e, Expr): callback_body += [e] continue else: call = e.value if not isinstance(call, Call): callback_body += [e] continue # Include statement if type(call.func) == Name: if not call.func.id == "INCLUDE": callback_body += [e] else: args = [] for a in call.args: args.append(eval_argument(a)) include = Include(*args) if len(callback_body) > 0: agenda.add_callback(callback_make_fun(callback_body)) callback_body = [] arts_api.agenda_append(agenda.ptr, include.agenda.ptr) else: att = call.func.value if not att.id == arg_name: callback_body += [e] continue # Extract method name. name = call.func.attr # m is not a workspace method if not name in workspace_methods: callback_body += [e] continue # m is a workspace method. m = workspace_methods[name] args = [ws, m] for a in call.args: # Handle starred expression if type(a) == Starred: bs = eval_argument(a.value) for b in bs: args.append(b) continue args.append(eval_argument(a)) # Extract keyword arguments kwargs = dict() for k in call.keywords: kwargs[k.arg] = eval( compile(Expression(k.value), "<unknown>", 'eval'), context) # Add function to agenda if len(callback_body) > 0: agenda.add_callback(callback_make_fun(callback_body)) callback_body = [] agenda.add_method(*args, **kwargs) # Check if there's callback code left to add to the agenda. if len(callback_body) > 0: agenda.add_callback(callback_make_fun(callback_body)) callback_body = [] return agenda
def arts_agenda(func): """ Parse python method as ARTS agenda This decorator can be used to define ARTS agendas using python function syntax. The function should have one arguments which is assumed to be a Workspace instance. All expressions inside the function must be calls to ARTS WSMs. The result is an Agenda object that can be used to copied into a named ARTS agenda Example: >>> @arts_agenda >>> def inversion_iterate_agenda(ws): >>> ws.x2artsStandard() >>> ws.atmfields_checkedCalc() >>> ws.atmgeom_checkedCalc() >>> ws.yCalc() >>> ws.VectorAddVector(ws.yf, ws.y, ws.y_baseline) >>> ws.jacobianAdjustAfterIteration() >>> >>> ws.Copy(ws.inversion_iterate_agenda, inversion_iterate_agenda) """ source = getsource(func) source = unindent(source) ast = parse(source) func_ast = ast.body[0] if not type(func_ast) == FunctionDef: raise Exception( "ARTS agenda definition can only decorate function definiitons.") args = func_ast.args.args try: arg_name = func_ast.args.args[0].arg except: raise Exception("Agenda definition needs workspace arguments.") ws = Workspace() context = copy(func.__globals__) context.update({arg_name: ws}) # Add resolved non-local variables from closure. nls, _, _, _ = getclosurevars(func) context.update(nls) def eval_argument(expr): if not hasattr(expr, "lineno"): setattr(expr, "lineno", 0) return eval(compile(Expression(expr), "<unknown>", 'eval'), context) # Create agenda a_ptr = arts_api.create_agenda(func.__name__.encode()) agenda = Agenda(a_ptr) illegal_statement_exception = Exception( "Agenda definitions may only contain calls to WSMs of the" "workspace argument " + arg_name + " or INCLUDE statements.") for e in func_ast.body: try: call = e.value except: raise Exception("Agendas may only contain call expressions.") # Include statement if type(call.func) == Name: if not call.func.id == "INCLUDE": raise illegal_statement_exception else: args = [] for a in call.args: args.append(eval_argument(a)) include = Include(*args) arts_api.agenda_append(agenda.ptr, include.agenda.ptr) else: att = call.func.value if not att.id == arg_name: raise illegal_statement_exception # Extract method name. try: name = call.func.attr m = workspace_methods[name] if not type(m) == WorkspaceMethod: raise Exception(name + " is not a known WSM.") except: raise Exception(name + " is not a known WSM.") # Extract positional arguments args = [ws, m] for a in call.args: # Handle starred expression if type(a) == Starred: bs = eval_argument(a.value) for b in bs: args.append(b) continue args.append(eval_argument(a)) # Extract keyword arguments kwargs = dict() for k in call.keywords: kwargs[k.arg] = eval( compile(Expression(k.value), "<unknown>", 'eval'), context) # Add function to agenda agenda.add_method(*args, **kwargs) return agenda