Skip to content

Commit cd6ab4c

Browse files
committed
refactor (Core): parse request arguments with inspect.signature, aling with Py3
1 parent 5a759cf commit cd6ab4c

File tree

1 file changed

+62
-18
lines changed

1 file changed

+62
-18
lines changed

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

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
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-
3534
from DIRAC.Resources.IdProvider.IdProviderFactory import IdProviderFactory
3635

3736
sLog = gLogger.getSubLogger(__name__.split(".")[-1])
@@ -396,28 +395,73 @@ def _getMethodName(self):
396395
raise NotImplementedError("Please, create the _getMethodName method")
397396

398397
def _getMethodArgs(self, args):
399-
"""Decode 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.
400408
401409
:return: tuple -- contain args and kwargs
402410
"""
403-
# Read information about method
404-
fArgs, fvArgs, fvKwargs, fDef = inspect.getargspec(self.methodObj)
405-
# Remove self argument from method arguments
406-
fArgs = [a for a in fArgs if a != "self"]
407-
# Pass all arguments
408-
kwargs = self.request.arguments.copy() if fvKwargs else {}
409-
# Get all defaults from method
410-
defaults = {a: fDef[fArgs[-len(fDef) :].index(a)] for a in fArgs[-len(fDef) :]} if fDef else {}
411-
# Calcule kwargs
412-
for arg in fArgs[len(args) :]:
413-
if arg not in defaults:
414-
kwargs[arg] = self.get_argument(arg)
415-
elif isinstance(defaults[arg], list):
416-
kwargs[arg] = self.get_arguments(arg) or defaults[arg]
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+
)
444+
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)
417461
else:
418-
kwargs[arg] = self.get_argument(arg, defaults[arg])
462+
keywordArguments[name] = value
419463

420-
return ([unquote(a) for a in args], kwargs)
464+
return (positionalArguments, keywordArguments)
421465

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

0 commit comments

Comments
 (0)