1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 """
31 This module contains the core logic for interface declaration and validation.
32 """
33
34 import types
35 import logging
36
37 from eoxserver.core.exceptions import InternalError, TypeMismatch, ConfigError
38
39
40 logger = logging.getLogger(__name__)
41
42 global RUNTIME_VALIDATION_LEVEL
43
44 RUNTIME_VALIDATION_LEVEL = "trust"
45
46
47
48
49
50 -class Arg(object):
51 """
52 This is the common base class for arguments of any kind; it can be used
53 in interface declarations as well to represent an argument of arbitrary
54 type.
55
56 The constructor requires a ``name`` argument which denotes the argument
57 name. The validation will check at class creation time if the method of an
58 implementing class defines an argument of the given name, so you should
59 always use valid Python variable names here (you can use arbitrary strings
60 for return value declarations though).
61
62 Furthermore, the constructor accepts a ``default`` keyword argument which
63 defines a default value for the declared argument. The validation will
64 check at class creation time if this default value is present in the
65 implementing class and fail if it is not.
66
67 Its methods are intended for internal use in runtime validation.
68 """
69
71 self.name = name
72
73 if "default" in kwargs:
74 self.default = kwargs["default"]
75 self.optional = True
76 else:
77 self.default = None
78 self.optional = False
79
81 """
82 Returns ``True`` if the argument is optional, meaning that a default
83 value has been defined for it, ``False`` otherwise.
84 """
85
86 return self.optional
87
89 """
90 Returns ``True`` if ``arg_value`` is an acceptable value for the
91 argument, ``False`` otherwise. Acceptable values are either the default
92 value if it has been defined or values of the expected type.
93 """
94
95 return (self.optional and self.default == arg_value) or \
96 self.isValidType(arg_value)
97
99 """
100 Returns ``True`` if the argument value ``arg_value`` has a valid type,
101 ``False`` otherwise. This method is overridden by :class:`Arg`
102 subclasses in order to check for individual types. The base class
103 implementation always returns ``True`` meaning that all types of
104 argument values are accepted.
105 """
106
107 return True
108
110 """
111 Returns the expected type name; used in error messages only. This
112 method is overridden by :class:`Arg` subclasses in order to customize
113 error reporting. The base class implementation returns ``""``.
114 """
115
116 return ""
117
119 """
120 Represents an argument of type :class:`str`.
121 """
123 return isinstance(arg_value, str)
124
127
129 """
130 Represents an argument of type :class:`unicode`.
131 """
133 return isinstance(arg_value, unicode)
134
137
139 """
140 Represents an argument of types :class:`str` or :class:`unicode`.
141 """
143 return (isinstance(arg_value, str) or isinstance(arg_value, unicode))
144
146 return "str' or 'unicode"
147
149 """
150 Represents an argument of type :class:`bool`.
151 """
153 return isinstance(arg_value, bool)
154
157
159 """
160 Represents an argument of type :class:`int`.
161 """
163 return isinstance(arg_value, int)
164
167
169 """
170 Represents an argument of type :class:`long`.
171 """
173 return isinstance(arg_value, long)
174
177
179 """
180 Represents an argument of type :class:`float`.
181 """
183 return isinstance(arg_value, float)
184
187
189 """
190 Represents a real number argument, i.e. an argument of types :class:`int`,
191 :class:`long` or :class:`float`.
192 """
194 return isinstance(arg_value, int) or \
195 isinstance(arg_value, long) or \
196 isinstance(arg_value, float)
197
199 return "int', 'long' or 'float"
200
202 """
203 Represents a complex number argument of type :class:`complex`.
204 """
206 return isinstance(arg_value, complex)
207
210
212 """
213 Represents an iterable argument.
214 """
216 return hasattr(arg_value, "__iter__")
217
219 return "iterable (pseudo-type)"
220
222 """
223 Represents a subscriptable argument.
224 """
226 return hasattr(arg_value, "__getitem__")
227
229 return "subscriptable (pseudo-type)"
230
231 -class ListArg(IterableArg, SubscriptableArg):
232 """
233 Represents an argument of type :class:`list`.
234 """
236 return isinstance(arg_value, list)
237
240
241 -class DictArg(IterableArg, SubscriptableArg):
242 """
243 Represents an argument of type :class:`dict`.
244 """
245
247 return isinstance(arg_value, dict)
248
251
253 """
254 Represents an new-style class argument. The range of accepted objects can
255 be restricted by providing the ``arg_class`` keyword argument to the
256 constructor. Runtime validation will then check if the argument value is
257 an instance of ``arg_class`` (or one of its subclasses) and fail otherwise.
258 """
259
272
274 return isinstance(arg_value, self.arg_class)
275
277 return self.arg_class.__name__
278
280 """
281 Represents arbitrary positional arguments as supported by Python with
282 the ``method(self, *args)`` syntax. The range of accepted objects can
283 be restricted by providing the ``arg_class`` keyword argument to the
284 constructor. Runtime validation will then check if the argument value is
285 an instance of ``arg_class`` (or one of its subclasses) and fail otherwise.
286
287 Note that a :class:`PosArgs` argument declaration can only be followed by
288 a :class:`KwArgs` declaration, otherwise validation will fail.
289 """
291 self.name = name
292 self.optional = True
293 self.default = None
294
295 if "arg_class" in kwargs:
296 try:
297 if issubclass(kwargs["arg_class"], object):
298 self.arg_class = kwargs["arg_class"]
299 else:
300 raise InternalError("Argument class must be a new-style class.")
301 except:
302 raise InternalError("Argument class must be a new-style class.")
303 else:
304 self.arg_class = None
305
307 return self.arg_class is None or isinstance(arg_value, self.arg_class)
308
310 if self.arg_class is None:
311 return "any"
312 else:
313 return self.arg_class.__name__
314
316 """
317 Represents arbitrary keyword arguments as supported by Python with the
318 ``method(self, **kwargs)`` syntax. Note that this must always be the
319 last input argument declaration in a method, otherwise validation will fail.
320 """
322 self.name = name
323 self.optional = True
324 self.default = None
325
326
327
328
329
330 -class Method(object):
331 """
332 The :class:`Method` is used for method declarations in interfaces. Its
333 constructor accepts an arbitrary number of positional arguments representing
334 input arguments to the method to be defined, and one optional keyword
335 argument ``returns`` which represents the methods return value, if any.
336
337 All arguments must be instances of :class:`Arg` or one of its subclasses.
338
339 The methods of the :class:`Method` class are intended for internal use by
340 the :class:`Interface` validation algorithms only.
341 """
343 self.validateArgs(args)
344
345 self.named_args = []
346 self.pos_args = None
347 self.kwargs = None
348
349 for arg in args:
350 if isinstance(arg, PosArgs):
351 self.pos_args = arg
352 elif isinstance(arg, KwArgs):
353 self.kwargs = arg
354 else:
355 self.named_args.append(arg)
356
357 self.returns = kwargs.get("returns", None)
358
360 """
361 Validate the input arguments. That is, check if they are in the
362 right order and no argument is defined more than once. Raises
363 :exc:`~.InternalError` if the arguments do not validate.
364
365 Used internally by the constructor during instance creation.
366 """
367 opt_args_flag = False
368 pos_args_flag = False
369 kwargs_flag = False
370
371 names = []
372
373 for arg in args:
374 if not isinstance(arg, Arg):
375 raise InternalError("Method arguments must be instances of Arg.")
376
377 if opt_args_flag:
378 if not arg.isOptional():
379 raise InternalError("Mandatory arguments must precede optional arguments.")
380
381 if pos_args_flag:
382 if not isinstance(arg, KwArgs):
383 raise InternalError("Only keyword arguments may follow optional positional argument block.")
384
385 if kwargs_flag:
386 raise InternalError("No arguments allowed after keyword arguments block.")
387
388 if arg.isOptional():
389 opt_args_flag = True
390 if isinstance(arg, PosArgs):
391 pos_args_flag = True
392 elif isinstance(arg, KwArgs):
393 kwargs_flag = True
394
395 if arg.name in names:
396 raise InternalError("Argument named '%s' appears multiple times." % arg.name)
397 else:
398 names.append(arg.name)
399
401 """
402 This method is at implementation class creation time to check if the
403 implementing class method conforms to the method declaration. It expects
404 the corresponding method as its single input argument ``impl_method``.
405 It makes extensive use of Python's great introspection capabilities.
406
407 Raises :exc:`~.InternalError` in case the implementation does not
408 validate.
409 """
410
411 if len(self.named_args) != impl_method.func_code.co_argcount - 1:
412 raise InternalError("Number of arguments does not match")
413
414 for i in range(0, len(self.named_args)):
415 if self.named_args[i].name != impl_method.func_code.co_varnames[i+1]:
416 raise InternalError("Expected argument named '%s', got '%s'." % (self.named_args[i].name, impl_method.func_code.co_varnames[i+1]))
417
418 if self.pos_args is not None and impl_method.func_code.co_flags & 4 == 0:
419 raise InternalError("Expected positional argument block.")
420
421 if self.kwargs is not None and impl_method.func_code.co_flags & 8 == 0:
422 raise InternalError("Expected keyword argument block.")
423
425 """
426 This method is called for runtime argument type validation. It gets the
427 input of the implementing method and checks it against the argument
428 declarations.
429
430 Raises :exc:`~.TypeMismatch` if validation fails.
431 """
432
433
434
435
436 intf_named_args = {}
437
438 for arg in self.named_args:
439 intf_named_args[arg.name] = arg
440
441 impl_named_args = {}
442 impl_pos_args = []
443 impl_kwargs = {}
444
445 for i in range(0, min(len(self.named_args), len(args))):
446 impl_named_args[self.named_args[i].name] = (self.named_args[i], args[i])
447
448 if len(args) > len(self.named_args):
449 if self.pos_args is not None:
450 impl_pos_args = args[len(self.named_args):]
451 else:
452 self._raiseWrongNumberOfArguments(method_name, len(args))
453
454 for name, arg_value in kwargs.items():
455 if name in intf_named_args:
456 if name in impl_named_args:
457 raise TypeError("%s() got multiple values for keyword argument '%s'" % (
458 method_name,
459 name
460 ))
461 else:
462 impl_named_args[name] = (intf_named_args[name], arg_value)
463 else:
464 if self.kwargs is not None:
465 impl_kwargs[name] = arg_value
466 else:
467 raise TypeError("%s() got an unexpected keyword argument '%s'" % (
468 method_name,
469 name
470 ))
471
472 if len(impl_named_args) < len(filter(lambda arg: not arg.isOptional(), self.named_args)):
473 self._raiseWrongNumberOfArguments(method_name, len(impl_named_args), len(kwargs) > 0)
474
475
476
477 msgs = []
478
479 logger.debug("validateType(): start validation")
480
481 for arg, arg_value in impl_named_args.values():
482 if not arg.isValid(arg_value):
483 msgs.append("%s(): Invalid type for argument '%s'. Expected '%s', got '%s'." % (
484 method_name,
485 arg.name,
486 arg.getExpectedType(),
487 str(type(arg_value))
488 ))
489
490 if self.pos_args is not None:
491 if not self.pos_args.isValid(impl_pos_args):
492 msgs.append("%s(): Invalid type for positional arguments. Expected '%s' only." % (
493 method_name,
494 self.pos_args.getExpectedType()
495 ))
496
497 logger.debug("validateType(): finish validation")
498
499 if len(msgs) > 0:
500 raise TypeMismatch("\n".join(msgs))
501
503 """
504 This method is called for runtime argument type validation. It expects
505 the method name ``method_name`` and the return value ``ret_value`` as
506 input and checks the return value against the return value declaration,
507 if any.
508
509 Raises :exc:`~.TypeMismatch` if validation fails.
510 """
511
512 if self.returns is not None and \
513 not self.returns.isValid(ret_value):
514 raise TypeMismatch("%s(): Invalid return type. Expected '%s', got '%s'" % (
515 method_name,
516 self.returns.getExpectedType(),
517 str(type(ret_value))
518 ))
519
521 if any(map(lambda arg : arg.isOptional(), self.named_args)):
522 if argcount > len(self.named_args):
523 adverb = "at most"
524 count = len(self.named_args)
525 else:
526 adverb = "at least"
527 count = len(filter(lambda arg : not arg.isOptional(), self.named_args))
528 else:
529 adverb = "exactly"
530 count = len(self.named_args)
531
532 if kwargs_present:
533 prefix = "non-keyword "
534 else:
535 prefix = ""
536
537 raise TypeError("%s() takes %s %d %sarguments (%d given)" % (
538 method_name,
539 adverb,
540 count,
541 prefix,
542 argcount
543 ))
544
573
575 """
576 This is the base class for all interface declarations. Derive from it or
577 one of its subclasses to create your own interface declaration.
578
579 The :class:`Interface` class has only class variables (the method
580 declarations) and class methods.
581 """
582
583 __metaclass__ = InterfaceMetaClass
584
585 @classmethod
586 - def implement(InterfaceCls, ImplementationCls):
587 """
588 This method takes an implementing class as input, validates it, and
589 returns the implementation.
590
591 In the validation step, :meth:`Method.validateImplementation`
592 is called for each method declared in the interface.
593 :exc:`~.InternalError` is raised if a method is not found or if
594 the method signature does not match the declaration.
595
596 If validation has passed, the implementation is getting prepared. The
597 implementation inherits from the implementing class. The ``__ifclass__``
598 magic attribute is added to the class dictionary. If runtime
599 validation has been enabled, the methods of the implementing class
600 defined in the interface are replaced by descriptors (instances of
601 :class:`WarningDescriptor` or :class:`FailingDescriptor`).
602
603 Finally, the implementation class is generated and returned.
604 """
605
606 name = InterfaceCls._getName(ImplementationCls)
607 bases = InterfaceCls._getBases(ImplementationCls)
608
609 InterfaceCls._validateImplementation(bases)
610
611 class_dict = InterfaceCls._getClassDict(ImplementationCls, bases)
612
613 return type(name, bases, class_dict)
614
615 @classmethod
616 - def _getName(InterfaceCls, ImplementationCls):
617 return "_Impl_%s_%s" % (ImplementationCls.__module__.replace(".", "_"), ImplementationCls.__name__)
618
619 @classmethod
620 - def _getBases(InterfaceCls, ImplementationCls):
621 bases = (ImplementationCls,)
622
623 return bases
624
625 @classmethod
627
628
629
630
631 runtime_validation_level = InterfaceCls._getRuntimeValidationLevel(ImplementationCls)
632
633 if runtime_validation_level.lower() == "trust":
634 class_dict = {}
635
636 elif runtime_validation_level.lower() == "warn":
637 class_dict = {}
638
639 interface_methods = InterfaceCls._getMethods()
640
641 for name, method in interface_methods.items():
642 func = InterfaceCls._getBaseMethod(name, bases)
643 class_dict[name] = WarningDescriptor(method, func)
644
645 elif runtime_validation_level.lower() == "fail":
646 class_dict = {}
647
648 interface_methods = InterfaceCls._getMethods()
649
650 logger.debug("Interface._getClassDict(): Interface Methods: %s" % str(interface_methods))
651
652 for name, method in interface_methods.items():
653 func = InterfaceCls._getBaseMethod(name, bases)
654 class_dict[name] = FailingDescriptor(method, func)
655
656 else:
657 class_dict = {}
658
659 class_dict.update({"__ifclass__": InterfaceCls})
660
661 return class_dict
662
663 @classmethod
665 interface_methods = InterfaceCls._getMethods()
666
667 implementation_dict = {}
668 for base in reversed(bases):
669 for cls in reversed(base.__mro__):
670 for name, attr in cls.__dict__.items():
671 if type(attr) is types.FunctionType:
672 implementation_dict[name] = attr
673
674 for name, method in interface_methods.items():
675 if name in implementation_dict:
676 method.validateImplementation(implementation_dict[name])
677 else:
678 raise InternalError("Method '%s' not found in implementation." % name)
679
680 @classmethod
682 interface_methods = {}
683
684 for name, attr in InterfaceCls.__dict__.items():
685 if isinstance(attr, Method):
686 interface_methods[name] = attr
687
688 for InterfaceBase in reversed(InterfaceCls.__mro__):
689 for name, attr in InterfaceBase.__dict__.items():
690 if isinstance(attr, Method):
691 interface_methods[name] = attr
692
693 return interface_methods
694
695 @classmethod
697 for base in bases:
698 for cls in base.__mro__:
699 if method_name in cls.__dict__:
700 return cls.__dict__[method_name]
701
702 raise InternalError("Base method %s() not found." % method_name)
703
704 @classmethod
706 global_level = RUNTIME_VALIDATION_LEVEL.lower()
707 intf_level = InterfaceCls.__iconf__.get("runtime_validation_level")
708 if "IMPL_CONF" in ImplementationCls.__dict__:
709 impl_level = ImplementationCls.IMPL_CONF.get("runtime_validation_level")
710 else:
711 impl_level = None
712
713 levels = (global_level, intf_level, impl_level)
714
715 if "fail" in levels:
716 return "fail"
717 elif "warn" in levels:
718 return "warn"
719 else:
720 return "trust"
721
727 """
728 This is the common base class for :class:`WarningDescriptor` and
729 :class:`FailingDescriptor`. The constructor expects the method declaration
730 ``method`` and the implementing function ``func`` as input.
731
732 The :meth:`__get__` method returns a callable wrapper around the
733 instance it is called with, the method declaration and the function that
734 implements the method. It is that object
735 that gets finally invoked when runtime validation is enabled.
736 """
737
739 self.method = method
740 self.func = func
741
742 - def __get__(self, instance, owner):
747
748 - def _wrap(self, instance):
750
752 """
753 This is the common base class for :class:`WarningWrapper` and
754 :class:`FailingWrapper`. Its constructor expects the method declaration,
755 the implementing function and the instance as input.
756 """
757 - def __init__(self, method, func, instance):
761
763 - def _wrap(self, instance):
765
767 """
768 This wrapper is callable. Its :meth:`__call__` method expects arbitrary
769 positional and keyword arguments, validates them against the method
770 declaration using :meth:`Method.validateType`, calls the implementing
771 function with these arguments and returns whatever it returns, calling
772 :meth:`Method.validateReturnType`.
773
774 If the validation methods raise a :exc:`~.TypeMismatch` exception the
775 exception text is logged as a warning, but the normal process of execution
776 goes on.
777 """
792
794 - def _wrap(self, instance):
796
798 """
799 This wrapper is callable. Its :meth:`__call__` method expects arbitrary
800 positional and keyword arguments, validates them against the method
801 declaration using :meth:`Method.validateType`, calls the implementing
802 function with these arguments and returns whatever it returns, calling
803 :meth:`Method.validateReturnType`.
804
805 If the validation methods raise a :exc:`~.TypeMismatch` exception it will
806 not be caught and thus cause the program to fail.
807 """
816
822 """
823 This is the configuration reader for :mod:`eoxserver.core.interfaces`.
824
825 Its constructor expects a :class:`Config` instance ``config`` as input.
826 """
827
830
832 """
833 Validates the configuration. Raises :exc:`~.ConfigError` if the
834 ``runtime_validation_level`` configuration setting in the
835 ``core.interfaces`` section contains an invalid value.
836 """
837 value = self.getRuntimeValidationLevel()
838
839 if value and value not in ("trust", "warn", "fail"):
840 raise ConfigError("'runtime_validation_level' parameter must be one of: 'trust', 'warn' or 'fail'.")
841
843 """
844 Returns the global runtime validation level setting or ``None`` if it
845 is not defined.
846 """
847 self.config.getConfigValue("core.interfaces", "runtime_validation_level")
848