requests使用
requests的安装
- pip的安装方式 pip install requests
- 克隆公共版本库 git clone git://github.com/kennethreitz/requests.git
- 到下载的指定目录 cd requests
- 再次使用pip下载 pip install .
快速上手
发送请求
>>> r = requests.get('https://api.github.com/events')
>>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
>>> r = requests.put('http://httpbin.org/put', data = {'key':'value'}) >>> r = requests.delete('http://httpbin.org/delete') >>> r = requests.head('http://httpbin.org/get') >>> r = requests.options('http://httpbin.org/get')
传递url参数
经常会为URL的查询字符串传递某种数据,如果是手工构建URL,那么数据会以键值对的形式放在URL中,跟在一个问号的后面,requests允许使用params关键字参数,一个字符串字典来提供这些参数
>>> payload = {'key1': 'value1', 'key2': 'value2'} >>> r = requests.get("http://httpbin.org/get", params=payload)
通过打印输出该URL,可以看到URL已被正确编码
需要注意的是字典里面的值为None的键都不会被添加到URL的查询字符串里,也可以将一个列表作为值传入
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}
>>> r = requests.get('http://httpbin.org/get', params=payload)
>>> print(r.url)
http://httpbin.org/get?key1=value1&key2=value2&key2=value3
响应内容
我们能够服务服务器响应的内容
>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...
request会自动解码来自服务器的内容,大多数unicode字符集都能被无缝解码,请求发出后,Requests会使用其推测的文本编码,可以找出Requests使用了什么编码,并且能够使用r.encoding属性来改变它
>>> r.encoding
'utf-8'
>>> r.encoding = 'ISO-8859-1'
二进制响应内容
可以以字节的方式访问请求响应体,对于非文本请求
>>> r.content
b'[{"repository":{"open_issues":0,"url":"https://github.com/...
Requests会自动解码gzip和deflate传输编码的响应数据,例如,以请求返回的二进制数据创建了一张图片,可以使用如下代码:
>>> from PIL import Image
>>> from io import BytesIO
>>> i = Image.open(BytesIO(r.content))
json响应内容
Requests中有一个内置的json解码器,用于处理json数据
>>> import requests
>>> r = requests.get('https://api.github.com/events')
>>> r.json()
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
如果json解码失败,r.json()会抛出一个异常,例如,响应内容是401,尝试访问r.json将会抛出ValueError:No JSON object could be decoded异常,需要注意的是,成功调用r.json并不意味着响应的成功,有的服务器会在失败的响应中包含一个json对象(比如HTTP 500的错误细节),这种json会被解码返回,要检查请求是否成功,请使用r.raise_for_status()或者检查r.status_code是否和你的期望相同
原始响应内容
在罕见的情况下,可能想获取来自服务器的原始套接字响应,那么可以访问r.raw.如果确实像这么干,确保在初始请求中设置了stream=True,具体可以这么做
>>> r = requests.get('https://api.github.com/events', stream=True)
>>> r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
>>> r.raw.read(10)
'x1fx8bx08x00x00x00x00x00x00x03' # 读取10个
一般情况下,应该以下面的模式将文本流保存到文件
with open(filename, 'wb') as fd:
for chunk in r.iter_content(chunk_size):
fd.write(chunk)
定制请求头
如果想为请求添加HTTP头部,值需要传递一个dict给headers参数就可以了
>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> r = requests.get(url, headers=headers)
注意:定制header的优先级低于某些特定的信息源
- 如果在.netrc中设置了用户认证信息,使用headers=设置的授权就不会生效,而如果设置了auth=参数,.netrc的设置就无效了
- 如果内重定向到别的主机,授权header就会被删掉
- 的代理授权header会被URL中提供的代理身份覆盖掉
- 在我们能判断内容长度的情况下,header的Content-Length会被改写
准确的说,Requests不会基于定制的header的具体情况改变自己的行为,只不过会在最后的请求中,所有的header信息都会被传递进去.所有的header值必须是string、bytestring或者unicode,尽管传递unicode header也是允许的,但是不建议这么做
post请求
通常,想要发送一些编码为表单形式的数据,非常像一个HTML表单,要实现这个,只需要简单传递一个字典给data参数,你的数据字典在发出请求时会自动编码为表单形式:
>>> payload = {'key1': 'value1', 'key2': 'value2'}
>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print(r.text)
{
...
"form": {
"key2": "value2",
"key1": "value1"
},
...
}
还可以为data参数传入一个元祖列表,在表单中多个元素使用同一key的时候,这种方式尤其有效
>>> payload = (('key1', 'value1'), ('key1', 'value2'))
>>> r = requests.post('http://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
"key1": [
"value1",
"value2"
]
},
...
}
很多时候你想要发送的数据并非编码为表单形式的,如果你传递一个string而不是一个dict,那么数据会被直接发布出去(在2.24版后新增的功能)
>>> import json
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, data=json.dumps(payload))
此处除了自行对dict进行编码,还可以使用json参数直接传递,然后就会被自动编码,这是2.24新加功能
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, json=payload)
post多部分编码的文件
requests使得上传多部分编码文件变得简单
>>> url = 'http://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}
可以设置文件名、文件类型和请求头
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}
>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}
响应状态码
我们可以检测响应状态码
>>> r = requests.get('http://httpbin.org/get')
>>> r.status_code
200
为方便引用,requests还附带一个内置的状态码查询对象
>>> r.status_code == requests.codes.ok # 返回一个boolearn值
如果发送了一个错误请求(一个4XX客户端错误或者5XX服务器错误响应),我们可以通过Response.raise_for_status()来抛出异常
>>> bad_r = requests.get('http://httpbin.org/status/404')
>>> bad_r.status_code
404
>>> bad_r.raise_for_status()
Traceback (most recent call last):
File "requests/models.py", line 832, in raise_for_status
raise http_error
requests.exceptions.HTTPError: 404 Client Error
响应头
我们可以查看以一个Python字典形式展示的服务器响应头
>>> r.headers
{
'content-encoding': 'gzip',
'transfer-encoding': 'chunked',
'connection': 'close',
'server': 'nginx/1.0.4',
'x-runtime': '148ms',
'etag': '"e1ca502697e5c9317743dc078f67693f"',
'content-type': 'application/json'
}
上面字典比较特殊,仅为HTTP头部而生的,HTTP头部都是大小写不敏感的,因此可以使用任意大写形式来访问这些响应头字段
>>> r.headers['Content-Type']
'application/json'
>>> r.headers.get('content-type')
'application/json'
Cookie
如果某个响应中包含一些cookie,可以快速访问他们:
>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)
>>> r.cookies['example_cookie_name']
'example_cookie_value'
要想发送cookies到服务器,可以使用cookies参数
>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'
Cookie的返回对象为RequestsCookieJar,它的行为和字典类似,但接口更为完整,适合跨域名跨路径使用,还可以把Cookie Jar传到Requests中
>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'http://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'
重定向与请求历史
默认情况下,除了HEAD,Requests会自动处理所有重定向,可以使用响应对象的history方法来追踪重定向,Response.history是一个Response对象的列表,为了完成请求而创建了这些对象,这个对象列表按照从最老到最近的请求进行排序
将所有HTTP请求重定向到HTTPS
>>> r = requests.get('http://github.com')
>>> r.url
'https://github.com/'
>>> r.status_code
200
>>> r.history
[<Response [301]>]
如果使用的是GET、OPTIONS、POST、PUT、PATCH或者DELETE,那么可以通过allow_redirects参数禁用重定向处理
>>> r = requests.get('http://github.com', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]
如果使用了HEAD,也可以启用重定向
>>> r = requests.head('http://github.com', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[<Response [301]>]
超时
可以告诉requests在经过以timeout参数设定的秒数时间之后停止等待响应,基本上所有的生产代码都应该使用这个参数,如果不适用,程序可能会永远失去响应
>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)
注意的是,timeout仅对连接过程有效,与响应体的下载有关,timeout并不是整个下载响应的时间限制,而是如果服务器在timeout秒内没有应答,将会引发一个异常
错误与异常
- 遇到网络问题(如:DNS查询失败、拒绝连接等)时,Requests会抛出一个ConnectionError异常
- 如果HTTP请求返回了不成功的状态码,Response.raise_for_status()会抛出一个HTTPerror异常
- 若请求超时,则抛出一个Timeout异常
- 若请求超过了设定的最大重定向次数,则会抛出一个TooManyRedirects异常
- 所有Requests显示抛出的异常都继承自requests.exceptions.RequestException
高阶用法
会话对象
会话对象能够跨请求保持某些参数,会在同一个Session实例发出的所有请求之间保持cookie,期间使用urllib3的connection pooling功能.所以如果想同一主机发送多个请求,底层的TCP连接将会被重用,从而带来显著的性能提升
跨请求保持一些cookie
s = requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")
print(r.text)
# '{"cookies": {"sessioncookie": "123456789"}}'
会话也可以用来为请求方法提供缺省数据,这是通过为会话对象的属性提供数据来实现的
s = requests.Session()
s.auth = ('user', 'pass')
s.headers.update({'x-test': 'true'})
# both 'x-test' and 'x-test2' are sent
s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
任何你传递请求方法的字典都会与已设置会话层数据合并,方法层的参数覆盖会话的参数,不过需要注意,就算使用了会话,方法级别的参数也不会被夸请求保持
s = requests.Session()
r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
print(r.text)
# '{"cookies": {"from-my": "browser"}}'
r = s.get('http://httpbin.org/cookies')
print(r.text)
# '{"cookies": {}}'
会话还可以用作前后文管理器,能确保with区块退出后会话能被关闭,即使发生了异常也是一样
with requests.Session() as s:
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
有时候你会想省略字典参数中一些会话层的键,要做到这一点,只需简单的在方法层参数中将那个键的值设置为None,那个键就会被自动省略掉
请求与响应对象
任何时候进行了类似requests.get()的调用,你都在做两件主要的事情
- 在构建一个Request对象,该对象将被发送到某个服务器请求或查询一些资源
- 一旦requests得到一个从服务器返回的相应就会产生一个Response对象,该相应对象包含服务器返回的所有信息,也包含你原来创建的Request对象
如果想访问服务器返回给我们的响应头部信息,可以在调用一下headers方法
>>> r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
>>> r.headers
然而,如果仅仅是想得到发送到服务器的请求头部,我们可以简单的访问该请求
>>> r.request.headers
准备请求
当你从API或者会话调用中收到一个Response对象时,request属性其实是使用了PreparedRequest,有时候在发送请求之前,需要对body或者header做一些额外处理
from requests import Request, Session
s = Session()
req = Request('GET', url,
data=data,
headers=header
)
prepped = req.prepare()
# do something with prepped.body
# do something with prepped.headers
resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
)
print(resp.status_code)
由于没有对Request对象做什么特殊事情,需要准备和修改Preparedequests对象,然后把它和别的参数一起发送到requests.*或者Session.*
然而,上述代码会失去Requests Session对象的一些优势,尤其Session级别的状态,例如:cookie就不会被应用到你的请求上去,要获取一个带有状态的PreparedRequest,用Session.prepare_request()取代Request.prepare()的调用
from requests import Request, Session
s = Session()
req = Request('GET', url,
data=data
headers=headers
)
prepped = s.prepare_request(req)
# do something with prepped.body
# do something with prepped.headers
resp = s.send(prepped,
stream=stream,
verify=verify,
proxies=proxies,
cert=cert,
timeout=timeout
)
print(resp.status_code)
SSL证书验证
Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样.SSL验证默认是开启的,如果证书验证失败,Requests会抛出SSLError
>>> requests.get('https://requestb.in')
requests.exceptions.SSLError: hostname 'requestb.in' doesn't match either of '*.herokuapp.com', 'herokuapp.com'
在该域名上我没有设置SSL,所以失败了,但Github设置了SSL
>>> requests.get('https://github.com', verify=True)
<Response [200]>
也可以为verify传入CA_BUNDLE文件的路径,或者包含可信任CA证书我呢间的文件夹路径
>>> requests.get('https://github.com', verify='/path/to/certfile')
或者将其保持在会话中
s = requests.Session()
s.verify = '/path/to/certfile'
如果verify设为文件夹路径,文件夹必须通过OpenSSL提供的c_rehash工具处理
还可以通过REQUESTS_CA_BUNDLE环境变量定义可信任CA列表
如果将verify设置为False,Requests也能忽略对SSL证书的验证
>>> requests.get('https://kennethreitz.org', verify=False)
<Response [200]>
默认情况下,verify是设置为True的,选项verify仅应用于主机证书
对于私有证书,你也可以传递一个CA_BUNDLE文件的路径给verify,也可以设置REQUEST_CA_BUNDLE环境变量
客户端证书
可以指定一个本地证书用作客户端证书,可以是单个文件(包含秘钥和证书)或一个包含两个文件路径的元祖
>>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key'))
<Response [200]>
或者包含在会话中
s = requests.Session()
s.cert = '/path/client.cert'
requests的简单应用
测试百度接口
import requests
base_url = "https://www.baidu.com"
result = requests.get(base_url + "/get")
print(result.status_code)
requests的参数传递
get请求
一般在GET请求中我们使用查询字符串来进行参数传递,在requests库中使用方法如下:
import requests
base_url = "http://192.168.1.105:8000/v1/cards/"
result = requests.get(base_url +"900000")
print(result.json())
print(result.url)post请求
import requests
base_url = "http://192.168.1.105:8000/v1/cards"
result = requests.post(base_url, data={"cardno": "100106", "type": "1"})
print(result.status_code)
print(result.url)
print(result.json())
requests请求头设置
使用headers对请求头做设置
import requests
base_url = "http://192.168.1.105:8000/v1/cards"
result = requests.post(base_url, data={"cardno": "100106", "type": "1"}, headers={"User-Agent": "Mozilla/8.0"})
print(result.status_code)
print(result.url)
print(result.json())
requests响应内容
# 获取状态码
print(result.status_code)
# 获取地址
print(result.url)
# 获取json格式的响应数据
print(result.json())
# 获取请求头
print(result.headers)
request中cookie设置
设置cookies
import requests
base_url = "http://192.168.1.105:8000/v1/cards"
result = requests.post(base_url, data={"cardno": "100107", "type": "1"}, cookies={"username":"wupeng"})
# 获取状态码
print(result.json())获取cookies
import requests
base_url = "https://www.baidu.com"
result = requests.get(base_url)
# 获取cookies
print(result.cookies)
# 输出cookies
for key, value in result.cookies.items():
print(key + ":" + value)调用了cookies属性成功得到Cookies,可以发现它是一个RequestCookiesJar类型,然后我们用items()方法转化为元祖组成的列表,遍历输出每一个Cookie的名和值,实现Cookies的遍历解析
request中超时设置和文件上传
超时
可以让requests在经过以timeout参数设定的秒数之后停止等待响应,防止某些请求没有响应而一直处于等待状态,如果超时就会抛出异常
import requests
base_url = "https://www.baidu.com"
result = requests.get(base_url,timeout=0.00002)
# 获取cookies
print(result.cookies)
# 输出cookies
for key, value in result.cookies.items():
print(key + ":" + value)文件上传
import requests
base_url = "https://www.baidu.com"
# rb表示二进制读取
result = requests.get(base_url,files={"file":open("wupeng.png","rb")})
print(result.text)
request中会话对象
在计算机中,尤其是在网络应用中,称为会话控制,session对象存储特定用户会话所需的属性及配置信息,这样当用户在应用程序的web页跳转时,存储在session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去.
比如你先进行了登录的操作,然后打开个人中心详情页面,个人中心详情页面如何知道展示的是刚刚登录的这个用户的信息,那么这里就需要使用session来存储相关信息
在接口测试过程中接口之间经常有依赖关系,比如两个请求一个是设置Cookie,灵感衣蛾是获取cookie,在没有session保存机制的情况下,第二个接口无法获取第一个接口设置的cookies值
import requests
base_url = "http://192.168.1.105:8000/v1/cards/"
session=requests.session()
result = session.get(base_url +"/cookies/set/user/51zxw")
print(result.json())
result2 = session.get(base_url +"/cookies/")
print(result2.json())
requests中SSL验证和代理设置
证书验证(12306的证书以前是自己颁发给自己的,现在已经被修复)
import requests
base_url = "https://www.12306.cn"
# 关于证书问题,如果不做证书验证可以关闭
result = requests.get(base_url,verify=False)
print(result.text)代理设置
- 网络代理,一种特殊的网络服务,允许一个网络终端(一般为客户端),通过这个服务与另一个网络终端进行非直接的链接
- 代理服务器位于客户端和访问互联网之间,服务器接收客户端的请求,然后代替客户端向目标网站发出请求,所有的流量均来自代理服务器的ip地址,从而获取到一些不能直接获取的资源
- 对于有些接口,在测试的时候请求几次,能正常获取内容,但是一旦开始大规模频繁请求服务器的时候就会开启验证,甚至会直接将ip封掉,那么防止这种情况发生,需要设置代理来解决这个问题,在Request中需要用到proxies这个参数,在爬虫会常用到代理
# 西刺免费代理(可以使用免费代理ip)
import requests
base_url="https://www.baidu.com"
proxies={"http":"http://14.115.104.133:9797"}
result=requests.get(base_url,proxies=proxies)
print(result.text)
requests身份验证
身份认证
很多借口都需要身份认证,Requests支持多种身份认证
import requests
from requests.auth import HTTPBasicAuth
from requests.auth import HTTPDigestAuth
base_url="http://httpbin.org"
r=requests.get(base_url+"/basic-auth/wupeng/8888",auth=HTTPBasicAuth("wupeng","8888"))
print(r.text)
r1=requests.get(base_url+"/digest-auth/auth/wupeng1/8888",auth=HTTPDigestAuth("wupeng1","8888"))
print(r1.text)
requests流式请求
import json
import requests
base_url = "http://httpbin.org"
r = requests.get(base_url, "/stream/10", stream=True)
if r.encoding is None:
r.encoding = "utf-8"
for line in r.iter_lines(decode_unicode=True):
if line:
data=json.loads(line)
print(data["id"])
停车场案例
接口自动化的框架可以结合功能自动化
可以使用功能自动化的pytest配置文件来操作pytest命令,实现框架执行
可以参考功能自动化中的allure来实现接口自动化的测试报告
可以植入jenkins实现集成持续性测试
Restful接口开发与测试
rest概述
- 简介
- 表现层状态转化,rest是web服务的一种架构风格
- 使用HTTP、URL、XML、JSON、HTML等广泛流行的标准和协议:轻量级,跨平台、跨语言的架构设计
- 是一种设计风格,不是一种标准,是一种思想
- rest原则
- 网络上的所有事物都可以被抽象为资源
- 每一个资源都有唯一的资源标识,对资源的操作不会改变这些标识
- 所有的操作都是无状态的
- 设计思想
- 遵循CRUD原则,对资源只需要四种行为:创建、获取、更新和删除就可以完成相关操作和处理
- 通过统一资源标识符来识别和定位资源,并且针对这些资源而执行的操作是通过HTTP规范定义的,核心的操作只有GET、POST、PUT、DELETE
HTTP方法幂等性与安全性
- GET SELECT 是幂等 是安全的
- POST INSERT 不是幂等 不是安全的
- PUT UPDATE 是幂等 不是安全的
- DELETE DELETE 是幂等的 不是安全的
幂等:对同一个rest接口多次请求,得到的资源状态是相同的
安全:对该rest接口请求,不会使服务器资源状态发生改变.
rest的优势
由于rest强制所有的操作都必须是无状态的,这就没有上下文的限制,如果做分布式,集群在不需要考虑上下文和会话保持的问题,极大的提高系统的可伸缩性
前后端分离,前端拿到的数据值负责展示和渲染,不对数据做任何处理,后端处理数据并以json格式传输出去,定义这样一套统一的接口,在web,ios,android三端都可以用相同的接口
Django安装
- 输入命令 pip install django(需要提前配置好python环境)
- 检验安装 django-admin
Django REST Framework
- 是一套基于Django的REST风格的框架
- 特点
- 功能强大灵活,可以帮助快速开发web api
- 支持认证策略,包括OAuth1和OAuth2
- 自持ORM和非ORM数据源的序列化
- 丰富的文档以及良好的社区支持
- 安装
- pip install djangorestframework
- pip install markdown
- pip install django-filter
创建API
- 当Django REST Framework安装好之后,创建一个新的项目django_restful,如下命令所示创建在D盘根目录,在项目下创建api应用 django-admin startproject django_restful
- 进入项目django_restful创建api应用,创建完成之后可以看到项目文件夹下多了一个api文件夹 cd django_restful并执行命令python manage.py startapp api
- 利用PyCharm打开settings.py添加api和rest_framework
- rest_framework权限配置(默认设置在全局范围内,通过DEFAULT_PERMISSION_CLASSES设置,在该文件末尾添加内容)
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'api'
]REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
数据库迁移
在原来的django_restful文件下输入命令 python manage.py migrate
创建超级管理员 python manage.py createsuperuser
启动Server python manage.py runserver
自定义host和port python manage.py runserver 127.0.0.1:8001
登录超级管理员账户 输入地址:http://127.0.0.1:8000/admin
数据序列化
Serializers用于定位API的表现形式,如返回哪些字段、返回怎样的格式等,这里序列化Django自带的User和Group.创建数据序列化,在api应用下创建serializers.py文件
序列化代码如下:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('url', 'name')
视图
创建视图views.py
视图用于如何向用户展示数据,比如用户查询User信息或查询Group信息,那么程序内部要定义好怎么去查询,再Django REST framework种,ViewSets用于定义视图的展现形式,例如返回哪些内容,需要做哪些权限处理
from django.shortcuts import render
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from api.serializers import UserSerializer, GroupSerializer
# create your views here.
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer在url中会定义相应的规则ViewSets,ViewSets则通过serializer_class找到对应的Serializers
这里将User和Group的所有对象赋予queryset,并返回这些值,再UserSerializer和GroupSerializer中定义要返回的字段
URL路由配置
urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('api-auth', include('rest_framework.urls', namespace='rest_framework')),
]
Swagger接口文档生成
接口开发完之后,接下来需要编写接口文档,传统的接口文档编写都是使用Word或者其他一些接口文档管理平台,这种形式接口文档维护更新比较麻烦,每次接口有变动时得手动修改文档,因此针对这种情况,这里推荐使用Swagger来管理接口文档
Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务,总体目标是使客户端和文件系统作为服务器以同样的速度来更新,每当接口有变动时,对应的接口文档也会自动更新.
为什么受欢迎?
- Swagger可以生成一个具有互动性的API控制台,开发者可以用来快速学习和尝试API
- Swagger可以生成客户端SDK代码用于各种不同的平台上实现
- Swagger文件可以在许多不同的平台上从代码注释中自动生成
- Swagger有一个强大的社区,里面有许多强悍的贡献
Django接入Swagger
安装 pip install django-rest-swagger
进入到settings.py文件,添加django-rest-swagger应用
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'api',
'rest_framework_swagger'
]进入到views.py将之前定义的UserViewSet和GroupViewSet补充注释
"""
retrieve:
Return a user instance.
list:
Return all users,odered by most recent joined.
create:
Create a new user.
delete:
Remove a existing user.
partial_update:
Update one or more fields on a existing user.
update:
Update a user.
""""""
retrieve:
Return a group instance.
list:
Return all groups,odered by most recent joined.
create:
Create a new group.
delete:
Remove a existing group.
partial_update:
Update one or more fields on a existing group.
update:
Update a group.
"""在url.py添加get_schema_view辅助函数(启动失败,使用drf-yasg代替)
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from rest_framework import routers
from api import views
from rest_framework.schemas import get_schema_view
from rest_framework_swagger.renderers import SwaggerUIRenderer, OpenAPIRenderer
schema_view = get_schema_view(title="API", renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer])
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('api-auth', include('rest_framework.urls', namespace='rest_framework')),
path('docs/', schema_view, name='docs')
]
drf-yasg的使用
安装 pip install drf-yasg
settings.py添加路由等内容
... from drf_yasg.views import get_schema_view from drf_yasg import openapi ...
schema_view = get_schema_view(
openapi.Info(
title="Lemon API接口文档平台", # 必传
default_version='v1', # 必传
description="接口文档",
contact=openapi.Contact(email="17327767735@163.com"),
license=openapi.License(name="BSD License"),
),
public=True,
# permission_classes=(permissions.AllowAny,), # 权限类
)urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('api-auth', include('rest_framework.urls', namespace='rest_framework')),
path('redoc', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
]
Restful接口测试-postman
将开发好的接口使用postman进行测试
这种情况出现,是因为没有进行授权,在Authorization选择Basic Auth进行授权
Restful接口测试-python
将开发好的接口使用python与pytest结合进行测试
import unittest
import requests
import pytest
class TestRestFul(unittest.TestCase):
def setUp(self) -> None:
self.base_url = "http://localhost:8000/users"
self.auth = ("wupeng", "bb961202")
# 获取操作与删除操作类似
def test_get_user(self):
r = requests.get(self.base_url + "/1/", auth=self.auth)
result = r.json()
self.assertEqual(result['username'], "wupeng")
# 添加操作与修改操作类似
def test_add_user(self):
r = requests.post(self.base_url + "/", data={'username': 'wupeng1', 'email': '1059457506@qq.com'},
auth=self.auth)
result1 = r.json()
self.assertEqual(result1['username'], "wupeng1")
# 判断是否授权
def test_no_auth(self):
r = requests.get(self.base_url)
result = r.json()
self.assertEqual(result['detail'], "Authentication credentials were not provided.")
if __name__ == '__main__':
pytest.main()
Restful接口测试-MySQL配置
在接口测试过程中,由于有些接口类型并不是安全的,比如DELETE类型,上一次请求之后下一次在请求结果就不一样了,甚至有时接口之间的数据还会相互干扰,导致接口断言失败时不能断定到底是接口程序引起的错误,还是测试数据变化引起的错误,通过测试数据库,每轮测试执勤啊将数据初始化,这样避免数据干扰.
django支持如下几种数据库:
- PostgreSQL
- MySQL
- Oracle
Django迁移MySQL
修改settings.py文件下的DATABASES
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1',
'PORT': '3306',
'NAME': 'django_restful',
'USER': 'root',
'PASSWORD': 'bb961202',
'OPTIONS': {
'isolation_level': None,
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
}
}
}安装MySQLdb驱动
打开django_restful中的__init__.py,添加如下代码:
import pymysql
pymysql.install_as_MySQLdb()
Django数据管理-数据库迁移
创建models
Django提供了完善的模型(model)层来创建和存取数据,包含所储存数据的必要字段和行为.通常,每个模型对应数据库中的唯一的一张表.
打开api中的models.py创建如下代码:
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=100)
email = models.CharField(max_length=100)
groups = models.CharField(max_length=100)
def __str__(self):
return self.username
class Group(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.nameDjango模型字段常用类型
导入Models
创建好Model后需要分别在serializers.py和views.py来导入,同时注释掉django默认的数据库
from api.models import User,Group数据库迁移
- python manage.py makemigrations api
- python manage.py migrate
执行1出错,raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)
解决方案
第一步:找到报错位置G:pythonlibsite-packagesdjangodbackendsmysqlase.py 并打开base.py,注释掉如下两行
version
=
Database.version_info
# if version < (1, 3, 13):
# raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)
第二步:再次执行python manage.py makemigrations 报错(如果不报错忽略)# 报错路径 <br>File "G:pythonlibsite-packagesdjangodbackendsmysqloperations.py", line 146, in last_executed_query
query
=
query.decode(errors
=
'replace'
)<br><br>
# 报错问题
AttributeError:
'str'
object
has no attribute
'decode'
修改:将代码里面query.decode改成:query.encodeif
query
is
not
None
:
query
=
query.encode(errors
=
'replace'
)
# 修改后的
return
query
第三步:报警告Warning: (3090, "Changing sql mode 'NO_AUTO_CREATE_USER' is deprecated. It will be removed in a future release.")注释掉setting.py下的DATABASES的'OPTIONS' 这一步可以忽略数据库迁移之后,在重新创建超级用户 python manage.py createsuperuser
Django数据管理-封装初始化操作
封装初始化操作
数据初始化操作主要包括:数据库连接、数据清除、数据插入、关闭数据库,在api项目虾米那新建一个目录test_project,然后创建文件:mysql_action.py
from pymysql import connect
import yaml
class DB():
def __init__(self):
print('connect db...')
self.conn = connect(host='127.0.0.1', user='root', password='bb961202', db='django_restful')
# 清除表
def clear(self, table_name):
print('clear db...')
clear_sql = 'truncate' + ' ' + table_name + ';'
with self.conn.cursor() as cursor:
cursor.execute('set foreign_key_checks=0;')
cursor.execute(clear_sql)
self.conn.commit()
# 添加表
def insert(self, table_name, table_data):
for key in table_data:
table_data[key] = "'" + str(table_data[key]) + "'"
key = ','.join(table_data.keys())
value = ','.join(table_data.values())
print(key)
print(value)
insert_sql = 'insert into' + ' ' + table_name + '(' + key + ')' + 'values' + '(' + value + ')'
print(insert_sql)
with self.conn.cursor() as cursor:
cursor.execute(insert_sql)
self.conn.commit()
# 关闭数据库
def close(self):
print('close db')
self.conn.close()
if __name__ == '__main__':
db = DB()
# db.clear('api_user')
# user_data = {'id': 1, 'username': 'wulei', 'email': '1059457506@qq.com','groups':'team'}
# db.insert('api_user', user_data)
db.close()
Django数据管理-封装初始化数据
mysql_action.py改写
from pymysql import connect
import json
import yaml
class DB():
def __init__(self):
print('connect db...')
self.conn = connect(host='127.0.0.1', user='root', password='bb961202', db='django_restful')
# 清除表
def clear(self, table_name):
print('clear db...')
clear_sql = 'truncate' + ' ' + table_name + ';'
with self.conn.cursor() as cursor:
self.conn.ping(reconnect=True)
cursor.execute('set foreign_key_checks=0;')
cursor.execute(clear_sql)
self.conn.commit()
# 添加表
def insert(self, table_name, table_data):
for key in table_data:
table_data[key] = "'" + str(table_data[key]) + "'"
key = ','.join(table_data.keys())
value = ','.join(table_data.values())
print(key)
print(value)
insert_sql = 'insert into' + ' ' + table_name + '(' + key + ')' + 'values' + '(' + value + ')'
print(insert_sql)
with self.conn.cursor() as cursor:
self.conn.ping(reconnect=True)
cursor.execute(insert_sql)
self.conn.commit()
# 关闭数据库
def close(self):
print('close db')
self.conn.close()
# 初始化数据
def init_data(self, datas):
for table, data in datas.items():
self.clear(table)
for d in data:
self.insert(table, d)
self.close()
if __name__ == '__main__':
db = DB()
db.close()
file = open("datas.json", "r")
datas = json.load(file)
db.init_data(datas)
# file = open("datas.yaml", "r")
# datas = yaml.load(file, Loader=yaml.FullLoader)
# db.init_data(datas)使用json初始化数据
{
"api_user": [
{
"id": 10,
"username": "kangraobin",
"email": "1059457506@qq.com",
"groups": "http://127.0.0.1:8000/groups/10/"
},
{
"id": 11,
"username": "kangraobin",
"email": "1059457506@qq.com",
"groups": "http://127.0.0.1:8000/groups/10/"
}
],
"api_group": [
{
"id": 11,
"name": "team11"
},
{
"id": 12,
"name": "team12"
}
]
}使用yaml初始化数据
api_user:
- id: 11
username: wupeng1
email: 1059@qq.com
groups: http://127.0.0.1:8000/groups/11/
- id: 12
username: wupeng2
email: 1059@qq.com
groups: http://127.0.0.1:8000/groups/12/
api_group:
- id: 11
name: team11
- id: 12
name: team12
Django数据管理-测试用例封装
test_project文件夹下创建test_django_restful.py文件,用于测试接口
import unittest
import requests
from api.test_project.mysql_action import DB
class TestRestFul(unittest.TestCase):
def setUp(self) -> None:
self.base_url = "http://localhost:8000/users"
self.auth = ("wupeng", "bb961202")
# 获取操作与删除操作类似
def test_get_user(self):
r = requests.get(self.base_url + "/11/", auth=self.auth)
result = r.json()
self.assertEqual(result['username'], "kangraobin")
# # 添加操作与修改操作类似
# def test_add_user(self):
# r = requests.post(self.base_url + "/", data={'username': 'wupeng1', 'email': '1059457506@qq.com'},
# auth=self.auth)
# result1 = r.json()
# self.assertEqual(result1['username'], "wupeng1")
#
# # 判断是否授权
# def test_no_auth(self):
# r = requests.get(self.base_url)
# result = r.json()
# self.assertEqual(result['detail'], "Authentication credentials were not provided.")
if __name__ == '__main__':
db=DB()
file=open("datas.yaml","r")
db.init_data(file)
unittest.main()
待续...
s = requests.Session() r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'}) print(r.text) # '{"cookies": {"from-my": "browser"}}' r = s.get('http://httpbin.org/cookies') print(r.text) # '{"cookies": {}}'
Requests Session
对象的一些优势, 尤其 Session
级别的状态,例如 cookie 就不会被应用到你的请求上去。要获取一个带有状态的 PreparedRequest
, 请用 Session.prepare_request()
取代 Request.prepare()
的调用