Python3: how to use module 'Import click' & Parsing command line - python-3.x

I am a hobby radio amateur [G6SGA] not a programmer but I do try. :)
using python3. I am trying to do the following and really can't get my head around - argparse and ended up trying to use 'Import click'. Still can't get my head around so here I am. Any all (polite) :) suggestions welcome.
I wish to ---
cmd line> python3 scratch.py [no options supplied]
output> "Your defaults were used and are:9600 and '/dev/ttyAMA0' "
or
cmd line> python3 scratch.py 115200 '/dev/ttyABC123'
output> "Your input values were used and are: 115200 and '/dev/ttyAMA0'"
so a command line that will take [or NOT] argument/s. store the argument to a variable in the code for future use.
This some of what I have tried: Yes I accept it's a mess
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
# ========================
# Include standard modules
# import click
# baud default = 9600
# port default = "/dev/ttyAMA0"
import click
#click.command()
# #click.option('--baud', required = False, default = 9600, help = 'baud rate defaults to: 9600')
# #click.option('--port', required = False, default = '/dev/ttyAMA0', help = 'the port to use defaults to: /dev/ttyAMA0')
#click.option('--item', type=(str, int))
def putitem(item):
click.echo('name=%s id=%d' % item)
def communications():
""" This checks the baud rate and port to use
either the command line supplied item or items.
Or uses the default values
abaud = 9600 # default baud rate
b=abaud
aport = "/dev/ttyAMA0"
p=aport
print(f"abaud = {b} and aport = {p}")
"""
# now I wish to check if there were supplied values
# on the command line
# print(f"Baud supplied {click.option.} port supplied {port}" )
if __name__ == '__main__':
putitem() # communications()

The code I have used to work this all out is below, I hope it helps somebody. Any better ways or mistakes please advise.
#!/usr/bin/env python3
# -*- coding: utf_8 -*-
import click
from typing import Tuple
# Command Line test string: python scratch_2.py -u bbc.co.uk aaa 9600 bbb ccc
myuri = "" # This is a placeholder for a GLOBAL variable -- take care!
list1 = [] # This is a placeholder for a GLOBAL variable -- take care!
#click.command(name="myLauncher", context_settings={"ignore_unknown_options": True})
#click.option('--uri', '-u', type=click.STRING, default=False, help ="URI for the server")
#click.argument('unprocessed_args', nargs = -1, type = click.UNPROCESSED)
def main(uri: str, unprocessed_args: Tuple[str, ...]) -> None:
# ==================== Checking the command line structure and obtaining variables
global myuri # define the use of a GLOBAL variable in this function
temp = list((str(j) for i in {unprocessed_args: Tuple} for j in i)) # sort out the command line arguments
res = len(temp)
# printing result
print("")
for e in range(0 ,res): # list each of the command line elements not including any uri
print("First check: An input line Tuple element number: " + str(e) +": " + str(temp[e])) # elements base 0
# ==================== deal with any URI supplied -- or NOT
if uri is False: #if --uri or -u is not supplied
print("No uri supplied\n")
print("The input line tuple list elements count: " + str(res))
# set a defaul GLOBAL value of myuri if it is not supplied
myuri = "https://192.168.0.90:6691/" #
else:
print("\nThe input line tuple list elements count : " + str(res) + " and we got a uri")
myuri = uri # set the GLOBAL value of myuri if the uri is
print(f"which is: {uri}, and therefore myuri also is: {myuri}") # a temp print to prove the values of the GLOBAL variable 'myuri'
# ==============================================================================================
# Testing choice of baud rate on command line
db_list = {
'4800': 'TEST48',
'9600': 'TEST96',
'19200': 'TEST19',
'38400': 'TEST38',
'57600': 'TEST57',
'115200': 'TEST11',
}
# Print databases ----- db_list ----- listed in dictionary
print("\nDatabases:")
for e in range(0 ,res) :
""" list each of the command line elements not including any uri
print("Second Check: An input line Tuple element number: " + str(e) +": " + str(temp[e]))
elements base 0 """
if str(temp[e]) in db_list.keys() :
print(f"The index of db contains {str(temp[e])}, The index refers to: {db_list[str(temp[e])]}")
if __name__ == "__main__":
# pylint: disable=no-value-for-parameter, unexpected-keyword-arg
main()

Related

How to save data using openpyxl with Python3 in a Excel file? Is there any way to take a bit of time to do that?

The procedure is :
Reading data from a txt file by line;
Then send the data to a serial port;
Save the data to a Excel file in a row with number/date/time
I have tried two methods, and both of them are worked.
But the is some problem:
The more data, the more time is taken when save the new data to the Excel file. For example, when write the data to the 10000 row, it's almost need 2 seconds (When I using append() to append a new row in the Excel file);
I want to send and save the data all the time, untill I stop the script in the Pycharm by click the STOP button. Once I did that, the Excel saved before is broken and CAN NOT open it again.
I want to solve these problem. Can anyone have any suggestions? Thank you so much!
Here is the code:
import serial
import serial.tools.list_ports
import time
import datetime
import sys
import os
import openpyxl
from openpyxl.styles import Font
from openpyxl.styles import PatternFill
from openpyxl.styles import Alignment
# The path to palce the Excel file
def creatfilepath(path):
# path -- The user input a path
if os.path.isdir(path):
datapath = path
print('\nM1:The path is OK, and the Excel file will be saved in {}'.format(datapath))
else:
if os.makedirs(path):
datapath = path
print('\nM2:The path does not exist, and scrip will creat the path {} to save the Excel'.format(datapath))
else:
datapath = os.getcwd()
print('\nM3:Creating the path is failed, and the Excel will be saved in same path {} where the script is.'.format(datapath))
return datapath
# Creating the Excel to save the data
def creatdatafile(filename, path, data_title):
# filename -- Excel filename
# path -- The path to save the Excel
# data_title -- Excel's title, which is a list and the default value is ['Number', 'Date', 'Time', 'Command', 'Type', 'Comment']
# Creating a Workbook
serial_data_1902_wb = openpyxl.Workbook()
# Accessing the active sheet
serial_data_1902_sheet = serial_data_1902_wb.active
# sheet'title
serial_data_1902_sheetname = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
serial_data_1902_sheet.title = serial_data_1902_sheetname
# Excel's filename
serial_data_1902_filename = filename + '_' + \
datetime.datetime.now().strftime('%Y%m%d_%H%M%S') + '.xlsx'
# The absolute path
ab_path = path + "\\" + serial_data_1902_filename
serial_data_1902_wb.save(ab_path)
# Loading the Excel
active_data_file = openpyxl.load_workbook(ab_path)
# Accessing the sheet
active_datasheet = active_data_file[serial_data_1902_sheetname]
# Setting the title
row_title = data_title
active_datasheet.append(row_title)
# Freezing the 1st row
active_datasheet.freeze_panes = 'A2'
# Accessing the title's scope
title_scope = active_datasheet.dimensions
# Arial/12/Bold/
title_font_obj = Font(name='Arial', size=10, bold=True,
italic=False, strike=False, color='000000')
# Fill type
title_fill_obj = PatternFill(fill_type='solid', fgColor='7EC0EE')
# Alignment
title_align_obj = Alignment(
horizontal='center', vertical='center', wrap_text=False)
# Formating the 1st row
for rowOfCellObjects in active_datasheet[title_scope]:
for cellObject in rowOfCellObjects:
cellObject.font = title_font_obj
cellObject.fill = title_fill_obj
cellObject.alignment = title_align_obj
active_data_file.save(ab_path)
# Return the absolute path
if os.path.exists(ab_path):
print('\nM4:The Excel {} have created, and the path is {}'.format(
serial_data_1902_filename, path))
return ab_path
else:
print('\nE0:Creating Excel is failed, please check!')
sys.exit(0)
def isavailable(port):
port_list = list(serial.tools.list_ports.comports()
)
if len(port_list) <= 0:
print('\nE1:There is not serial port on the host!')
sys.exit(0)
else:
port_name_list = []
for port_objet in port_list:
port_name = port_objet[0]
port_name_list.append(port_name.lower())
print('\nM5:The serial ports is:{}'.format(port_name_list))
if port.lower() in port_name_list:
print('\nM6:')
else:
print('\nE2:')
sys.exit(0)
# Creating a vitural serial port and open it
def createport(port, baudrate=115200):
isavailable(port)
try:
serialport = serial.Serial(port, baudrate, timeout=1, write_timeout=1)
except ValueError:
sys.exit('\nE3:')
except (OSError, serial.SerialException):
sys.exit('\nE4:')
if serialport.is_open and port == serialport.portstr:
print('\nM7:')
serialport.flushInput()
else:
sys.exit('\nE5:')
return serialport
def savecommand(num, command_list, excelabpath, excelworkbook, excelsheet):
# num -- The number of command
# command_list -- A list which contain the data
# excelabpath -- The absolute path of Excel
# excelworkbook -- The Excel which save data
# excelsheet -- the sheet which save the data
try:
i = 0
for row in excelsheet.iter_rows(min_row=num+1, max_col=5, max_row=num+1):
for cell in row:
cell.value = command_list[i]
i += 1
if i < len(command_list):
continue
else:
break
excelworkbook.save(excelabpath)
except TypeError:
print('\nE10:')
def sendcommand(abpath, workbook, sheet, commandfile, port, intertime=0.5, commamd_num=1):
#
if not port.is_open:
try:
port.open()
except:
print('\nE6:')
else:
pass
try:
with open(commandfile, 'r', encoding='utf-8') as file_object:
for line in file_object:
if len(line) > 1 and (not line.startswith('#')):
command_hex = bytes.fromhex(
line.strip())
try:
print(commamd_num, " ", datetime.datetime.now(),
" ", line)
port.write(command_hex)
# The list contain the number/date/time/command
data_list = [commamd_num, datetime.datetime.now().strftime(
'%Y/%m/%d'), datetime.datetime.now().strftime('%H:%M:%S.%f'), line.strip()]
savecommand(commamd_num, data_list, abpath, workbook, sheet)
time.sleep(intertime)
commamd_num += 1
except serial.SerialTimeoutException:
print("\nE7:")
time.sleep(1)
port.write(command_hex)
# The list contain the number/date/time/command
data_list = [commamd_num, datetime.datetime.now().strftime(
'%Y/%m/%d'), datetime.datetime.now().strftime('%H:%M:%S.%f'), line.strip()]
savecommand (commamd_num, data_list, abpath, workbook, sheet)
time.sleep(intertime)
commamd_num += 1
else:
continue
except FileNotFoundError:
print('\nE8:')
sys.exit(0)
except PermissionError:
print('\nE9:')
sys.exit(0)
port.close()
if not port.is_open:
print('\nM9:')
return commamd_num
# ~~~~~~~~~~~~~~ The parameters of serial port ~~~~~~~~~~~~~~~~~~
# port number
comNumber = 2
# port name
comPort = 'com{}'.format(comNumber)
# baudRate
baudRate = 115200
# ~~~~~~~~~~~~~~ Other parameters ~~~~~~~~~~~~~~~~~~
# The path of command file
commandPath = r'.\1902.txt'
# The Excel path
savePath = r'D:\1902Code'
# The name of Excel
excelName = 'SerialData1902'
# The title
excelTitle = ['Number', 'Date', 'Time', 'Command', 'Type', 'Comment']
# The time between two command was sent
interTime = 0.01
#
isForever = 1
# ~~~~~~~~~~~~~~~~~~~~~ Begin ~~~~~~~~~~~~~~~~~~~
# Creating the path which save the Excel
excel_path = creatfilepath(savePath)
# Creating the Excel
excel_ab_path = creatdatafile(excelName, excel_path, excelTitle)
# Loading the Excel
excel_workbook = openpyxl.load_workbook(excel_ab_path)
# Accessing the sheet
excel_sheet = excel_workbook.active
# Creating the serial port
serial_port = createport(comPort, baudRate)
# The number of sending command and start with 1
command_num = 1
if isForever:
print('\nM10:')
while isForever:
command_num = sendcommand(excel_ab_path, excel_workbook, excel_sheet,
commandPath, serial_port, interTime, command_num)
elif isForever == 0:
print('\nM11:')
command_num = sendcommand(excel_ab_path, excel_workbook, excel_sheet,
commandPath, serial_port, interTime, command_num)
else:
print('\nE11:')

Restrict input string length in argparse add_argument

I want to add an argument of type string but restrict the length of the string to 1.
group.add_argument(
"--test_param",
dest="test_param",
type=str,
required=True,
default=None,
)
Is there any way I can do it here without having to raise an error by checking len(test_param) later?
You can try using a lambda function in type
Ex:
import argparse
group = argparse.ArgumentParser(description='Data.')
group.add_argument(
"--test_param",
dest="test_param",
type=lambda x: x if x.isalpha() and len(x) <=1 else False, #Sample Function
required=True,
default=None,
)
args = group.parse_args()
value = getattr(args, 'test_param')
if value:
print(value)
else:
print("error!!!")
Output:
python sample.py --test_param fddd #->>error!!
python sample.py --test_param f #->>f
You can do this with the type parameter of add_argument. The recommended way is to raise an exception during the type check:
import argparse
import sys
parser = argparse.ArgumentParser()
def len_gt_0(s):
if len(s) > 0:
return s
raise argparse.ArgumentTypeError("Value must have length greater than 0")
parser.add_argument("--foo", type=len_gt_0, required=True)
opts = parser.parse_args(sys.argv[1:])
print(f"{ opts.foo = }")
Output:
$ python3.8 /tmp/foo.py --foo=abc
opts.foo = 'abc'
$ python3.8 /tmp/foo.py --foo=
usage: foo.py [-h] --foo FOO
foo.py: error: argument --foo: Value must have length greater than 0
$ python3.8 /tmp/foo.py
usage: foo.py [-h] --foo FOO
foo.py: error: the following arguments are required: --foo
You can use the choices argument available while adding a argument for example in my code i just want to select from 1 or 2. I used the following snippet as follows:
parser.add_argument(
"report",
type=int,
metavar="[report #]",
help="1. for Annual Max/Min Temperature 2. for Hottest day of each year",
choices=[1, 2]
)
it will restrict the input from these choices only and will show the error result if you have given out of these choices like this:
usage: [report#] [data_dir]
[report #]
1 for Annual Max/Min Temperature
2 for Hottest day of each year
[data_dir]
Directory containing weather data files
main.py: error: argument [report #]: invalid choice: 3 (choose from 1, 2)
If you don't want to add the choices you can use lamda or a function for validating the data. You can get help from my example below:
parser.add_argument(
"data_dir",
type=check_directory_availability,
metavar="[data_directory]",
help="Directory containing weather data files",
)
def check_directory_availability(dir_name):
if not os.path.isdir(dir_name):
raise NotADirectoryError("Provided directory name doesn't exists")
return dir_name
Hope so it may help you.

Save CSV for every functioncall with another name

at the moment I am able to create one CSV file with all the content I get at once.
Now I would like to create a list where I have different names in it.
How can I produce for every functioncall a different CSV file name? I thought about looping a list but I just want a +1 iteration at each call. I thought about saving my state somehow and use it in next functioncall. Everytime I initialize my variable with 0 and so I don't get 1. I think I could do it with Python Function Parameter calls but I have no idea how to use it. Can someone give me a little tip or example? If there are better ideas (maybe my idea is totally bullshit), how to solve this, just help please.
The comments in the code shall represent my imagination.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tenable.sc import SecurityCenter as SC
import os.path
import sys
import getpass
import csv
SC_HOST = '...'
def parse_entry(entry):
split_after_path = ''
ip = entry.get('ip', None)
pluginText = entry.get('pluginText', None)
if 'Path : ' in pluginText:
for line in pluginText.splitlines(0):
if 'Path : ' in line:
split_after_path_in_plugintext = line.split("Path : ",1)[1]
# place = ['place1', 'place2', 'place3', 'place4', 'place5']
# i = 0
# i = i+1
file_exists = os.path.isfile('testfile_path.csv')
# file_exists = os.path.isfile('testfile_path_'+place[i]+'.csv')
data = open('testfile_path.csv', 'a')
# data = open('testfile_path_'+place[i]+'.csv', 'a')
with data as csvfile:
header = ['IP Address', 'Path']
writer = csv.DictWriter(csvfile, lineterminator='\n', quoting=csv.QUOTE_NONNUMERIC, fieldnames=header)
if not file_exists:
writer.writeheader()
writer.writerow({'IP Address': ip, 'Path': split_after_path})
data.close()
def main():
sc_user = input('[<<] username: ')
sc_pass = getpass.getpass('[<<] password: ')
sc = SC(SC_HOST)
sc.login(sc_user, sc_pass)
# Query API for data
# asset = [12,13,14,25,29]
# i = 0
# assetid = asset[i]
# vuln = sc.analysis.vulns(('pluginID', '=', '25072')('asset','=','assetid'))
# i = i+1
vuln = sc.analysis.vulns(('pluginID', '=', '25072'),('asset','=','11'))
for entry in vuln:
parse_entry(entry)
sc.logout()
return 0
if __name__ == '__main__':
sys.exit(main())
The simplest and most obvious solution is to pass the full file path to your parse_entry function, ie:
def parse_entry(entry, filepath):
# ...
if 'Path : ' in pluginText:
for line in pluginText.splitlines(0):
if 'Path : ' in line:
# ...
file_exists = os.path.isfile(filepath)
with open(filepath, 'a') as csvfile:
# ...
Then in main() use enumerate() to build sequential filenames:
def main():
# ...
for i, entry in enumerate(vuln):
path = "'testfile_path{}.csv".format(i)
parse_entry(entry, path)
You can use a function attribute to keep track of the number of times the function has been called.
def parse_entry(entry):
parse_entry.i += 1
# outside the function you have to initialize the attribute
parse_entry.i = 0
Or you can look at other ways to initialize the function attribute in this post.
Alternatively, you can use glob to get the current number of files.
from glob import glob
i = len(glob('testfile_path_*.csv'))

Never resets list

I am trying to create a calorie counter the standard input goes like this:
python3 calories.txt < test.txt
Inside calories the food is the following format: apples 500
The problem I am having is that whenever I calculate the values for the person it seems to never return to an empty list..
import sys
food = {}
eaten = {}
finished = {}
total = 0
#mappings
def calories(x):
with open(x,"r") as file:
for line in file:
lines = line.strip().split()
key = " ".join(lines[0:-1])
value = lines[-1]
food[key] = value
def calculate(x):
a = []
for keys,values in x.items():
for c in values:
try:
a.append(int(food[c]))
except:
a.append(100)
print("before",a)
a = []
total = sum(a) # Problem here
print("after",a)
print(total)
def main():
calories(sys.argv[1])
for line in sys.stdin:
lines = line.strip().split(',')
for c in lines:
values = lines[0]
keys = lines[1:]
eaten[values] = keys
calculate(eaten)
if __name__ == '__main__':
main()
Edit - forgot to include what test.txt would look like:
joe,almonds,almonds,blue cheese,cabbage,mayonnaise,cherry pie,cola
mary,apple pie,avocado,broccoli,butter,danish pastry,lettuce,apple
sandy,zuchini,yogurt,veal,tuna,taco,pumpkin pie,macadamia nuts,brazil nuts
trudy,waffles,waffles,waffles,chicken noodle soup,chocolate chip cookie
How to make it easier on yourself:
When reading the calories-data, convert the calories to int() asap, no need to do it every time you want to sum up somthing that way.
Dictionary has a .get(key, defaultvalue) accessor, so if food not found, use 100 as default is a 1-liner w/o try: ... except:
This works for me, not using sys.stdin but supplying the second file as file as well instead of piping it into the program using <.
I modified some parsings to remove whitespaces and return a [(name,cal),...] tuplelist from calc.
May it help you to fix it to your liking:
def calories(x):
with open(x,"r") as file:
for line in file:
lines = line.strip().split()
key = " ".join(lines[0:-1])
value = lines[-1].strip() # ensure no whitespaces in
food[key] = int(value)
def getCal(foodlist, defValueUnknown = 100):
"""Get sum / total calories of a list of ingredients, unknown cost 100."""
return sum( food.get(x,defValueUnknown ) for x in foodlist) # calculate it, if unknown assume 100
def calculate(x):
a = []
for name,foods in x.items():
a.append((name, getCal(foods))) # append as tuple to list for all names/foods eaten
return a
def main():
calories(sys.argv[1])
with open(sys.argv[2]) as f: # parse as file, not piped in via sys.stdin
for line in f:
lines = line.strip().split(',')
for c in lines:
values = lines[0].strip()
keys = [x.strip() for x in lines[1:]] # ensure no whitespaces in
eaten[values] = keys
calced = calculate(eaten) # calculate after all are read into the dict
print (calced)
Output:
[('joe', 1400), ('mary', 1400), ('sandy', 1600), ('trudy', 1000)]
Using sys.stdin and piping just lead to my console blinking and waiting for manual input - maybe VS related...

Simple /etc/shadow Cracker

I'm trying to get this shadow file cracker working but I keep getting a TypeError: integer required.
I'm sure its the way I'm using the bytearray function. I've tried creating a new object with bytearray for the "word" and the "salt" however to no avail. So then I tried passing the bytearray constructor to the pbkdf2 function and still nothing. I will post the code:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import hashlib, binascii
import os,sys
import crypt
import codecs
from datetime import datetime,timedelta
import argparse
today = datetime.today()
# Takes in user and the encrypted passwords and does a simple
# Brute Force Attack useing the '==' operator. SHA* is defined by
# a number b/w $, the char's b/w the next $ marker would be the
# rounds, then the salt, and after that the hashed password.
# object.split("some symbol or char")[#], where # is the
# location/index within the list
def testPass(cryptPass,user):
digest = hashlib.sha512
dicFile = open ('Dictionary.txt','r')
ctype = cryptPass.split("$")[1]
if ctype == '6':
print "[+] Hash type SHA-512 detected ..."
print "[+] Be patien ..."
rounds = cryptPass.split("$")[2].strip('rounds=')
salt = cryptPass.split("$")[3]
print "[DEBUG]: " + rounds
print "[DEBUG]: " + salt
# insalt = "$" + ctype + "$" + salt + "$" << COMMENTED THIS OUT
for word in dicFile.readlines():
word = word.strip('\n')
print "[DEBUG]: " + word
cryptWord = hashlib.pbkdf2_hmac(digest().name,bytearray(word, 'utf-8'),bytearray(salt, 'utf-8'), rounds)
if (cryptWord == cryptPass):
time = time = str(datetime.today() - today)
print "[+] Found password for the user: " + user + " ====> " + word + " Time: "+time+"\n"
return
else:
print "Nothing found, bye!!"
exit
# argparse is used in main to parse arguments pass by the user.
# Path to shadow file is required as a argument.
def main():
parse = argparse.ArgumentParser(description='A simple brute force /etc/shadow .')
parse.add_argument('-f', action='store', dest='path', help='Path to shadow file, example: \'/etc/shadow\'')
argus=parse.parse_args()
if argus.path == None:
parse.print_help()
exit
else:
passFile = open (argus.path,'r', 1) # ADDING A 1 INDICATES A BUFFER OF A
for line in passFile.readlines(): # SINGLE LINE '1<=INDICATES
line = line.replace("\n","").split(":") # EXACT BUFFER SIZE
if not line[1] in [ 'x', '*','!' ]:
user = line[0]
cryptPass = line[1]
testPass(cryptPass,user)
if __name__=="__main__":
main()
OUTPUT:
[+] Hash type SHA-512 detected ...
[+] Be patien ...
[DEBUG]: 65536
[DEBUG]: A9UiC2ng
[DEBUG]: hellocat
Traceback (most recent call last):
File "ShadowFileCracker.py", line 63, in <module>
main()
File "ShadowFileCracker.py", line 60, in main
testPass(cryptPass,user)
File "ShadowFileCracker.py", line 34, in testPass
cryptWord = hashlib.pbkdf2_hmac(digest().name,bytearray(word, 'utf-8'),bytearray(salt, 'utf-8'), rounds)
TypeError: an integer is required
The rounds variable needs to be an integer, not a string. The correct line should be:
rounds = int(cryptPass.split("$")[2].strip('rounds='))
Also, strip() might not be the best method for removing the leading "rounds=". It will work, but it strips a set of characters and not a string. A slightly better method would be:
rounds = int(cryptPass.split("$")[2].split("=")[1])

Resources