Log importer modules from within the module that is being imported

For example, i have 2 .py files module1 and module2.
module2 imports module1 using the import statement.

I want to have some way inside the module1 to detect (from within the module1 itself) which modules importing it. in this case the module2 is importing it.

Why does module1 need to know? It seems an unusual design.

You could look at the python stack when module1 is loaded using the traceback module functions.

This will only work for the first import.

There is a way to determine within a function what function called the function (but is could be that the caller is a module). This involves looking back at the stack of execution frames, but I have never done this. There may be a function in inspect that helps with this. Look there.

this seems to do the job for me, or I dont know if its giving me illutions???.

I am using pydroid3 ide in my old android 7.0.

#assuming its the last frame
print(next(reversed(inspect.stack())).frame.f_globals[“file”])`

in which situations it can fall apart??

I consider this a code-smell that you are doing this at all.
Of course you may have a very good reason to do this.

Why do you want to do this?

this is what I am trying to do.

import ast
import inspect

def dprn(*iargs, sep="\n"):
	"""
		Goal of this function is to detect the lvalues of the arguments
		passed to it and then print the passed rvalue as it is in this
		form, <<- detected_lvalue: rvalue ->> respecting the sep argument.
		
		Currently it works only if the caller is on the same module
		because eval cant get the rvalue of a lvalue if its on a separate
		module, but, i want it to put on a myutils module, and want to
		import it to any module and use it. in future I may modify it to
		be a decorator.
	""" 
	### -0- detect from which module this func is being called
	cmf = next(reversed(inspect.stack())).frame.f_globals["__file__"]
	### -0-
	with open(cmf, mode="r", encoding="utf-8") as f:
		ap = ast.parse(f.read())
		### -1- dumping the full ast node tree for manual inspection
		cadf = f"{cmf[0:(cmf.rfind('/'))]}/castd.txt"
		with open(cadf, mode="wt", encoding="utf-8") as df:
			df.write(ast.dump(ap, indent=4))
		### -1-
		for cn in ast.walk(ap):
			if type(cn) is ast.Call:
				if type(cn.func) is ast.Name:
					if cn.func.id == "dprn":
						#print(f"cnd={ast.dump(cn)}\n")
						avl, anl, cnt = [], [], 0
						for cfa in cn.args:
							#print(f"cfa_d={ast.dump(cfa)}\n")
							can_up = ast.unparse(cfa)
							#print(can_up)
							try:
								### -2- trying to find the rvalue
								# of the current lvalue. but
								# eval will not work if the caller
								# is on a different module then
								# this dprn function. I need to
								# find a way to get the rvalue
								# of a lvalue which is in a
								# different module. I need this
								# because of the current caller
								# detection logic bellow.
								# for theres a different detection
								# logic, i may not need this.
								can_ev = eval(can_up)
								### -2-
							except NameError:
								can_ev = "NA"
							avl.append(can_ev)
							if type(cfa) is ast.Name:
								anl.append(can_up)
							else:
								anl.append(f"und{cnt}")
							cnt += 1
						#print(f"avl={avl}")
						avl = tuple(avl)
						### -3- current caller detection
						# but will not work if different
						# lvalues have the same rvalue.
						if avl == iargs: #detection logic
						### -3-
							lnl = len(sorted(anl, key=lambda e: len(e), reverse=True)[0])
							for n, v in zip(anl, avl):
								print(f"{n}{' '*(lnl-len(n))}: {v}", end=sep)

Is that code the answer to why you need to know who imports the module?
Can you just tell me in english proses and not code?

short answer is yes. and the long answer is if this func is on the same module as the caller then i dont need that but if both on a separate module and use the func by importing it then yes I do need to know which module called the func because ast to make the node tree needs the source code.

@indrajit The question is broader. Why are you doing any of this in the first place? What is the actual problem you want to solve, independent of any potential implementation? We ask, because all of this is extraordinary unusual, fragile, and error-prone. If you share your actual problem or motivation, then perhaps there is an entirely different solution we can suggest, that doesn’t involve this highly unorthodox approach.

5 Likes

i have already shared what i am up to. did you read the comments of the code i shared? my goal with this and my aporoach, and the problems I am facing. you can read it there.

btw, I found a better approach.

def dprn(*args, sep="\n"):
	if not type(sep) is str:
		raise TypeError(f"The optional keyward arg "
				f"sep must be a string, {type(sep)} "
				f"given.") 
	varn = [f"und{i}" for i in range(len(args))]
	il = inspect.currentframe().f_back.f_locals.items()
	for i, arg in enumerate(args):
		for n, v in il:
			if v == arg:
				varn[i] = n
	lvnl = len(sorted(varn, key=lambda x: len(x),
								reverse=True)[0])
	for a, b in zip(varn, args):
		print(f"{a}:{' '*(lvnl-len(a))} {b}", end=sep)

i am still testing the possible shortcommings, for now i found that if for example do like

xx, yy, zz = 11, 12, 11
dprn(xx, yy, zz)

then the detection logic falters. i need a different aproach for detection logic.

I did read your comments, but they don’t answer the “why” question I asked, at all. You seem committed to this unusual course, though, so I will just wish you good luck.

Change your api to pass in the required context would be one solution.
Maybe that is the module of the caller. Maybe an instance of a class to hold the data?

I guess that the data is such that you cannot avoid using eval?

why people make frankenstine? because they can. “what if i do this” is the root of every reason for anything. and for me beside this while debugging scripts i tend to use print function a lot, but then sometimes values needs to be named to understand whats doing what. and i am a lazy person, so writing those names=value sometimes becomes frastrating. so i wanted a solution. hope this is enough for you.