Skip to content

Commit 894a8cc

Browse files
committed
refactor: move method arguments detection to TornadoREST class
1 parent e72fad9 commit 894a8cc

File tree

2 files changed

+76
-66
lines changed

2 files changed

+76
-66
lines changed

src/DIRAC/Core/Tornado/Server/TornadoREST.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99

1010
__RCSID__ = "$Id$"
1111

12+
import inspect
1213
from tornado import gen
1314
from tornado.ioloop import IOLoop
15+
from urllib.parse import unquote
1416

1517
from DIRAC import gLogger, S_OK
1618
from DIRAC.ConfigurationSystem.Client import PathFinder
@@ -115,6 +117,75 @@ def _getMethodName(self):
115117
"%s method not implemented. You can use the index method to handle this." % method
116118
)
117119

120+
def _getMethodArgs(self, args):
121+
"""Search method arguments.
122+
123+
By default, the arguments are taken from the description of the method itself.
124+
Then the arguments received in the request are assigned by the name of the method arguments.
125+
126+
.. warning:: this means that the target methods cannot be wrapped in the decorator,
127+
or if so the decorator must duplicate the arguments and annotation of the target method
128+
129+
:param tuple args: positional arguments, they are determined by a variable path_< method name >, e.g.:
130+
`path_methodName = ['([A-z0-9-_]*)']`. In most cases, this is simply not used.
131+
132+
:return: tuple -- contain args and kwargs
133+
"""
134+
# Read signature of a target function
135+
# https://docs.python.org/3/library/inspect.html#inspect.Signature
136+
signature = inspect.signature(self.methodObj)
137+
138+
# Collect all values of the arguments transferred in a request
139+
args = [unquote(a) for a in args] # positional arguments
140+
kwargs = {a: self.get_argument(a) for a in self.request.arguments} # keyword arguments
141+
142+
# Create a mapping from request arguments to parameters
143+
bound = signature.bind(*args, **kwargs)
144+
# Set default values for missing arguments.
145+
bound.apply_defaults()
146+
147+
keywordArguments = {}
148+
positionalArguments = []
149+
# Now let's check whether the value of the argument corresponds to the type specified in the objective function or type of the default value.
150+
for name in signature.parameters:
151+
value = bound.arguments[name]
152+
kind = signature.parameters[name].kind
153+
default = signature.parameters[name].default
154+
155+
# Determine what type of the target function argument is expected. By Default it's str.
156+
annotation = (
157+
signature.parameters[name].annotation
158+
# Select the type specified in the target function, if any.
159+
# E.g.: def export_f(self, number:int): return S_OK(number)
160+
if signature.parameters[name].annotation is not inspect.Parameter.empty
161+
else str
162+
# If there is no argument annotation, take the default value type, if any
163+
# E.g.: def export_f(self, number=0): return S_OK(number)
164+
if default is inspect.Parameter.empty
165+
else type(default)
166+
)
167+
168+
# If the type of the argument value does not match the expectation, we convert it to the appropriate type
169+
if value != default:
170+
# Get list of the arguments
171+
if annotation is list:
172+
value = self.get_arguments(name) if name in self.request.arguments else [value]
173+
# Get integer argument
174+
elif annotation is int:
175+
value = int(value)
176+
177+
# Collect positional parameters separately
178+
if kind == inspect.Parameter.POSITIONAL_ONLY:
179+
positionalArguments.append(value)
180+
elif kind == inspect.Parameter.VAR_POSITIONAL:
181+
positionalArguments.extend(value)
182+
elif kind == inspect.Parameter.VAR_KEYWORD:
183+
keywordArguments.update(value)
184+
else:
185+
keywordArguments[name] = value
186+
187+
return (positionalArguments, keywordArguments)
188+
118189
@gen.coroutine
119190
def get(self, *args, **kwargs): # pylint: disable=arguments-differ
120191
"""Method to handle incoming ``GET`` requests.

src/DIRAC/Core/Tornado/Server/private/BaseRequestHandler.py

Lines changed: 5 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from DIRAC.Core.Security.X509Chain import X509Chain # pylint: disable=import-error
3232
from DIRAC.FrameworkSystem.Client.MonitoringClient import MonitoringClient
3333
from DIRAC.Resources.IdProvider.Utilities import getProvidersForInstance
34+
3435
from DIRAC.Resources.IdProvider.IdProviderFactory import IdProviderFactory
3536

3637
sLog = gLogger.getSubLogger(__name__.split(".")[-1])
@@ -395,73 +396,11 @@ def _getMethodName(self):
395396
raise NotImplementedError("Please, create the _getMethodName method")
396397

397398
def _getMethodArgs(self, args):
398-
"""Search method arguments.
399-
400-
By default, the arguments are taken from the description of the method itself.
401-
Then the arguments received in the request are assigned by the name of the method arguments.
402-
403-
.. warning:: this means that the target methods cannot be wrapped in the decorator,
404-
or if so the decorator must duplicate the arguments and annotation of the target method
405-
406-
:param tuple args: positional arguments, they are determined by a variable path_< method name >, e.g.:
407-
`path_methodName = ['([A-z0-9-_]*)']`. In most cases, this is simply not used.
408-
409-
:return: tuple -- contain args and kwargs
410-
"""
411-
# Read signature of a target function
412-
# https://docs.python.org/3/library/inspect.html#inspect.Signature
413-
signature = inspect.signature(self.methodObj)
414-
415-
# Collect all values of the arguments transferred in a request
416-
args = [unquote(a) for a in args] # positional arguments
417-
kwargs = {a: self.get_argument(a) for a in self.request.arguments} # keyword arguments
418-
419-
# Create a mapping from request arguments to parameters
420-
bound = signature.bind(*args, **kwargs)
421-
# Set default values for missing arguments.
422-
bound.apply_defaults()
423-
424-
keywordArguments = {}
425-
positionalArguments = []
426-
# Now let's check whether the value of the argument corresponds to the type specified in the objective function or type of the default value.
427-
for name in signature.parameters:
428-
value = bound.arguments[name]
429-
kind = signature.parameters[name].kind
430-
default = signature.parameters[name].default
431-
432-
# Determine what type of the target function argument is expected. By Default it's str.
433-
annotation = (
434-
signature.parameters[name].annotation
435-
# Select the type specified in the target function, if any.
436-
# E.g.: def export_f(self, number:int): return S_OK(number)
437-
if signature.parameters[name].annotation is not inspect.Parameter.empty
438-
else str
439-
# If there is no argument annotation, take the default value type, if any
440-
# E.g.: def export_f(self, number=0): return S_OK(number)
441-
if default is inspect.Parameter.empty
442-
else type(default)
443-
)
399+
"""Decode args.
444400
445-
# If the type of the argument value does not match the expectation, we convert it to the appropriate type
446-
if value != default:
447-
# Get list of the arguments
448-
if annotation is list:
449-
value = self.get_arguments(name) if name in self.request.arguments else [value]
450-
# Get integer argument
451-
elif annotation is int:
452-
value = int(value)
453-
454-
# Collect positional parameters separately
455-
if kind == inspect.Parameter.POSITIONAL_ONLY:
456-
positionalArguments.append(value)
457-
elif kind == inspect.Parameter.VAR_POSITIONAL:
458-
positionalArguments.extend(value)
459-
elif kind == inspect.Parameter.VAR_KEYWORD:
460-
keywordArguments.update(value)
461-
else:
462-
keywordArguments[name] = value
463-
464-
return (positionalArguments, keywordArguments)
401+
:return: (list, dict) -- tuple contain args and kwargs
402+
"""
403+
raise NotImplementedError("Please, create the _getMethodArgs method")
465404

466405
def _getMethodAuthProps(self):
467406
"""Resolves the hard coded authorization requirements for method.

0 commit comments

Comments
 (0)