Print file name and line number - python-3.x

I want to print the name of file along with line number, but it print the library name and its line number.
#!/bin/env python3
# library source code (lib.py)
from inspect import currentframe, getframeinfo
def get_file_name():
return __file__
def get_line():
frameinfo = getframeinfo(currentframe())
return f"[{frameinfo.filename}:{frameinfo.lineno}]"
def log(data):
print(f"{get_line()} {data}")
# program source code (program.py)
#!/bin/env python3
import lib
lib.log("hi")
Output:
[/home/shubhapp/python3/codathon/doubt/lib.py:9] hi
Expected output:
[/home/shubhapp/python3/codathon/doubt/program.py:7] hi

def get_file_name():
return __file__
__file__ is returned from the namespace of the module itself, not from the caller. That's why you didn't get main.py, but test.py.
def get_line():
frameinfo = getframeinfo(currentframe())
return f"[{frameinfo.filename}:{frameinfo.lineno}]"
You are not getting desired output because currentframe() gave you the frame in which get_line was being executed, not where it was invoked. To get to the invocation point use the following approach:
get get_line():
# fs means FrameSummary.
caller_fs = traceback.extract_stack()[0]
filename, lineno = caller_fs.filename, caller_fs.lineno
# Do whatever you want to do now.
If you want to see intermediate invocation points, you can use traceback.print_stack().

Related

Making a flag in argparse require 2 arguments with different types

I'd like to make a parser for a program like follows program --serve some/path /file/to/serve.html
Looking at the argparse documentation https://docs.python.org/3/library/argparse.html#type
I cannot for the life of me figure out how I could parse the first argument of --serve as a string and the second as argparse.FileType('r')
I'd like to do something like
parser.add_argument('--serve', nargs=2, type=(str, argparse.FileType('r')), action='append', help='...')
Is there a way to do this with argparse?
If you implement a custom type and instead of using --nargs=2 you use a delimiter to separate the two arguments, you could do something like this:
import os
import stat
import argparse
def mytype(v):
dirpath, filepath = v.split(':')
try:
res = os.stat(filepath)
except FileNotFoundError:
raise ValueError(f'{filepath} does not exist')
else:
if not stat.S_ISREG(res.st_mode):
raise ValueError(f'{filepath} is not a regular file')
return dirpath, filepath
p = argparse.ArgumentParser()
p.add_argument('--serve', type=mytype, action='append', help='...')
args = p.parse_args()
print(args)
If I run this in a directory that contains the file foo.txt, we see:
# with a file that does not exist
$ python argtest.py --serve somedir:bar.txt
usage: argtest.py [-h] [--serve SERVE]
argtest.py: error: argument --serve: invalid mytype value: 'somedir:bar.txt'
# with a file that *does* exist
$ python argtest.py --serve somedir:foo.txt
Namespace(serve=[('somedir', 'foo.txt')])
This isn't opening the file; it's testing that the second argument points to a regular file. You could instead of the file and return a file object instead of the path if you want.

Convert script accepting command line arguments to be importable as well

I've got a script (foo.py) which I originally developed to be called from the command line with named arguments. I'd now like to refactor it so it may called both from the command line and via import as well. Calling the script as is after import throws an unexpected keyword argument error, as written (since the main function itself has no arguments). I was under the impression that the __name__=="__main__" chunk would've provided the same experience in both calls, but I've obviously misunderstood. How could I refactor this so it can take the named arguments interactively and upon import by another script?
foo.py
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--token', help='API token')
parser.add_argument('--namedArgA', help='Switch parameter', action='store_true')
return parser.parse_args()
def main():
args = parse_args()
token = args.token
# Other stuff with the args
print(argDict)
if __name__=="__main__":
main()
bar.py
import foo
baz = foo.main(token='myToken',namedArgA='True')

How to write a simple test code to test a python program that has a function with two arguments?

I'm new in python, I have written a python program that reads a list of files and saves the total number of a particular character (ch) in a dictionary and then returns it.
The program works fine, now I'm trying to write a simple test code to test the program.
I tried with the following code,
def test_read_files():
assert read_files("H:\\SomeTextFiles\\zero-k.txt", 'k') == 0, "Should be 0"
if __name__ == "__main__":
test_read_files()
print("Everything passed")
I named the program as test_read_files.py
My python code is as follows:
# This function reads a list of files and saves number of
# a particular character (ch) in dictionary and returns it.
def read_files(filePaths, ch):
# dictionary for saing no of character's in each file
dictionary = {}
for filePath in filePaths:
try:
# using "with statement" with open() function
with open(filePath, "r") as file_object:
# read file content
fileContent = file_object.read()
dictionary[filePath] = fileContent.count(ch)
except Exception:
# handling exception
print('An Error with opening the file '+filePath)
dictionary[filePath] = -1
return dictionary
fileLists = ["H:\\SomeTextFiles\\16.txt", "H:\\SomeTextFiles\\Statement1.txt",
"H:\\SomeTextFiles\\zero-k.txt", "H:\\SomeTextFiles"]
print(read_files(fileLists, 'k'))
I named it as read_files.py
When I run the test code, getting an error: NameError: name 'read_files' is not defined
The program and the test code all are in the same folder (different than the python folder though).
Hopefully I am understanding this correctly, but if both of you python files:
test_read_files.py
read_files.py
Are in the same directory.. Then you should be able to just add at the top of the test_read_files.py the following import command:
from read_files import read_files
This will import the read_files function from your read_files.py script and that way you will be able to run it inside the other file.

Calling functions from a script using argparse without using subprocess

I have been given an existing script (let's call it existing.py) that in its MVCE form has the following structure.
import argparse
FLAGS = None
def func():
print(FLAGS.abc)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--abc',
type=str,
default='',
help='abc.'
)
FLAGS, unparsed = parser.parse_known_args()
func()
As this is part of tool that gets constantly updated, I cannot change existing.py. Normally, existing.py is invoked with commandline arguments.
python -m existing.py --abc "Ok"
which prints the output 'Ok'.
I wish to call the functions (not the whole script) in existing.py using another script. How can I feed in the FLAGS object that is used in the functions of the script? I do not wish to use subprocess will just run the script in its entirety.
I know that argparse creates the FLAGS as a Namespace dictionary and I can construct it in calling.py (see code below) but I cannot then push it back into the function that is imported from existing.py into calling.py. The following is the calling.py that I've tried.
from existing import func
import argparse
args = argparse.Namespace()
args.abc = 'Ok'
FLAGS = args
func()
which throws an error
AttributeError: 'NoneType' object has no attribute 'abc'
This is different from other StackOverflow questions as this question explicitly forbids subprocess and the existing script cannot be changed.
Import existing and use
existing.FLAGS = args
Now functions defined in the existing namespace should see the desired FLAGS object.

How to import variable values from a file that is running?

I am running a python file, say file1, and in that, I am importing another python file, say file2, and calling one of its functions. Now, the file2 needs the value of a variable which is defined in file 1. Also, before importing file2 in file1, the value of the variable was changed during the run-time. How do I make the file file2, access the current value of the variable from file 1?
The content of file1 is:
variable = None
if __name__ == '__main__':
variable = 123
from file2 import func1
func1()
The content of file2 is:
from file1 import variable as var
def func1():
print(var)
When I run the file1, I want the function func1 in file2 to print 123. But it prints None. One way I can tackle this is by saving the content of the variable in some ordinary file when it is modified, and then retrieving it when needed. But the application in which I am using this code, the size of the variable is massive, like around 300 MB. So, I believe it won't be efficient enough to write the content of the variable in a text file, every time it is modified. How do I do this? (Any suggestions are welcome)
The main script is run with the name __main__, not by its module name. This is also how the if __name__ == '__main__' check works. Importing it by its regular name creates a separate module with the regular content.
If you want to access its attributes, import it as __main__:
from __main__ import variable as var
def func1():
print(var)
Note that importing __main__ is fragile. On top of duplicating the module, you may end up importing a different module if your program structure changes. If you want to exchange global data, use well-defined module names:
# constants.py
variable = None
# file1.py
if __name__ == '__main__':
import constants
constants.variable = 123
from file2 import func1
func1()
# file2.py
from constants import variable as var
def func1():
print(var)
Mandatory disclaimer: Ideally, functions do not rely on global variables. Use parameters for passing variables into functions:
# constants.py
variable = None
# file1.py
if __name__ == '__main__':
from file2 import func1
func1(123)
# file2.py
from constants import variable
def func1(var=variable):
print(var)

Resources