python3+tkinter实践历程(一)——基于requests与tkinter的API工具
系列文章目录
python3+tkinter实践历程(一)——基于requests与tkinter的API工具
python3+tkinter实践历程(二)——基于tkinter的日志检索工具
python3+tkinter实践历程(三)——基于requests与tkinter的模拟web登录工具
python3+tkinter实践历程(四)——模仿CRT完成基于socket通信与tkinter的TCP串口客户端
分享背景
①分享意图在于帮助新入门的朋友,提供思路,里面详细的注释多多少少能解决一些问题。欢迎大佬指点跟交流。
②2021年8月,开始陆续有需求制作一些工具,因为python语言跟tkinter工具相对简单,所以就基于这些做了好几个不同用处的工具。
③分享从完成的第一个工具开始分享,分享到最新完成的工具,对于tkinter的理解也从一开始的摸索入门,到后来逐渐熟练,完成速度也越来越快,所用到的tk的功能点也越来越多。
制作背景
实现让不会代码的人也能使用该图形化工具对API进行测试,或者调用API
最终功能
① 将加密过程、登录流程、各个API的body、headers、url全部封装好
② 使用者根据自己的需求修改工具给出的body即可
代码详解
代码中关于产品登录流程及加密方式不展示
# -*- coding=utf-8 -*-
"""API工具"""
import tkinter as tk
from tkinter import *
import json
import requests
import base64
import datetime
import hashlib
import tkinter.font as tf
# HTTP类,关于HTTP的操作均在此类中封装实现
class Http(object):
def __init__(self, url='', username='', password='', api_name=''):
"""初始化类时传入url、账号、密码、API名称"""
# 以下将一系列参数变成整个类中都可调用的属性
self.check_url = url # ip:端口
self.check_username = username # 账号
self.check_pwd = password # 密码
self.check_api_name = api_name # API名称
self.url = 'http://' + url + api_name # 整个url:http://xx.xx.xx.xx:80/login
self.encrypted_value = xx # 此为某加密的结果
# body字典
self.xx_body = """
<pageIndex>1</pageIndex>
<pageSize>20</pageSize>
<condition>
<logType type="list">
<itemType type="logType"/>
<item>
<![CDATA[LOG_ALL]]>
</item>
</logType>
<startTime>
<![CDATA[%s]]>
</startTime>
<endTime>
<![CDATA[%s]]>
</endTime>
<langId>
<![CDATA[0x0804]]>
</langId>
</condition>
"""
def send_request(self, re_type='post', body='', authorization=''):
# 发送接口请求的函数
if not self.check_url or not self.check_username or not self.check_pwd or not self.check_api_name or not body or not authorization:
# 检查所需数据是否已传入,未传入则返回
print('请填写相关信息')
return
# basic 的鉴权值是“账号:密码” 的Base64加密值
if authorization == 'basic':
self.headers = {xx }
elif authorization == 'digest':
self.headers = {xx}
else:
return
if re_type == 'get':
return
if re_type == 'post':
print('给%s发送%s请求,body:%s' % (self.url, re_type, body))
try:
# 进行请求
respone = requests.post(self.url, headers=self.headers, data=body)
print(respone)
if not respone.content:
print('无data信息')
# 没有返回信息就返回状态码
return respone.status_code
else:
# 返回以utf-8解码的报文信息
print(respone.content.decode('utf-8'))
return respone.content.decode('utf-8')
except Exception as e:
print('请求异常:%s' % e)
return False
def get_api_info(self, api_name):
"""获取API的默认参数、及默认的具体body"""
# 有一些API的url会有后缀参数,比如http://xx.xx.xx.xx:80/login/1、http://xx.xx.xx.xx:80/login/up、http://xx.xx.xx.xx:80/login/xx
# 有些API不需要body,有些需要body的需要用户自己去填写所需内容,所以body和url都需要展示在界面上供使用者更改
# 这里是对某个API的body的预处理,自动生成两个常用时间然后放进body中,再输出给使用者
xx_start_time = str(datetime.date.today())
xx_end_time = xx_start_time + ' 23:59:59'
xx_start_time = xx_start_time + ' 00:00:00'
# url的对应字典,每个API的对应的具体url内容从这里取
url_param = {
"xx": "/xx",
}
# body的对应字典,每个API的对应的具体body内容从这里取
body = {
"xx": self.xx_bdoy% (xx_start_time, xx_end_time),
}
# 如果url字典和body字典未加入该API,则返回
if api_name not in url_param or api_name not in body:
print('api:%s,无url_parm或者body,请添加' % api_name)
return {}
api_info = {'url_param': url_param[api_name], 'body': body[api_name]}
print('api:%s的信息为:' % api_name + json.dumps(api_info, ensure_ascii=False))
# 取得了该API的url和body后,制作成api_info字典,返回给上层
return api_info
def get_digest_respone(self):
"""用于计算加密后的respone的值"""
# 加密算法省略,加密结果返回上层
return all_s
# 核心类,整个工具的核心
class Application(tk.Frame):
def __init__(self, master=None):
# 继承于tkinter的Frame类,可使用Frame的布局方式
tk.Frame.__init__(self, master)
self.grid()
# API列表(一个元组),用于显示在界面。与下面的self.api_name_lb这个tk的Listbox列表框配合使用
self.api_list = ('GetxxxxInfo',)
# 初始化时执行下面create_widgets函数构造出GUI界面
self.create_widgets()
def create_widgets(self):
"""创建界面"""
# 设备IP端口
# 创建一个Label控件,指定text
dev_url = tk.Label(self, text="设备IP及端口(xx:xx)")
# 左右间距为1,row/column 默认初始值是0,即第1行第1列
dev_url.grid(padx=1, row=0, column=0)
# 创建一个tk下的字符串类型的容器,容器用set方法赋值,用get方法获取值
dev_url = tk.StringVar()
dev_url.set("10.100.10.108:80") # set的作用的预置的文字(在标签内可变的文本)
# 创建一个Entry框,即小型输入框,用textvariable绑定dev_url容器,容器类的值即Entry的默认显示值,在界面上可以随意更改
self.dev_url = tk.Entry(self, textvariable=dev_url, width=20)
self.dev_url.grid(padx=1, row=0, column=1)
# 账号密码的创建方法与设备IP端口同理
# 账号
user_name = tk.Label(self, text="账号")
user_name.grid(padx=1, row=0, column=2)
user_name = tk.StringVar()
user_name.set("")
self.user_name = tk.Entry(self, textvariable=user_name, width=20)
self.user_name.grid(padx=1, row=0, column=3)
# 密码
password = tk.Label(self, text="密码")
password.grid(padx=1, row=0, column=4)
password = tk.StringVar()
password.set("")
self.password = tk.Entry(self, textvariable=password, width=20)
self.password.grid(padx=1, row=0, column=5)
# 两种认证方式的单选框
# 创建一个tk下的int类型的容器,容器用set方法赋值,用get方法获取值
self.authentication = tk.IntVar()
self.authentication.set(1)
# 创建两个Radiobutton控件,variable与self.authentication容器绑定,默认是为1,两个按钮互相冲突,必有一个被选择。选择按钮2时,self.authentication的值就会改变成按钮2的value,即2
self.select_basic = Radiobutton(self, variable=self.authentication, text='basic', value=1)
self.select_basic.grid(padx=1, row=0, column=6)
self.select_digest = Radiobutton(self, variable=self.authentication, text='digest', value=2)
self.select_digest.grid(padx=1, row=0, column=7)
# 接口列表
self.log_label = tk.Label(self, text="接口列表")
self.log_label.grid(padx=1, row=1, column=0)
# 创建字符串类型的容器
self.api_name = tk.StringVar()
self.api_name.set(self.api_list)
# 创建Listbox控件,用于显示一组文本选项,与self.api_name容器绑定,后续调用curselection()就知道容器元组中哪个下标的文本
self.api_name_lb = tk.Listbox(self, listvariable=self.api_name, height=32)
self.api_name_lb.grid(padx=1, row=2, column=0)
# 获取接口请求体按钮
# 创建Button控件,绑定show_api_body函数,点击按钮则执行此函数
self.get_body_btn = tk.Button(self, text='获取接口请求体', command=self.show_api_body)
self.get_body_btn.grid(padx=1, row=1, column=1)
# 发送接口按钮
# button控件,同理
self.get_body_btn = tk.Button(self, text='发送接口', command=self.send_api_request)
self.get_body_btn.grid(padx=1, row=1, column=5)
# 接口url具体参数输入框
api_param = tk.Label(self, text="接口及参数输入")
api_param.grid(padx=1, row=1, column=2)
api_param = tk.StringVar()
api_param.set("")
# Entry控件,预置为空,选择接口后会把url输出到界面
self.api_param = tk.Entry(self, textvariable=api_param, width=20)
self.api_param.grid(padx=1, row=1, column=3)
# body框 + 滚动条
# Font 调整Text框内的字体大小
ft = tf.Font(size=9)
# Text框-用于打印API的body
self.api_body = tk.Text(self, width=57, height=47, font=ft)
# Scrollbar控件,滚动条
body_slide_bar = tk.Scrollbar(self, command=self.api_body.yview, orient="vertical")
body_slide_bar.grid(padx=1, row=2, column=4, sticky='ns')
self.api_body.grid(padx=1, row=2, column=1, columnspan=4)
# 将body框与滚动条绑定
self.api_body.config(font=ft, yscrollcommand=body_slide_bar.set)
# respone框 + 滚动条
self.api_respone = tk.Text(self, width=80, height=45)
respone_slide_bar = tk.Scrollbar(self, command=self.api_respone.yview, orient="vertical")
respone_slide_bar.grid(padx=1, row=2, column=11, sticky='ns')
self.api_respone.grid(padx=1, row=2, column=5, columnspan=6)
self.api_respone.config(yscrollcommand=respone_slide_bar.set)
# 使用说明
row = Frame(self)
row.grid(padx=1, row=3, column=0, columnspan=100, sticky='w')
instructions = """
使用说明:输入设备IP及端口,用IP:端口的形式,输入正确的账号密码,选择测试的认证方式basic/digest,
然后于接口列表选择要测试的接口,选择后点击"获取接口请求体"按钮,"接口及参数输入"框中会出现接口的url,
正中间的框会出现接口的请求体(body),两个框的内容均修改为所需内容后,点击"发送接口"按钮,右方框会出现接口返回的内容。
"""
text_param = tk.Label(row, text=instructions, font=('宋体', 12), justify='left')
text_param.pack(side='left')
def show_api_body(self):
"""点击这个按钮必须检查url、账号、密码是否符合要求,获取接口请求体"""
index = self.api_name_lb.curselection() # 获取该列表选择框的选择项
if not index:
print('未选择')
return
else:
print('序号是:%s' % index[0]) # index[0]为self.api_list这个元组的下标
api_name = self.api_list[int(index[0])] # 通过下标确定选择了哪个API
print('选择的接口是:%s' % api_name)
# 一系列必填项判断操作
if not self.dev_url.get():
print('设备IP端口不能为空')
return
else:
url = self.dev_url.get()
if not self.user_name.get():
print('账号不能为空')
return
else:
username = self.user_name.get()
if not self.password.get():
print('密码不能为空')
return
else:
password = self.password.get()
# 创建HTTP类对象
dev_http = Http(url, username, password, api_name)
# 调用HTTP类中的get_api_info函数,传入API名称,返回API预置的url与body
api_info = dev_http.get_api_info(api_name)
if not api_info:
print('无api的信息,请检查')
return
# 传输url到接口参数框
self.api_param.delete(0, 'end') # Entry框的清除方式
self.api_param.insert(0, api_info['url_param']) # Entry框的添加方式
# 传输body到body框
if self.api_body.get(0.0): # 如果Text框已有值,则先清除
self.api_body.delete(0.0, 'end') # Text框的清除方式
self.api_body.insert('end', api_info['body']) # Text框的添加方式
def send_api_request(self):
"""发送接口请求"""
# 从界面上获取一系列的数据
api_param = self.api_param.get()
api_body = self.api_body.get(0.0, 'end')
url = self.dev_url.get()
username = self.user_name.get()
password = self.password.get()
authentication = {'1': 'basic', '2': 'digest'}.get(str(self.authentication.get()))
print('发送接口请求,传入数据:url:%s,账号:%s,密码:%s,认证方式:%s' % (url, username, password, authentication))
# 创建HTTP类对象,调用send_request函数发送请求
dev_http = Http(url, username, password, api_param)
respone = dev_http.send_request(body=api_body, authorization=authentication)
if respone:
if self.api_respone.get(0.0):
self.api_respone.delete(0.0, 'end')
self.api_respone.insert('end', respone)
else:
print('接口返回数据为空')
if self.api_respone.get(0.0):
self.api_respone.delete(0.0, 'end')
self.api_respone.insert('end', '接口发送异常')
root = tk.Tk() # 创建Tk对象
app = Application(master=root) # 创建Application类的对象app,主界面为root
root.title("第三方API测试工具") # 工具标题
root.resizable(False, False) # 禁止调整窗口大小
sw = root.winfo_screenwidth() # 得到屏幕宽度
sh = root.winfo_screenheight() # 得到屏幕高度
# 窗口宽高为1200/800
ww = 1200
wh = 800
x = (sw-ww) / 2
y = (sh-wh) / 2
root.geometry("%dx%d+%d+%d" % (ww, wh, x, y)) # 设置窗口宽高为1200x800,且自适应居中
root.deiconify() # 显示窗口
app.mainloop() # 进入消息循环
工具截图展示