Skip to content

Commit cd3ed27

Browse files
author
Luiko Czub
committed
decorators separated into module testlinkdecorators lczub#8
separation makes module testlinkapigeneric more readable
1 parent faa5035 commit cd3ed27

File tree

5 files changed

+388
-269
lines changed

5 files changed

+388
-269
lines changed

Diff for: src/testlink/testlinkapigeneric.py

+36-141
Original file line numberDiff line numberDiff line change
@@ -18,148 +18,12 @@
1818
# ------------------------------------------------------------------------
1919

2020
import xmlrpclib
21-
from functools import wraps
21+
import testlinkerrors
2222
from .testlinkhelper import TestLinkHelper, VERSION
23-
#from .testlinkargs import registerMethod, registerArgOptional, getMethodsWithPositionalArgs
24-
from .testlinkargs import *
25-
import testlinkerrors #, testlinkargs
26-
27-
28-
# # Default Definition which (python) API-Method expects which positional arguments
29-
# # this must not be equal to mandatory params of the (php) xmlrpc Methods
30-
# # it defines arguments, which values must be passed without explicit names
31-
# # to the API-Method
32-
# # this is stored during the init in ._positionalArgNames
33-
# # subclasses could override this definition, if their (python) method should
34-
# # work with different positional arguments
35-
# positionalArgNamesDefault = {
36-
# 'createBuild' : ['testplanid', 'buildname'],
37-
# 'createTestCase' : ['testcasename', 'testsuiteid', 'testprojectid',
38-
# 'authorlogin', 'summary', 'steps'],
39-
# 'createTestPlan' : ['testplanname', 'testprojectname'],
40-
# 'createTestProject' : ['testprojectname', 'testcaseprefix'],
41-
# 'createTestSuite' : ['testprojectid', 'testsuitename', 'details'],
42-
# 'getBuildsForTestPlan' : ['testplanid'],
43-
# 'getFirstLevelTestSuitesForTestProject' : ['testprojectid'],
44-
# 'getFullPath' : ['nodeid'],
45-
# 'getLastExecutionResult' : ['testplanid'],
46-
# 'getLatestBuildForTestPlan' : ['testplanid'],
47-
# 'getProjectTestPlans' : ['testprojectid'],
48-
# 'getTestCaseCustomFieldDesignValue' : ['testcaseexternalid', 'version',
49-
# 'testprojectid', 'customfieldname'],
50-
# 'getTestCaseIDByName' : ['testcasename'],
51-
# 'getTestCasesForTestPlan' : ['testplanid'],
52-
# 'getTestCasesForTestSuite' : ['testsuiteid'],
53-
# 'getTestPlanByName' : ['testprojectname', 'testplanname'],
54-
# 'getTestPlanPlatforms' : ['testplanid'],
55-
# 'getTestProjectByName' : ['testprojectname'],
56-
# 'getTestSuiteByID' : ['testsuiteid'],
57-
# 'getTestSuitesForTestPlan' : ['testplanid'],
58-
# 'getTestSuitesForTestSuite' : ['testsuiteid'],
59-
# 'getTotalsForTestPlan' : ['testplanid'],
60-
# 'doesUserExist' : ['user'],
61-
# 'repeat' : ['str'],
62-
# 'reportTCResult' : ['testplanid', 'status'],
63-
# 'uploadExecutionAttachment' : ['executionid']
64-
# }
65-
66-
# decorators for generic api calls
67-
# see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
68-
69-
def decoApiCallWithoutArgs(methodAPI):
70-
""" Decorator for calling server methods without arguments """
71-
72-
# register methods without positional and optional arguments
73-
registerMethod(methodAPI.__name__)
74-
75-
@wraps(methodAPI)
76-
def wrapperWithoutArgs(self):
77-
return self.callServerWithPosArgs(methodAPI.__name__)
78-
return wrapperWithoutArgs
79-
80-
def decoMakerApiCallWithArgs(argNamesPositional=[], argNamesOptional=[]):
81-
""" creates a decorator for calling a server method with arguments
82-
83-
argNamesPositional defines a list of positional arguments, which should be
84-
registered in the global apiMethodsArgNames for the server method
85-
argNamesOptional defines a list of optional arguments, which should be
86-
registered in the global apiMethodsArgNames for the server method
87-
88-
"""
89-
90-
def decoApiCallWithArgs(methodAPI):
91-
""" Decorator for calling a server method with arguments """
92-
93-
# register methods positional and optional arguments
94-
registerMethod(methodAPI.__name__, argNamesPositional, argNamesOptional)
95-
# define the method server call
96-
@wraps(methodAPI)
97-
def wrapperWithArgs(self, *argsPositional, **argsOptional):
98-
return self.callServerWithPosArgs(methodAPI.__name__,
99-
*argsPositional, **argsOptional)
100-
return wrapperWithArgs
101-
return decoApiCallWithArgs
102-
103-
def decoApiCallAddDevKey(methodAPI):
104-
""" Decorator to expand parameter list with devKey"""
105-
# register additional optional argument devKey
106-
registerArgOptional(methodAPI.__name__, 'devKey')
107-
@wraps(methodAPI)
108-
def wrapperAddDevKey(self, *argsPositional, **argsOptional):
109-
if not ('devKey' in argsOptional):
110-
argsOptional['devKey'] = self.devKey
111-
return methodAPI(self, *argsPositional, **argsOptional)
112-
return wrapperAddDevKey
113-
114-
def decoMakerApiCallReplaceTLResponseError(replaceCode=None):
115-
""" creates a decorator, which replace an TLResponseError with an empty list,
116-
117-
Default (replaceCode=None) handles the cause 'Empty Result'
118-
- ok for getProjectTestPlans, getBuildsForTestPlan, which returns just ''
119-
Problems are getTestPlanByName, getFirstLevelTestSuitesForTestProject
120-
- they do not return just '', they returns the error message
121-
3041: Test plan (noPlatform) has no platforms linked
122-
7008: Test Project (noSuite) is empty
123-
could be handled with replaceCode=3041 / replaceCode=7008
124-
125-
"""
126-
# for understanding, what we are doing here please read
127-
# # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
128-
# - Passing arguments to the decorator
129-
130-
def decoApiCallReplaceTLResponseError(methodAPI):
131-
""" Decorator to replace an TLResponseError with an empty list """
132-
@wraps(methodAPI)
133-
def wrapperReplaceTLResponseError(self, *argsPositional, **argsOptional):
134-
response = None
135-
try:
136-
response = methodAPI(self, *argsPositional, **argsOptional)
137-
except testlinkerrors.TLResponseError as tl_err:
138-
if tl_err.code == replaceCode:
139-
# empty result (response == '') -> code == None
140-
# special case NoPlatform -> code == 3041
141-
response = []
142-
else:
143-
# seems to be another response failure - we forward it
144-
raise
145-
return response
146-
return wrapperReplaceTLResponseError
147-
return decoApiCallReplaceTLResponseError
148-
149-
def decoApiCallAddAttachment(methodAPI):
150-
""" Decorator to expand parameter list with devKey and attachmentfile
151-
attachmentfile is a python file descriptor pointing to the file
152-
"""
153-
def wrapperAddAttachment(self, attachmentfile, *argsPositional, **argsOptional):
154-
if not ('devKey' in argsOptional):
155-
argsOptional['devKey'] = self.devKey
156-
argsAttachment = self._getAttachmentArgs(attachmentfile)
157-
# add additional key/value pairs from argsOptional
158-
# although overwrites filename, filetype, content with user definition
159-
# if they exist
160-
argsAttachment.update(argsOptional)
161-
return methodAPI(self, *argsPositional, **argsAttachment)
162-
return wrapperAddAttachment
23+
from .testlinkargs import getMethodsWithPositionalArgs
24+
from .testlinkdecorators import decoApiCallAddAttachment,\
25+
decoApiCallAddDevKey, decoApiCallWithoutArgs, \
26+
decoMakerApiCallReplaceTLResponseError, decoMakerApiCallWithArgs
16327

16428

16529
class TestlinkAPIGeneric(object):
@@ -195,6 +59,37 @@ def __init__(self, server_url, devKey, **args):
19559

19660
# GENERIC API CALLS - using decorators
19761
# http://stackoverflow.com/questions/1015307/python-bind-an-unbound-method
62+
63+
# Method definitions should be build either with
64+
# @decoMakerApiCallWithArgs(argNamesPositional, argNamesOptional)
65+
# for calling a server method with arguments
66+
# - argNamesPositional = list default positional args
67+
# - argNamesOptional = list additional optional args
68+
# to check the server response, if it includes TestLink Error Codes or
69+
# an empty result (which raise a TLResponseError)
70+
# or
71+
# @decoApiCallWithoutArgs
72+
# for calling server methods without arguments
73+
# to check the server response, if it includes TestLink Error Codes or
74+
# an empty result (which raise a TLResponseError)
75+
#
76+
# Additional behavior could be added with
77+
#
78+
# @decoApiCallAddDevKey
79+
# - to expand the parameter list with devKey key/value pair
80+
# before calling the server method
81+
# @decoMakerApiCallReplaceTLResponseError(replaceCode)
82+
# - to catch an TLResponseError after calling the server method and
83+
# with an empty list
84+
# - replaceCode : TestLink Error Code, which should be handled
85+
# default is None for "Empty Results"
86+
# @decoApiCallAddAttachment(methodAPI):
87+
# - to add an mandatory argument 'attachmentfile'
88+
# - attachmentfile is a python file descriptor pointing to the file
89+
# - to expand parameter list with key/value pairs
90+
# 'filename', 'filetype', 'content'
91+
# from 'attachmentfile' before calling the server method
92+
19893

19994
@decoApiCallAddDevKey
20095
@decoMakerApiCallWithArgs(['testplanid'])

Diff for: src/testlink/testlinkargs.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from .testlinkerrors import TLArgError
2121

2222

23-
__doc__ = """ This modul is used as a 'singleton' to register the supported
24-
TestLink API methods and there (positional and optional) arguments """
23+
__doc__ = """ This internal module is used as a 'singleton' to register the
24+
supported TestLink API methods and there (positional and optional) arguments """
2525

2626

2727
# main hash, where the registered methods and there arguments are stored

Diff for: src/testlink/testlinkdecorators.py

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#! /usr/bin/python
2+
# -*- coding: UTF-8 -*-
3+
4+
# Copyright 2013 Luiko Czub, TestLink-API-Python-client developers
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
# ------------------------------------------------------------------------
19+
20+
from functools import wraps
21+
from .testlinkargs import registerMethod, registerArgOptional
22+
from .testlinkerrors import TLResponseError
23+
24+
__doc__ = """ This internal module defines the decorator functions, which are
25+
used to generate the TestLink API methods in a generic way
26+
27+
Method definitions should be build either with
28+
@decoMakerApiCallWithArgs(argNamesPositional, argNamesOptional)
29+
for calling a server method with arguments
30+
- argNamesPositional = list default positional args
31+
- argNamesOptional = list additional optional args
32+
to check the server response, if it includes TestLink Error Codes or
33+
an empty result (which raise a TLResponseError)
34+
or
35+
@decoApiCallWithoutArgs
36+
for calling server methods without arguments
37+
to check the server response, if it includes TestLink Error Codes or
38+
an empty result (which raise a TLResponseError)
39+
40+
Additional behavior could be added with
41+
42+
@decoApiCallAddDevKey
43+
- to expand the parameter list with devKey key/value pair
44+
before calling the server method
45+
@decoMakerApiCallReplaceTLResponseError(replaceCode)
46+
- to catch an TLResponseError after calling the server method and with an
47+
empty list
48+
- replaceCode : TestLink Error Code, which should be handled
49+
default is None for "Empty Results"
50+
@decoApiCallAddAttachment(methodAPI):
51+
- to add an mandatory argument 'attachmentfile'
52+
- attachmentfile is a python file descriptor pointing to the file
53+
- to expand parameter list with key/value pairs
54+
'filename', 'filetype', 'content'
55+
from 'attachmentfile' before calling the server method
56+
"""
57+
58+
59+
60+
# decorators for generic api calls
61+
# see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
62+
63+
def decoApiCallWithoutArgs(methodAPI):
64+
""" Decorator for calling server methods without arguments """
65+
66+
# register methods without positional and optional arguments
67+
registerMethod(methodAPI.__name__)
68+
69+
@wraps(methodAPI)
70+
def wrapperWithoutArgs(self):
71+
return self.callServerWithPosArgs(methodAPI.__name__)
72+
return wrapperWithoutArgs
73+
74+
def decoMakerApiCallWithArgs(argNamesPositional=[], argNamesOptional=[]):
75+
""" creates a decorator for calling a server method with arguments
76+
77+
argNamesPositional defines a list of positional arguments, which should be
78+
registered in the global apiMethodsArgNames for the server method
79+
argNamesOptional defines a list of optional arguments, which should be
80+
registered in the global apiMethodsArgNames for the server method
81+
82+
"""
83+
84+
def decoApiCallWithArgs(methodAPI):
85+
""" Decorator for calling a server method with arguments """
86+
87+
# register methods positional and optional arguments
88+
registerMethod(methodAPI.__name__, argNamesPositional, argNamesOptional)
89+
# define the method server call
90+
@wraps(methodAPI)
91+
def wrapperWithArgs(self, *argsPositional, **argsOptional):
92+
return self.callServerWithPosArgs(methodAPI.__name__,
93+
*argsPositional, **argsOptional)
94+
return wrapperWithArgs
95+
return decoApiCallWithArgs
96+
97+
def decoApiCallAddDevKey(methodAPI):
98+
""" Decorator to expand parameter list with devKey"""
99+
# register additional optional argument devKey
100+
registerArgOptional(methodAPI.__name__, 'devKey')
101+
@wraps(methodAPI)
102+
def wrapperAddDevKey(self, *argsPositional, **argsOptional):
103+
if not ('devKey' in argsOptional):
104+
argsOptional['devKey'] = self.devKey
105+
return methodAPI(self, *argsPositional, **argsOptional)
106+
return wrapperAddDevKey
107+
108+
def decoMakerApiCallReplaceTLResponseError(replaceCode=None):
109+
""" creates a decorator, which replace an TLResponseError with an empty list,
110+
111+
Default (replaceCode=None) handles the cause 'Empty Result'
112+
- ok for getProjectTestPlans, getBuildsForTestPlan, which returns just ''
113+
Problems are getTestPlanByName, getFirstLevelTestSuitesForTestProject
114+
- they do not return just '', they returns the error message
115+
3041: Test plan (noPlatform) has no platforms linked
116+
7008: Test Project (noSuite) is empty
117+
could be handled with replaceCode=3041 / replaceCode=7008
118+
119+
"""
120+
# for understanding, what we are doing here please read
121+
# # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
122+
# - Passing arguments to the decorator
123+
124+
def decoApiCallReplaceTLResponseError(methodAPI):
125+
""" Decorator to replace an TLResponseError with an empty list """
126+
@wraps(methodAPI)
127+
def wrapperReplaceTLResponseError(self, *argsPositional, **argsOptional):
128+
response = None
129+
try:
130+
response = methodAPI(self, *argsPositional, **argsOptional)
131+
except TLResponseError as tl_err:
132+
if tl_err.code == replaceCode:
133+
# empty result (response == '') -> code == None
134+
# special case NoPlatform -> code == 3041
135+
response = []
136+
else:
137+
# seems to be another response failure - we forward it
138+
raise
139+
return response
140+
return wrapperReplaceTLResponseError
141+
return decoApiCallReplaceTLResponseError
142+
143+
def decoApiCallAddAttachment(methodAPI):
144+
""" Decorator to expand parameter list with devKey and attachmentfile
145+
attachmentfile is a python file descriptor pointing to the file
146+
"""
147+
@wraps(methodAPI)
148+
def wrapperAddAttachment(self, attachmentfile, *argsPositional, **argsOptional):
149+
if not ('devKey' in argsOptional):
150+
argsOptional['devKey'] = self.devKey
151+
argsAttachment = self._getAttachmentArgs(attachmentfile)
152+
# add additional key/value pairs from argsOptional
153+
# although overwrites filename, filetype, content with user definition
154+
# if they exist
155+
argsAttachment.update(argsOptional)
156+
return methodAPI(self, *argsPositional, **argsAttachment)
157+
return wrapperAddAttachment
158+

0 commit comments

Comments
 (0)