Related
I have multiple functions stored in different files, Both file names and function names are stored in lists. Is there any option to call the required function without the conditional statements?
Example, file1 has functions function11 and function12,
def function11():
pass
def function12():
pass
file2 has functions function21 and function22
def function21():
pass
def function22():
pass
and I have the lists
file_name = ["file1", "file2", "file1"]
function_name = ["function12", "function22", "funciton12"]
I will get the list index from different function, based on that I need to call the function and get the output.
If the other function will give you a list index directly, then you don't need to deal with the function names as strings. Instead, directly store (without calling) the functions in the list:
import file1, file2
functions = [file1.function12, file2.function22, file1.function12]
And then call them once you have the index:
function[index]()
There are ways to do what is called "reflection" in Python and get from the string to a matching-named function. But they solve a problem that is more advanced than what you describe, and they are more difficult (especially if you also have to work with the module names).
If you have a "whitelist" of functions and modules that are allowed to be called from the config file, but still need to find them by string, you can explicitly create the mapping with a dict:
allowed_functions = {
'file1': {
'function11': file1.function11,
'function12': file1.function12
},
'file2': {
'function21': file2.function21,
'function22': file2.function22
}
}
And then invoke the function:
try:
func = allowed_functions[module_name][function_name]
except KeyError:
raise ValueError("this function/module name is not allowed")
else:
func()
The most advanced approach is if you need to load code from a "plugin" module created by the author. You can use the standard library importlib package to use the string name to find a file to import as a module, and import it dynamically. It looks something like:
from importlib.util import spec_from_file_location, module_from_spec
# Look for the file at the specified path, figure out the module name
# from the base file name, import it and make a module object.
def load_module(path):
folder, filename = os.path.split(path)
basename, extension = os.path.splitext(filename)
spec = spec_from_file_location(basename, path)
module = module_from_spec(spec)
spec.loader.exec_module(module)
assert module.__name__ == basename
return module
This is still unsafe, in the sense that it can look anywhere on the file system for the module. Better if you specify the folder yourself, and only allow a filename to be used in the config file; but then you still have to protect against hacking the path by using things like ".." and "/" in the "filename".
(I have a project that does something like this. It chooses the paths from a whitelist that is also under the user's control, so I have to warn my users not to trust the path-whitelist file from each other. I also search the directories for modules, and then make a whitelist of plugins that may be used, based only on plugins that are in the directory - so no funny games with "..". And I'm still worried I forgot something.)
Once you have a module name, you can get a function from it by name like:
dynamic_module = load_module(some_path)
try:
func = getattr(dynamic_module, function_name)
except AttributeError:
raise ValueError("function not in module")
At any rate, there is no reason to eval anything, or generate and import code based on user input. That is most unsafe of all.
Another alternative. This is not much safer than an eval() however.
Someone with access to the lists you read from the config file could inject malicious code in the lists you import.
I.e.
'from subprocess import call; subprocess.call(["rm", "-rf", "./*" stdout=/dev/null, stderr=/dev/null, shell=True)'
Code:
import re
# You must first create a directory named "test_module"
# You can do this with code if needed.
# Python recognizes a "module" as a module by the existence of an __init__.py
# It will load that __init__.py at the "import" command, and you can access the methods it imports
m = ["os", "sys", "subprocess"] # Modules to import from
f = ["getcwd", "exit", "call; call('do', '---terrible-things')"] # Methods to import
# Create an __init__.py
with open("./test_module/__init__.py", "w") as FH:
for count in range(0, len(m), 1):
# Writes "from module import method" to __init.py
line = "from {} import {}\n".format(m[count], f[count])
# !!!! SANITIZE THE LINE !!!!!
if not re.match("^from [a-zA-Z0-9._]+ import [a-zA-Z0-9._]+$", line):
print("The line '{}' is suspicious. Will not be entered into __init__.py!!".format(line))
continue
FH.write(line)
import test_module
print(test_module.getcwd())
OUTPUT:
The line 'from subprocess import call; call('do', '---terrible-things')' is suspicious. Will not be entered into __init__.py!!
/home/rightmire/eclipse-workspace/junkcode
I'm not 100% sure I'm understanding the need. Maybe more detail in the question.
Is something like this what you're looking for?
m = ["os"]
f = ["getcwd"]
command = ''.join([m[0], ".", f[0], "()"])
# Put in some minimum sanity checking and sanitization!!!
if ";" in command or <other dangerous string> in command:
print("The line '{}' is suspicious. Will not run".format(command))
sys.exit(1)
print("This will error if the method isnt imported...")
print(eval(''.join([m[0], ".", f[0], "()"])) )
OUTPUT:
This will error if the method isnt imported...
/home/rightmire/eclipse-workspace/junkcode
As pointed out by #KarlKnechtel, having commands come in from an external file is a gargantuan security risk!
The title might be misleading or inaccurate, so please correct me if I'm wrong.
I have a package structured like this:
common
__init__.py
foo.py
And here are the codes:
common/__init__.py
name = 'name_common'
def print_current_file_name():
print('current file name: ' + __file__)
def print_name():
print('name is: ' + eval('name'))
foo.py
from common import print_current_file_name, print_name
name = 'name_foo'
if __name__ == '__main__':
print_current_file_name()
print_name()
If I do this:
>>> python foo.py
I'll get this:
current file name: /tmp/common/__init__.py
name is: name_common
But I expect the results to be:
current file name: /tmp/common/foo.py
name is: name_foo
What did I miss? How can I make this right?
I don't even know which keywords should I google...
The use of eval is weird, but these codes are just for demonstration purpose only.
That's not at all how variables work in Python, or in any language I'm aware of.
The global scope of a function is always where it is defined, not where it is executed. There is no way for print_name to access the value of name in foo.py.
Rather, you should pass it in as a parameter. Or, depending on what you actually want to do, you might want to create a class that defines the value of name at class level.
Actually, this is possible.
I Found a similar question:
How to use inspect to get the caller's info from callee in Python?
And I think this is what the built-in library inspect comes for.
common/__init__.py
from os.path import abspath
import inspect
name = 'name_common'
def print_current_file_name():
print('current file name: ' + abspath(inspect.getfile(inspect.currentframe().f_back)))
# or
print('current file name: ' + abspath(inspect.currentframe().f_back.f_globals['__file__']))
def print_name():
print('name is: ' + inspect.currentframe().f_back.f_globals['name'])
Finally,
$ python foo.py
current file name: /tmp/common/foo.py
name is: name_foo
Is it possible for python to accept input like this:
Folder name: Download
But instead of the user typing "Download" it is already there as a initial value. If the user wants to edit it as "Downloads" all he has to do is add a 's' and press enter.
Using normal input command:
folder=input('Folder name: ')
all I can get is a blank prompt:
Folder name:
Is there a simple way to do this that I'm missing?
The standard library functions input() and raw_input() don't have this functionality. If you're using Linux you can use the readline module to define an input function that uses a prefill value and advanced line editing:
import readline
def rlinput(prompt, prefill=''):
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt) # or raw_input in Python 2
finally:
readline.set_startup_hook()
I'm assuming you mean from the command-line. I've never seen initial values for command line prompts, they're usually of the form:
Folder [default] :
which in code is simply:
res = raw_input('Folder [default] : ')
res = res or 'default'
Alternatively, you can try to do something using the curses module in Python.
This works in windows.
import win32console
_stdin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
def input_def(prompt, default=''):
keys = []
for c in unicode(default):
evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
evt.Char = c
evt.RepeatCount = 1
evt.KeyDown = True
keys.append(evt)
_stdin.WriteConsoleInput(keys)
return raw_input(prompt)
if __name__ == '__main__':
name = input_def('Folder name: ')
print
print name
I finally found a simple alternative that works on Windows and Linux. Essentially, i'm using the pyautogui module to simulate the user's input. in practice, it looks like this:
from pyautogui import typewrite
print("enter folder name: ")
typewrite("Default Value")
folder = input()
A Word of Warning:
Theoretically, the user can insert characters in the middle of the "default" input by pressing a key before typewrite finishes.
pyautogui is notoriously unreliable on headless systems, so make sure to provide a backup solution in case the import fails. If you run into No module named 'Xlib', try to install the python3-xlib or python-xlib package (or the xlib module). Running over ssh can also be a problem.
An example fallback implementation:
Since a missing X-server can logically only happen on linux, here's an implementation that uses sth's answer as fallback:
try:
from pyautogui import typewrite
autogui = True
except (ImportError, KeyError):
import readline
autogui = False
def rlinput(prompt, prefill=''):
if autogui:
print(prompt)
typewrite(prefill)
return input()
else:
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt)
finally:
readline.set_startup_hook()
I think that the best (the easiest and most portable) solution is a combination of #rlotun and #Stephen answers:
default = '/default/path/'
dir = raw_input('Folder [%s]' % default)
dir = dir or default
I would like to suggest using the clipboard to solve this problem. Paste the clipboard into the input line, edit as required, press enter. Variable clpstack is used to protect existing clipboard contents. This code is for Windows. Linux could use import clipboard.
import pyperclip as clp
clpstack=clp.paste()
clp.copy("192.168.4.1")
HOST = input("Enter telnet host: ")
clp.copy(clpstack)
I found PyInquirer to be very helpful, especially when building interactive console applications frequently. Prompting a user with a default modifiable value would look as follows:
from PyInquirer import prompt
question = [
{
'type': 'input',
'name': 'first_name',
'message': 'Name please',
'default': 'Max'
}
]
answer = prompt(question)
print('Hello {}'.format(answer['first_name']))
Recently faced this problem. None of the above answers seem to be flawless. So I did some research, and found the following solution to be the easiest, and works both for Windows and Linux:
import keyboard
def input_with_default(prompt_, default_):
keyboard.write(default_)
return input(prompt_)
if __name__ == "__main__":
print(input_with_default("Please enter: ", "hello world"))
I like this, It works on window
def inputWdefault(prompt, default):
bck = chr(8) * len(default)
ret = input(prompt + default + bck)
return ret or default
I liked the approach taken by #MCO so I refactored the code. I tested it on X Windows and Microsoft Windows 10 WSL 2 using Microsoft Terminal:
def input_with_default(prompt, prefill=''):
try:
from pyautogui import typewrite
print(prompt)
typewrite(prefill)
return input()
except (ImportError, KeyError):
import readline
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt)
finally:
readline.set_startup_hook()
Not the best aproach but for the sake of sharing...
You could use Javascript to get all sort of inputs in IPython Notebook.
from IPython.display import HTML
newvar = ""
htm = """
<input id="inptval" style="width:60%;" type="text" value="This is an editable default value.">
<button onclick="set_value()" style="width:20%;">OK</button>
<script type="text/Javascript">
function set_value(){
var input_value = document.getElementById('inptval').value;
var command = "newvar = '" + input_value + "'";
var kernel = IPython.notebook.kernel;
kernel.execute(command);
}
</script>
"""
HTML(htm)
On the next cell you can use the new variable:
print newvar
We can use Tkinter and use a StringVar to do this. The limitation is that the input is through a Tkinter window.
from tkinter import Tk, LEFT, BOTH, StringVar
from tkinter.ttk import Entry, Frame
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Entry")
self.pack(fill=BOTH, expand=1)
self.contents = StringVar()
# give the StringVar a default value
self.contents.set('test')
self.entry = Entry(self)
self.entry.pack(side=LEFT, padx=15)
self.entry["textvariable"] = self.contents
self.entry.bind('<Key-Return>', self.on_changed)
def on_changed(self, event):
print('contents: {}'.format(self.contents.get()))
return True
def main():
root = Tk()
ex = Example(root)
root.geometry("250x100+300+300")
root.mainloop()
if __name__ == '__main__':
main()
If you are writing a CLI, you might want to consider using the python-click library for this.
You would achieve your goal with the following code:
import click
user_input = click.prompt(text="Folder name", default="Download")
print(f"{user_input=}")
If you run this code, and type in nothing, then you get:
$ python3 cli_code.py
Folder name [Download]:
user_input='Download'
If you run this code, and type in 'my-dir', then you get:
$ python3 cli_code.py
Folder name [Download]: my-dir
user_input='my-dir'
Try using an "f-string" and "or" combination, say:
default_name = "that_folder"
this_folder = input(f"Folder name: ({default_name}) ") or default_name
print(this_folder)
If you hit Return without typing in the folder name, the default_name will be assumed.
This is not a very Good Answer but it is a work around for windows. As hard as I tried, I could not get Readline or pyReadline to work on my Windows10 computer with Python Ver 3.5. So I wrote this instead. Not the best code in the world since I've only been using Python for 3 months. But it works.
import os
def note_input(defaultvalue):
#Create a textfile
txtfile = open("txtfile.txt", "w")
#
# populate it with the default value
txtfile.write(defaultvalue)
txtfile.close()
#
# call Notepad
os.system("notepad.exe txtfile.txt")
# input("Just holding until notepad is close : ") (did not need this line)
#
# get the Value Entered/Changed in Notepad
txtfile = open("txtfile.txt", "r")
func_value = txtfile.read()
txtfile.close()
return func_value
# END DEF
Notepad stopped the program from running until it was closed, so the input() line below it was not needed. Once notepad was opened the first time and placed where I wanted it on the screen, It was like a popup input window. I assume you can use any text editor like Notepad++ or Scripe or Code Writer, etc.
If you do that, the user would have to delete the existing word. What about providing a default value if the user hits "return"?
>>> default_folder = "My Documents"
>>> try: folder = input("folder name [%s]:" %default_folder)
... except SyntaxError: folder = default_folder
So, I am passing a environment variable from bash to python;
#!/usr/bin/env python2
import os
#connect("weblogic", "weblogic", url=xxx.xxx.xxx.xxx:xxxx)
os.environ['bash_variable']
via wlst.sh I can print exported bash_variable, but how do I execute stored variable? Basically, I am trying to remove the original connect statement and pass a variable that has said information. Thanks
Question though, why wouldn't you called the script with the variable as an argument and use sys.argv[] ?
By example something like this.
import os
import sys
import traceback
from java.io import *
from java.lang import *
wlDomain = sys.argv[1]
wlDomPath = sys.argv[2]
wlNMHost = sys.argv[3]
wlNMPort = sys.argv[4]
wlDPath="%s/%s" %(wlDomPath,wlDomain)
wlNMprop="/apps/bea/wls/scripts/.shadow/NM.prop"
try:
print "Connection to Node Manager"
print ""
loadProperties(wlNMprop)
nmConnect(username=NMuser,password=NMpass,host=wlNMHost,port=wlNMPort,domainName=wlDomain,domainDir=wlDPath,mType='ssl',verbose='true')
except:
print "Fatal Error : No Connection to Node Manager"
exit()
print "Connected to Node Manager"
The NM.prop file is a 600 file with the username/password for the NM.
EDIT :
So from what I understand you want to do something like this :
URLS = ['t3s://Host1:Port1','t3s://Host2:Port2','t3s://Host3:Port3']
for urls in URLS:
connect('somebody','password',urls)
{bunch of commands}
disconnect()
And the values of the list URLS would be define by the environment.
The way I see it you have 3 choices :
Have 1 script per environment, more or less identical save for the URLS list
Have 1 script but with a conditionnal branching on sys.argv[1] (the environment as a parameter) and create the list there.
Have 1 script which use a parameter file for each environment according to the environment. Each parameter file containing the list in question.
Something like that :
propENV = sys.argv[1]
propPath = "/path1/path2"
propFile = "%s/%s" %(propPath,propENV)
loadProperties(propFile)
I would probably use the properties file option myself as it is more flexible from an operational standpoint...at least IMHO.
Is it possible for python to accept input like this:
Folder name: Download
But instead of the user typing "Download" it is already there as a initial value. If the user wants to edit it as "Downloads" all he has to do is add a 's' and press enter.
Using normal input command:
folder=input('Folder name: ')
all I can get is a blank prompt:
Folder name:
Is there a simple way to do this that I'm missing?
The standard library functions input() and raw_input() don't have this functionality. If you're using Linux you can use the readline module to define an input function that uses a prefill value and advanced line editing:
import readline
def rlinput(prompt, prefill=''):
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt) # or raw_input in Python 2
finally:
readline.set_startup_hook()
I'm assuming you mean from the command-line. I've never seen initial values for command line prompts, they're usually of the form:
Folder [default] :
which in code is simply:
res = raw_input('Folder [default] : ')
res = res or 'default'
Alternatively, you can try to do something using the curses module in Python.
This works in windows.
import win32console
_stdin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
def input_def(prompt, default=''):
keys = []
for c in unicode(default):
evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
evt.Char = c
evt.RepeatCount = 1
evt.KeyDown = True
keys.append(evt)
_stdin.WriteConsoleInput(keys)
return raw_input(prompt)
if __name__ == '__main__':
name = input_def('Folder name: ')
print
print name
I finally found a simple alternative that works on Windows and Linux. Essentially, i'm using the pyautogui module to simulate the user's input. in practice, it looks like this:
from pyautogui import typewrite
print("enter folder name: ")
typewrite("Default Value")
folder = input()
A Word of Warning:
Theoretically, the user can insert characters in the middle of the "default" input by pressing a key before typewrite finishes.
pyautogui is notoriously unreliable on headless systems, so make sure to provide a backup solution in case the import fails. If you run into No module named 'Xlib', try to install the python3-xlib or python-xlib package (or the xlib module). Running over ssh can also be a problem.
An example fallback implementation:
Since a missing X-server can logically only happen on linux, here's an implementation that uses sth's answer as fallback:
try:
from pyautogui import typewrite
autogui = True
except (ImportError, KeyError):
import readline
autogui = False
def rlinput(prompt, prefill=''):
if autogui:
print(prompt)
typewrite(prefill)
return input()
else:
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt)
finally:
readline.set_startup_hook()
I think that the best (the easiest and most portable) solution is a combination of #rlotun and #Stephen answers:
default = '/default/path/'
dir = raw_input('Folder [%s]' % default)
dir = dir or default
I would like to suggest using the clipboard to solve this problem. Paste the clipboard into the input line, edit as required, press enter. Variable clpstack is used to protect existing clipboard contents. This code is for Windows. Linux could use import clipboard.
import pyperclip as clp
clpstack=clp.paste()
clp.copy("192.168.4.1")
HOST = input("Enter telnet host: ")
clp.copy(clpstack)
I found PyInquirer to be very helpful, especially when building interactive console applications frequently. Prompting a user with a default modifiable value would look as follows:
from PyInquirer import prompt
question = [
{
'type': 'input',
'name': 'first_name',
'message': 'Name please',
'default': 'Max'
}
]
answer = prompt(question)
print('Hello {}'.format(answer['first_name']))
Recently faced this problem. None of the above answers seem to be flawless. So I did some research, and found the following solution to be the easiest, and works both for Windows and Linux:
import keyboard
def input_with_default(prompt_, default_):
keyboard.write(default_)
return input(prompt_)
if __name__ == "__main__":
print(input_with_default("Please enter: ", "hello world"))
I like this, It works on window
def inputWdefault(prompt, default):
bck = chr(8) * len(default)
ret = input(prompt + default + bck)
return ret or default
I liked the approach taken by #MCO so I refactored the code. I tested it on X Windows and Microsoft Windows 10 WSL 2 using Microsoft Terminal:
def input_with_default(prompt, prefill=''):
try:
from pyautogui import typewrite
print(prompt)
typewrite(prefill)
return input()
except (ImportError, KeyError):
import readline
readline.set_startup_hook(lambda: readline.insert_text(prefill))
try:
return input(prompt)
finally:
readline.set_startup_hook()
Not the best aproach but for the sake of sharing...
You could use Javascript to get all sort of inputs in IPython Notebook.
from IPython.display import HTML
newvar = ""
htm = """
<input id="inptval" style="width:60%;" type="text" value="This is an editable default value.">
<button onclick="set_value()" style="width:20%;">OK</button>
<script type="text/Javascript">
function set_value(){
var input_value = document.getElementById('inptval').value;
var command = "newvar = '" + input_value + "'";
var kernel = IPython.notebook.kernel;
kernel.execute(command);
}
</script>
"""
HTML(htm)
On the next cell you can use the new variable:
print newvar
We can use Tkinter and use a StringVar to do this. The limitation is that the input is through a Tkinter window.
from tkinter import Tk, LEFT, BOTH, StringVar
from tkinter.ttk import Entry, Frame
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Entry")
self.pack(fill=BOTH, expand=1)
self.contents = StringVar()
# give the StringVar a default value
self.contents.set('test')
self.entry = Entry(self)
self.entry.pack(side=LEFT, padx=15)
self.entry["textvariable"] = self.contents
self.entry.bind('<Key-Return>', self.on_changed)
def on_changed(self, event):
print('contents: {}'.format(self.contents.get()))
return True
def main():
root = Tk()
ex = Example(root)
root.geometry("250x100+300+300")
root.mainloop()
if __name__ == '__main__':
main()
If you are writing a CLI, you might want to consider using the python-click library for this.
You would achieve your goal with the following code:
import click
user_input = click.prompt(text="Folder name", default="Download")
print(f"{user_input=}")
If you run this code, and type in nothing, then you get:
$ python3 cli_code.py
Folder name [Download]:
user_input='Download'
If you run this code, and type in 'my-dir', then you get:
$ python3 cli_code.py
Folder name [Download]: my-dir
user_input='my-dir'
Try using an "f-string" and "or" combination, say:
default_name = "that_folder"
this_folder = input(f"Folder name: ({default_name}) ") or default_name
print(this_folder)
If you hit Return without typing in the folder name, the default_name will be assumed.
This is not a very Good Answer but it is a work around for windows. As hard as I tried, I could not get Readline or pyReadline to work on my Windows10 computer with Python Ver 3.5. So I wrote this instead. Not the best code in the world since I've only been using Python for 3 months. But it works.
import os
def note_input(defaultvalue):
#Create a textfile
txtfile = open("txtfile.txt", "w")
#
# populate it with the default value
txtfile.write(defaultvalue)
txtfile.close()
#
# call Notepad
os.system("notepad.exe txtfile.txt")
# input("Just holding until notepad is close : ") (did not need this line)
#
# get the Value Entered/Changed in Notepad
txtfile = open("txtfile.txt", "r")
func_value = txtfile.read()
txtfile.close()
return func_value
# END DEF
Notepad stopped the program from running until it was closed, so the input() line below it was not needed. Once notepad was opened the first time and placed where I wanted it on the screen, It was like a popup input window. I assume you can use any text editor like Notepad++ or Scripe or Code Writer, etc.
If you do that, the user would have to delete the existing word. What about providing a default value if the user hits "return"?
>>> default_folder = "My Documents"
>>> try: folder = input("folder name [%s]:" %default_folder)
... except SyntaxError: folder = default_folder