吉拉邮政公司;PUT Rest调用从Python返回错误400
我在Jira Rest API中遇到了一个非常奇怪的问题—无论我尝试使用POST请求创建问题,还是使用PUT请求对Jira/Rest/API/latest/issue/I更新问题,我都会从Python 2.7请求中收到错误代码400,但它从Powershell的调用Web请求中成功 但是,我可以使用GET请求从Jira服务器提取信息,包括:吉拉邮政公司;PUT Rest调用从Python返回错误400,python,rest,python-requests,jira,jira-rest-api,Python,Rest,Python Requests,Jira,Jira Rest Api,我在Jira Rest API中遇到了一个非常奇怪的问题—无论我尝试使用POST请求创建问题,还是使用PUT请求对Jira/Rest/API/latest/issue/I更新问题,我都会从Python 2.7请求中收到错误代码400,但它从Powershell的调用Web请求中成功 但是,我可以使用GET请求从Jira服务器提取信息,包括: 项目清单 IssueType的列表 自定义字段的列表 给定项目的给定类型的问题 Zephyr插件的测试执行 Zephyr插件的测试周期 我已经从Atla
- 项目清单
- IssueType的列表
- 自定义字段的列表
- 给定项目的给定类型的问题
- Zephyr插件的测试执行
- Zephyr插件的测试周期
- 我已经验证了授权是正确的(GET请求也需要授权,所有请求都有效)
- 我正在使用一个对Jira实例具有管理员级别访问权限的帐户进行测试
- 我已将json剥离回与REST API文档匹配的字段和格式:
- 我已经确保所有相关的会话cookie和头数据都被存储并添加到后续请求中
- 我已经通过Issue/createmeta验证了我有能力创建该问题类型(正如我前面指出的,它在Powershell中工作)
- 我尝试使用Issuetype名称和id以及项目密钥和id作为标识符,两者都没有改变任何东西
- 我甚至尝试过在/issue路径上省略和包含尾部斜杠,以防这很重要
- 我已经验证了这不是Python的用户代理因POST/PUT请求而被阻止的情况
class Migrator(object):
RestURLs = {
"projects": "api/latest/project",
"issuetype": "api/latest/issuetype",
"fields": "api/latest/field",
"tests": "api/latest/search?maxResults={limit}&expand=meta&jql=IssueType='{testType}'+and+project={projectKey}",
"zSteps": "zapi/latest/teststep/{issueId}",
"zExecutions": "zapi/latest/zql/executeSearch?zqlQuery=project={projectKey}",
"zCycles": "zapi/latest/cycle?projectId={projectId}",
"issue": "api/latest/issue/{issueKey}",
"xSteps": "raven/1.0/api/test/{issueKey}/step/{stepId}",
"xSet": "raven/1.0/api/testset/{issueKey}/test",
"xExecution": "raven/1.0/api/testexec/{issueKey}/test"
}
CustomFields = {
"Zephyr Teststep": "",
"Manual Test Steps": "",
"Test Type": ""
}
IssueNames = {
"zephyr":"Zephyr - Test",
"xray":"Test",
"set":"Test Set",
"execution":"Test Execution"
}
IssueTypes = {}
def __init__(self):
self.results = []
print("new Migrator initialised")
self.restHandler = RestHandler()
self.baseURL = "http://127.0.0.1/jira/rest/"
self.authentication = ""
self.commonHeaders = {}
self.projectList = []
self.project = None
self.testList = []
self.executionList = {}
self.versionList = set()
self.cycleList = {}
self.setList = []
def connect(self, username, password, serverUrl="http://127.0.0.1"):
# 1 - connect to jira
if serverUrl[-1] != '/':
serverUrl += '/'
self.baseURL = str.format("{0}jira/rest/", serverUrl)
self.authentication = "Basic " + base64.b64encode(username + ":" + password)
self.commonHeaders = {"Authorization": self.authentication}
print("Connecting to Server: " + self.baseURL)
headers = self.commonHeaders
projList = self.restHandler.perform(method=HTTP.GET,url=self.baseURL,path=Migrator.RestURLs["projects"],headers=headers)
# 2 - populate projects list
for projDict in projList:
self.projectList.append(Project().fromDict(projDict))
class RestHandler(object):
def __init__(self):
self.headerStore = {'X-CITNET-USER':"",
'X-ASEN':"",
'X-ASESSIONID':"",
'X-AUSERNAME':""}
self.cookieJar = requests.cookies.RequestsCookieJar()
def perform(self, method, url, path, headers={}, urlData={"projectId": "", "projectKey": "", "issueId": "", "issueKey": ""},
formData=dict(), body=""):
resultData = "{}"
path = url + path.format(**urlData)
body = body if isinstance(body, str) else json.dumps(body)
if self.headerStore:
headers.update(self.headerStore)
jar = self.cookieJar
print(str(method))
print(path)
if method is HTTP.GET:
resultData = requests.get(path, headers=headers, cookies = jar)
elif method is HTTP.POST:
print (body)
path = path.rstrip('/')
resultData = requests.post(path, json=body, headers=headers, cookies = jar)
elif method is HTTP.PUT:
print (body)
resultData = requests.put(path, json=body, headers=headers, cookies = jar)
elif method is HTTP.DELETE:
request = "DELETE request to " + path
else:
raise TypeError
print("\n\n===============================\nRest Call Debugging\n===============================")
print(resultData)
print(resultData.url)
print(resultData.status_code)
print(resultData.headers)
print(resultData.content)
print("\n\n===============================\n/Rest Call Debugging\n==============================")
if 199 < resultData.status_code < 300:
for hKey, hValue in resultData.headers.iteritems():
if hKey in self.headerStore.keys():
self.headerStore[hKey] = hValue
self.cookieJar.update(resultData.cookies)
print "testing breakpoint"
return json.loads(resultData.content)
else:
raise IOError(resultData.reason)
根据此方法:
def migrateExecutions(self, project):
print "working..."
for execution in self.executionList:
# Restricting it only to my issues for testing...
if execution.assigneeUserName == "boydnic":
headers = self.commonHeaders
execData = {"fields":{}}
execData["fields"]["issuetype"] = {"id":self.IssueTypes[self.IssueNames["execution"]].id}
execData["fields"]["project"] = {"id":project.id}
# execData["fields"]["reporter"] = {"name": userName}
# execData["fields"]["assignee"] = {"name": execution.assigneeUserName}
execData["fields"]["summary"] = "Execution for Issue: " + execution.issueKey
execData["fields"]["description"] = execution.comment if execution.comment else execData["fields"]["summary"]
xrayExec = self.createIssue(execData)
self.results.append(self.restHandler.perform(method=HTTP.POST, url=self.baseURL,
path=self.RestURLs["xExecution"], urlData={"issueKey":xrayExec.key},
headers=headers, body={"add":[execution.issueKey]}))
对于此方法:
def createIssue(self, issueTemplate):
result = self.restHandler.perform(method=HTTP.POST, url=self.baseURL, path=Migrator.RestURLs["issue"], urlData={"issueKey":""}, headers=self.commonHeaders, body=issueTemplate)
issue = Issue()
issue.id = result["id"]
issue.key = result["key"]
issue.self = result["self"]
print("Created Issue: "+issue.key)
return issue
它本身称这个类:
class Migrator(object):
RestURLs = {
"projects": "api/latest/project",
"issuetype": "api/latest/issuetype",
"fields": "api/latest/field",
"tests": "api/latest/search?maxResults={limit}&expand=meta&jql=IssueType='{testType}'+and+project={projectKey}",
"zSteps": "zapi/latest/teststep/{issueId}",
"zExecutions": "zapi/latest/zql/executeSearch?zqlQuery=project={projectKey}",
"zCycles": "zapi/latest/cycle?projectId={projectId}",
"issue": "api/latest/issue/{issueKey}",
"xSteps": "raven/1.0/api/test/{issueKey}/step/{stepId}",
"xSet": "raven/1.0/api/testset/{issueKey}/test",
"xExecution": "raven/1.0/api/testexec/{issueKey}/test"
}
CustomFields = {
"Zephyr Teststep": "",
"Manual Test Steps": "",
"Test Type": ""
}
IssueNames = {
"zephyr":"Zephyr - Test",
"xray":"Test",
"set":"Test Set",
"execution":"Test Execution"
}
IssueTypes = {}
def __init__(self):
self.results = []
print("new Migrator initialised")
self.restHandler = RestHandler()
self.baseURL = "http://127.0.0.1/jira/rest/"
self.authentication = ""
self.commonHeaders = {}
self.projectList = []
self.project = None
self.testList = []
self.executionList = {}
self.versionList = set()
self.cycleList = {}
self.setList = []
def connect(self, username, password, serverUrl="http://127.0.0.1"):
# 1 - connect to jira
if serverUrl[-1] != '/':
serverUrl += '/'
self.baseURL = str.format("{0}jira/rest/", serverUrl)
self.authentication = "Basic " + base64.b64encode(username + ":" + password)
self.commonHeaders = {"Authorization": self.authentication}
print("Connecting to Server: " + self.baseURL)
headers = self.commonHeaders
projList = self.restHandler.perform(method=HTTP.GET,url=self.baseURL,path=Migrator.RestURLs["projects"],headers=headers)
# 2 - populate projects list
for projDict in projList:
self.projectList.append(Project().fromDict(projDict))
class RestHandler(object):
def __init__(self):
self.headerStore = {'X-CITNET-USER':"",
'X-ASEN':"",
'X-ASESSIONID':"",
'X-AUSERNAME':""}
self.cookieJar = requests.cookies.RequestsCookieJar()
def perform(self, method, url, path, headers={}, urlData={"projectId": "", "projectKey": "", "issueId": "", "issueKey": ""},
formData=dict(), body=""):
resultData = "{}"
path = url + path.format(**urlData)
body = body if isinstance(body, str) else json.dumps(body)
if self.headerStore:
headers.update(self.headerStore)
jar = self.cookieJar
print(str(method))
print(path)
if method is HTTP.GET:
resultData = requests.get(path, headers=headers, cookies = jar)
elif method is HTTP.POST:
print (body)
path = path.rstrip('/')
resultData = requests.post(path, json=body, headers=headers, cookies = jar)
elif method is HTTP.PUT:
print (body)
resultData = requests.put(path, json=body, headers=headers, cookies = jar)
elif method is HTTP.DELETE:
request = "DELETE request to " + path
else:
raise TypeError
print("\n\n===============================\nRest Call Debugging\n===============================")
print(resultData)
print(resultData.url)
print(resultData.status_code)
print(resultData.headers)
print(resultData.content)
print("\n\n===============================\n/Rest Call Debugging\n==============================")
if 199 < resultData.status_code < 300:
for hKey, hValue in resultData.headers.iteritems():
if hKey in self.headerStore.keys():
self.headerStore[hKey] = hValue
self.cookieJar.update(resultData.cookies)
print "testing breakpoint"
return json.loads(resultData.content)
else:
raise IOError(resultData.reason)
类RestHandler(对象):
定义初始化(自):
self.headerStore={'X-CITNET-USER':“”,
“X-ASEN”:“,
“X-ASESSIONID”:“,
“X-AUSERNAME”:“”}
self.cookieJar=requests.cookies.RequestsCookieJar()
def perform(self,method,url,path,headers={},urlData={“projectId”:“,”projectKey:“,”issueId:“,”issueKey:“,”,
formData=dict(),body=“”):
resultData=“{}”
path=url+path.format(**urlData)
body=isinstance时的body(body,str)else json.dumps(body)
如果self.headerStore:
headers.update(self.headerStore)
jar=self.cookieJar
打印(str(方法))
打印(路径)
如果方法是HTTP.GET:
resultData=requests.get(路径,headers=headers,cookies=jar)
elif方法是HTTP.POST:
印刷品(正文)
path=path.rstrip(“/”)
resultData=requests.post(路径,json=body,headers=headers,cookies=jar)
elif方法是HTTP.PUT:
印刷品(正文)
resultData=requests.put(路径,json=body,headers=headers,cookies=jar)
elif方法为HTTP.DELETE:
request=“删除请求到”+路径
其他:
提高打字错误
打印(“\n\n===========================================\n重新启动调用调试\n===========================================”)
打印(结果数据)
打印(resultData.url)
打印(结果数据状态\ U代码)
打印(resultData.headers)
打印(resultData.content)
打印(“\n\n=====================================\n/Rest调用调试\n==============================================”)
如果199
Rest处理程序类中包含的调试部分仅给出以下内容:
===============================
Rest Call Debugging
===============================
https://webgate.test.ec.europa.eu/CITnet/jira/rest/api/latest/issue
400
{'X-AUSERNAME': 'boydnic', 'X-AREQUESTID': '<redacted>', 'X-Content-Type-Options': 'nosniff', 'Transfer-Encoding': 'chunked', 'Set-Cookie': 'crowd.token_key=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; HttpOnly, crowd.token_key=<redacted>; Path=/; HttpOnly, JSESSIONID=<redacted>; Path=/CITnet/jira; HttpOnly, atlassian.xsrf.token=<redacted>; Path=/CITnet/jira', 'X-Seraph-LoginReason': 'OUT, OK', 'X-ASEN': '<redacted>', 'X-CITNET-USER': 'boydnic', 'Connection': 'Keep-Alive', 'X-ASESSIONID': '<redacted>', 'Cache-Control': 'no-cache, no-store, no-transform, proxy-revalidate', 'Date': 'Tue, 24 Apr 2018 08:29:16 GMT', 'Server': 'Apache-Coyote/1.1', 'Content-Type': 'application/json;charset=UTF-8'}
{"errorMessages":["Can not instantiate value of type [simple type, class com.atlassian.jira.rest.v2.issue.IssueUpdateBean] from JSON String; no single-String constructor/factory method"]}
===============================
/Rest Call Debugging
==============================
===============================
Rest调用调试
===============================
https://webgate.test.ec.europa.eu/CITnet/jira/rest/api/latest/issue
400
{'X-AUSERNAME':'boydnic','X-AREQUESTID':'','X-Content-Type-Options':'nosniff','Transfer-Encoding':'chunked','Set-Cookie':'crowd.token_-key=“”Expires=Thu,01-Jan-1970 00:00:10 GMT;Path=/;HttpOnly,crowd.token_key=;Path=/;HttpOnly,JSESSIONID=;Path=/CITnet/jira;HttpOnly,atlassian.xsrf.token=;Path=/CITnet/jira',X-Seraph-LoginReason':'OUT,OK','X-ASEN':'X-CITnet-USER':'boydnic','Connection':'Keep-Alive','X-ASESSIONID':'Cache-Control:'no-Cache,no-Cache,no-transform,proxy revalidate,'日期':'2018年4月24日星期二08:29:16 GMT,'服务器':'Apache Coyote/1.1','内容类型':'应用程序/json;字符集=UTF-8'}
{“errorMessages”:[“无法从JSON字符串实例化类型[简单类型,类com.atlassian.jira.rest.v2.issue.IssueUpdateBean]的值;没有单个字符串构造函数/工厂方法”]}
===============================
/Rest调用调试
==============================
与此I/O错误混杂在一起:
(我为这篇文章理清了STD和ERR流)
回溯(最近一次调用上次):文件
“C:/Users/BOYDnic/Documents/migrator/issumedimor.py”,第546行,在
jiraMigrator.migrate(projectKey)
文件“C:/Users/BOYDnic/Documents/migrator/issumedimor.py”,第330行,在migrate中
self.migrateExecutions(项目)文件“C:/Users/BOYDnic/Documents/migrator/issumestemperator.py”,第475行,在
迁移执行
xrayExec=self.createIssue(execData)文件“C:/Users/BOYDnic/Documents/migrator/issumedimor.py”,第334行,在
创建问题
result=self.restHandler.perform(方法=HTTP.POST,url=self.baseURL,路径=Migrator.restURL[“问题”],
urlD
Traceback (most recent call last): File
"C:/Users/BOYDnic/Documents/migrator/issueMigrator.py", line 546, in
<module> <Response [400]>
jiraMigrator.migrate(projectKey)
File "C:/Users/BOYDnic/Documents/migrator/issueMigrator.py", line 330, in migrate
self.migrateExecutions(project) File "C:/Users/BOYDnic/Documents/migrator/issueMigrator.py", line 475, in
migrateExecutions
xrayExec = self.createIssue(execData) File "C:/Users/BOYDnic/Documents/migrator/issueMigrator.py", line 334, in
createIssue
result = self.restHandler.perform(method=HTTP.POST, url=self.baseURL, path=Migrator.RestURLs["issue"],
urlData={"issueKey":""}, headers=self.commonHeaders,
body=issueTemplate) File
"C:/Users/BOYDnic/Documents/migrator/issueMigrator.py", line 84, in
perform
raise IOError(resultData.reason) IOError: Bad Request
{"fields": {"issuetype": {"id": 10702},"project": {"id":10061},"description": "","summary": "Execution for Issue: SDBX-859"}}
"{\"fields\": {\"issuetype\": {\"id\": \"10702\"}, \"project\": {\"id\": \"10061\"}, \"description\": \"Execution for Issue: SDBX-859\", \"summary\": \"Execution for Issue: SDBX-859\"}}"