Search code examples
pythonabstract-classpython-typing

Type checking against dynamically created objects


I want to perform some dynamic type checking for the inputs and outputs of a method. The solution that I've came up with is as follows. Basically, in the __init__ function of the superclass, I update the subclass method with some dynamic type checking for both input and output. Note that, the type-checking should be based on the attributes (AlgorithmAttribute) that user defined during instantiation. I wonder if there is any better solution out there.

import abc
import six
import attr
from typing import Dict, Text

@attr.s
class AlgorithmAttribute(object):
  inputs = attr.ib(type=Dict[Text, int])
  outputs = attr.ib(type=Dict[Text, int])

class BaseHandler(six.with_metaclass(abc.ABCMeta, object)):
  def __init__(self, alg):
    self.alg = alg
    self.out = self.__getattribute__('prepare')
    self.__setattr__('prepare', self.temp)

  def temp(self, a, b):
    result = self.out(a, b)
    # Verifies inputs.
    if set(a.keys()) == set(self.alg.inputs.keys()):
      print('Inputs correct!')
    # Verifies outputs.
    if set(result.keys()) == set(self.alg.outputs.keys()):
      print('Outputs correct!')
    return result

  @abc.abstractmethod
  def prepare(self, a):
    pass


class SubHandler(BaseHandler):
    def __init__(self, alg):
      super(SubHandler, self).__init__(alg)

    def prepare(self, a:Dict[Text, int], b:Dict[Text, int]) -> Dict[Text, int]:
        return {'c': 33}


alg = AlgorithmAttribute(inputs={'a': 2, 'b': 3}, outputs={'c': 10})
sh = SubHandler(alg)
sh.prepare({'a': 20, 'b': 200}, b={'c': 20, 'd': 200})

Solution

  • It's not very clear to me why you validate the first argument of prepare() against inputs or what's the point of passing a dict with unused values to AlgorithmAttribute, but a more Pythonic way, in my opinion, would be to ditch the classes and use a decorator:

    from dataclasses import dataclass
    from typing import Dict, Text
    
    @dataclass
    class AlgorithmAttribute(object):
        inputs: Dict[Text, int]
        outputs: Dict[Text, int]
    
    def validate_inputs_outputs(validation_params: AlgorithmAttribute):
        def wrap(func):
            def wrapped_func(*args, **kwargs):
                result = func(*args, **kwargs)
                # Verifies inputs.
                if set(args[0].keys()) == set(validation_params.inputs.keys()):
                    print('Inputs correct!')
                # Verifies outputs.
                if set(result.keys()) == set(validation_params.outputs.keys()):
                    print('Outputs correct!')
                return result
            return wrapped_func
        return wrap
    
    @validate_inputs_outputs(AlgorithmAttribute(inputs={'a': 2, 'b': 3}, outputs={'c': 10}))
    def prepare(a:Dict[Text, int], b:Dict[Text, int]) -> Dict[Text, int]:
        return {'c': 33}
    
    prepare({'a': 20, 'b': 200}, b={'c': 20, 'd': 200})