Python Type System












0












$begingroup$


I am trying to create a custom type system in Python.



from collections import Iterable
import six

class Field(object):
"""
Specification of a field
"""

def __init__(self,
prop_type,
required=False,
repeated=False,
choices=None,
default=None,
validators=None,
alias=None,
converter=None):
self._type = prop_type
self._required = required
self._repeated = repeated
if choices is not None:
if not isinstance(choices, Iterable):
raise IllegalTypeError("Expected iterable got '%s' (%r)" % (type(choices), choices))
choices = frozenset(choices)
self._choices = choices
self._default = default
self._validators = validators or ()
self._alias = alias or None
self._converter = converter

def is_required(self):
return self._required

def is_repeated(self):
return self._repeated

# Workaround for not having __set_name__ hook in Python versions before 3.6
def set_name(self, name):
"""
Upon class creation, this method is called on all the attribute defined in the class with name of the attribute.
"""
self._name = name

def _convert_value(self, value):
# run converter only if value is not already of defined type
value_to_store = value
if self._converter:
if self.is_repeated() and isinstance(value_to_store, (list, set)):
value_to_store = [
self._converter(val) if (not isinstance(val, self._dtype())) else val for val in value_to_store]
elif not isinstance(value_to_store, self._dtype()):
value_to_store = self._converter(value_to_store)
return value_to_store

def _store_value(self, owner, value):
"""
Owner is the object that holds the corresponding field
"""
value_to_store = value
if value_to_store:
value_to_store = self._convert_value(value_to_store)
if self._repeated:
if isinstance(value_to_store, (list, set)):
value_to_store = [self._run_validators(item) for item in value_to_store]
else:
value_to_store = [self._run_validators(value_to_store)]
else:
value_to_store = self._run_validators(value_to_store)
owner._values[self._name] = value_to_store

def _retrieve_value(self, owner):
return owner._values.get(self._name)

def _run_validators(self, value):
if value:
self.check_type(value)
self.check_choices(value)
for validator in self._validators:
validator(value)
# validator should raise the exception if value is invalid
return value

def _get_for_dict(self, owner):
return self._retrieve_value(owner)

def check_type(self, value):
if not isinstance(value, self._dtype()):
raise IllegalTypeError("Expected '%s' got '%s' (%r) for the field %s" % (self._dtype(),
type(value),
value,
self._name))

def check_choices(self, value):
if self._choices is not None:
if value not in self._choices:
raise IllegalChoiceError("Value provided is outside the set of choices for the field : %s" % self._name)

def _check_required_field(self, value):
return self._required and value is None

def _dtype(self):
return self._type

def __get__(self, owner, value):
if owner is None:
return self
return self._retrieve_value(owner)

def __set__(self, owner, value):
self._store_value(owner, value)


class ComplexField(Field):
"""
Specification of a complex field
"""
def __init__(self, model_class, **kwargs):
super(ComplexField, self).__init__(model_class, **kwargs)
self._model_class = model_class

def __getattr__(self, name):
return self._model_class._properties.get(name)

def _get_for_dict(self, owner):
value = self._retrieve_value(owner)
if self._repeated:
value = [item._to_dict() for item in value]
else:
value = value._todict()
return value


class BaseModel(type):
""" """

def __init__(cls, name, bases, classdict):
def make_default(field, value):
def default(self):
if callable(value):
return value(self)
return value
setattr(cls, 'default_%s' % field, default)
super(BaseModel, cls).__init__(name, bases, classdict)
cls._properties = {}
cls._alias_to_property_map = {}
for name in set(dir(cls)):
attr = getattr(cls, name, None)
if isinstance(attr, Field):
attr.set_name(name)
cls._properties[attr._name] = attr
if not getattr(cls, 'default_%s' % attr._name, None):
make_default(attr._name, attr._default)
# Add alias to field class map
if attr._alias:
cls._alias_to_property_map[attr._alias] = attr._name

def __repr__(cls):
props =
for _, prop in sorted(cls._properties.items()):
props.append('%s=%r' % (prop._name, prop))
return '%s<%s>' % (cls.__name__, ', '.join(props))


@six.add_metaclass(BaseModel)
class Model(object):
""" """

def __init__(self, **kwds):
# add checks for required fields
self._values = {}
self._set_attributes(kwds)
self._populate()
self.__post_init__()

def _set_attributes(self, kwds):
cls = self.__class__
for name, value in kwds.items():
prop = getattr(cls, name)
if not isinstance(prop, Field):
raise IllegalTypeError('Cannot set non-field "%s" value' % name)
prop._store_value(self, value)

def _to_dict(self):
values = {}
for _, prop in self._properties.items():
name = prop._name
value = prop._get_for_dict(self)
values[name] = value
return values

asdict = _to_dict

def _populate(self):
for name, prop in self._properties.items():
if getattr(self, name) is None:
prop._store_value(self, getattr(self, "default_%s" % name)())

def __post_init__(self):
pass

def _check_required_fields(self):
pass

def __iter__(self):
values = self._to_dict()
for key, value in values.items():
yield (key, value)

def __getitem__(self, key):
return getattr(self, key)

def __setitem__(self, key, value):
cls = self.__class__
prop = getattr(cls, key)
if not isinstance(prop, Field):
raise IllegalTypeError('Cannot set non-field "%s" value' % key)
return setattr(self, key, value)

def __len__(self):
return 1

def __post__init__(self):
pass

@classmethod
def create_object_from_dict(cls, input_dict):
""" """

# Step 1: Validate
# Check if all dictionary fields are defined fields
# Check if all required fields are present
# Convert the aliases
# Execute converters

current_fields = cls.validate_and_convert_input_dictionary(input_dict)
for key, value in current_fields.items():
field_class = cls.get_field_class(key)
is_repeated = cls.is_field_repeated(key)

if not cls.is_domain_class(field_class):
val = cls.create_simple_field(value, field_class, is_repeated)

elif isinstance(value, dict):
val = cls.create_complex_field_from_dict(field_class, value)

elif isinstance(value, list):
# currently list is assumed to be list of dicts this will be changed accordingly
val = [cls.create_complex_field_from_dict(field_class, v) for v in value]

else:
raise IllegalTypeError('Improper type of value for field %s' % key)

current_fields[key] = val

return cls(**current_fields)

@classmethod
def is_field_repeated(cls, field_name):
return cls._properties[field_name].is_repeated()

@classmethod
def create_simple_field(cls, value, field_class, is_repeated):
val = None
# if we have multiple arguments unwrap it and pass to the constructor
if isinstance(value, dict):
val = field_class(**value)
elif is_repeated:
if isinstance(value, (list, tuple, set)):
val = [field_class(v) for v in value]
else:
val = [field_class(value)]
else:
if isinstance(value, (list, tuple, set)):
val = field_class(*value)
else:
val = field_class(value)
return val

@classmethod
def get_field_class(cls, field_name):
# first we check if class is present in the field class map
class_map = getattr(cls, "field_class_map", None)
if class_map and field_name in class_map:
return class_map[field_name]
return cls._properties[field_name]._type

@classmethod
def check_if_all_fields_valid(cls, input_dict): # checks if all the fields defined are already in cls definition
return all([k in cls._properties for k, v in input_dict.items()])

@classmethod
def check_if_all_required_fields_present(cls, input_dict):
return all([k2 in input_dict for k2 in [k for k, v in cls._properties.items() if v.is_required()]])

@classmethod
def create_complex_field_from_dict(cls, field_class, input_dict):
return field_class.create_object_from_dict(input_dict)

@classmethod
def is_domain_class(cls, field_class):
return issubclass(field_class, Model)

@classmethod
def convert_alias(cls, input_dict):
return {
cls._alias_to_property_map[k] if k in cls._alias_to_property_map else k: v for k, v in input_dict.items()}

@classmethod
def apply_converters(cls, input_dict):
current_dict = {}
for key, val in input_dict.items():
if val:
val = cls._properties[key]._convert_value(val)
current_dict[key] = val
return current_dict

@classmethod
def validate_and_convert_input_dictionary(cls, input_dict):
current_dict = cls.convert_alias(input_dict)

if not cls.check_if_all_fields_valid(current_dict):
raise IllegalFieldError("Fields present are outside the fields allowed")

if not cls.check_if_all_required_fields_present(current_dict):
raise RequiredFieldNotPresentError("All required fields are not present in the dictionary ")

return cls.apply_converters(current_dict)


# Example field classes
class JobSpec(Model):
job_name = Field(str, default=None)
command_spec = Field(CommandSpec, repeated = True, default=None)

class CommandSpec(Mode):
command_name = Field(str, default=None)


I'd like general comments on how I could improve the coding style. If I am reinventing the wheel, I am using bad coding practices etc.









share







New contributor




Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.







$endgroup$

















    0












    $begingroup$


    I am trying to create a custom type system in Python.



    from collections import Iterable
    import six

    class Field(object):
    """
    Specification of a field
    """

    def __init__(self,
    prop_type,
    required=False,
    repeated=False,
    choices=None,
    default=None,
    validators=None,
    alias=None,
    converter=None):
    self._type = prop_type
    self._required = required
    self._repeated = repeated
    if choices is not None:
    if not isinstance(choices, Iterable):
    raise IllegalTypeError("Expected iterable got '%s' (%r)" % (type(choices), choices))
    choices = frozenset(choices)
    self._choices = choices
    self._default = default
    self._validators = validators or ()
    self._alias = alias or None
    self._converter = converter

    def is_required(self):
    return self._required

    def is_repeated(self):
    return self._repeated

    # Workaround for not having __set_name__ hook in Python versions before 3.6
    def set_name(self, name):
    """
    Upon class creation, this method is called on all the attribute defined in the class with name of the attribute.
    """
    self._name = name

    def _convert_value(self, value):
    # run converter only if value is not already of defined type
    value_to_store = value
    if self._converter:
    if self.is_repeated() and isinstance(value_to_store, (list, set)):
    value_to_store = [
    self._converter(val) if (not isinstance(val, self._dtype())) else val for val in value_to_store]
    elif not isinstance(value_to_store, self._dtype()):
    value_to_store = self._converter(value_to_store)
    return value_to_store

    def _store_value(self, owner, value):
    """
    Owner is the object that holds the corresponding field
    """
    value_to_store = value
    if value_to_store:
    value_to_store = self._convert_value(value_to_store)
    if self._repeated:
    if isinstance(value_to_store, (list, set)):
    value_to_store = [self._run_validators(item) for item in value_to_store]
    else:
    value_to_store = [self._run_validators(value_to_store)]
    else:
    value_to_store = self._run_validators(value_to_store)
    owner._values[self._name] = value_to_store

    def _retrieve_value(self, owner):
    return owner._values.get(self._name)

    def _run_validators(self, value):
    if value:
    self.check_type(value)
    self.check_choices(value)
    for validator in self._validators:
    validator(value)
    # validator should raise the exception if value is invalid
    return value

    def _get_for_dict(self, owner):
    return self._retrieve_value(owner)

    def check_type(self, value):
    if not isinstance(value, self._dtype()):
    raise IllegalTypeError("Expected '%s' got '%s' (%r) for the field %s" % (self._dtype(),
    type(value),
    value,
    self._name))

    def check_choices(self, value):
    if self._choices is not None:
    if value not in self._choices:
    raise IllegalChoiceError("Value provided is outside the set of choices for the field : %s" % self._name)

    def _check_required_field(self, value):
    return self._required and value is None

    def _dtype(self):
    return self._type

    def __get__(self, owner, value):
    if owner is None:
    return self
    return self._retrieve_value(owner)

    def __set__(self, owner, value):
    self._store_value(owner, value)


    class ComplexField(Field):
    """
    Specification of a complex field
    """
    def __init__(self, model_class, **kwargs):
    super(ComplexField, self).__init__(model_class, **kwargs)
    self._model_class = model_class

    def __getattr__(self, name):
    return self._model_class._properties.get(name)

    def _get_for_dict(self, owner):
    value = self._retrieve_value(owner)
    if self._repeated:
    value = [item._to_dict() for item in value]
    else:
    value = value._todict()
    return value


    class BaseModel(type):
    """ """

    def __init__(cls, name, bases, classdict):
    def make_default(field, value):
    def default(self):
    if callable(value):
    return value(self)
    return value
    setattr(cls, 'default_%s' % field, default)
    super(BaseModel, cls).__init__(name, bases, classdict)
    cls._properties = {}
    cls._alias_to_property_map = {}
    for name in set(dir(cls)):
    attr = getattr(cls, name, None)
    if isinstance(attr, Field):
    attr.set_name(name)
    cls._properties[attr._name] = attr
    if not getattr(cls, 'default_%s' % attr._name, None):
    make_default(attr._name, attr._default)
    # Add alias to field class map
    if attr._alias:
    cls._alias_to_property_map[attr._alias] = attr._name

    def __repr__(cls):
    props =
    for _, prop in sorted(cls._properties.items()):
    props.append('%s=%r' % (prop._name, prop))
    return '%s<%s>' % (cls.__name__, ', '.join(props))


    @six.add_metaclass(BaseModel)
    class Model(object):
    """ """

    def __init__(self, **kwds):
    # add checks for required fields
    self._values = {}
    self._set_attributes(kwds)
    self._populate()
    self.__post_init__()

    def _set_attributes(self, kwds):
    cls = self.__class__
    for name, value in kwds.items():
    prop = getattr(cls, name)
    if not isinstance(prop, Field):
    raise IllegalTypeError('Cannot set non-field "%s" value' % name)
    prop._store_value(self, value)

    def _to_dict(self):
    values = {}
    for _, prop in self._properties.items():
    name = prop._name
    value = prop._get_for_dict(self)
    values[name] = value
    return values

    asdict = _to_dict

    def _populate(self):
    for name, prop in self._properties.items():
    if getattr(self, name) is None:
    prop._store_value(self, getattr(self, "default_%s" % name)())

    def __post_init__(self):
    pass

    def _check_required_fields(self):
    pass

    def __iter__(self):
    values = self._to_dict()
    for key, value in values.items():
    yield (key, value)

    def __getitem__(self, key):
    return getattr(self, key)

    def __setitem__(self, key, value):
    cls = self.__class__
    prop = getattr(cls, key)
    if not isinstance(prop, Field):
    raise IllegalTypeError('Cannot set non-field "%s" value' % key)
    return setattr(self, key, value)

    def __len__(self):
    return 1

    def __post__init__(self):
    pass

    @classmethod
    def create_object_from_dict(cls, input_dict):
    """ """

    # Step 1: Validate
    # Check if all dictionary fields are defined fields
    # Check if all required fields are present
    # Convert the aliases
    # Execute converters

    current_fields = cls.validate_and_convert_input_dictionary(input_dict)
    for key, value in current_fields.items():
    field_class = cls.get_field_class(key)
    is_repeated = cls.is_field_repeated(key)

    if not cls.is_domain_class(field_class):
    val = cls.create_simple_field(value, field_class, is_repeated)

    elif isinstance(value, dict):
    val = cls.create_complex_field_from_dict(field_class, value)

    elif isinstance(value, list):
    # currently list is assumed to be list of dicts this will be changed accordingly
    val = [cls.create_complex_field_from_dict(field_class, v) for v in value]

    else:
    raise IllegalTypeError('Improper type of value for field %s' % key)

    current_fields[key] = val

    return cls(**current_fields)

    @classmethod
    def is_field_repeated(cls, field_name):
    return cls._properties[field_name].is_repeated()

    @classmethod
    def create_simple_field(cls, value, field_class, is_repeated):
    val = None
    # if we have multiple arguments unwrap it and pass to the constructor
    if isinstance(value, dict):
    val = field_class(**value)
    elif is_repeated:
    if isinstance(value, (list, tuple, set)):
    val = [field_class(v) for v in value]
    else:
    val = [field_class(value)]
    else:
    if isinstance(value, (list, tuple, set)):
    val = field_class(*value)
    else:
    val = field_class(value)
    return val

    @classmethod
    def get_field_class(cls, field_name):
    # first we check if class is present in the field class map
    class_map = getattr(cls, "field_class_map", None)
    if class_map and field_name in class_map:
    return class_map[field_name]
    return cls._properties[field_name]._type

    @classmethod
    def check_if_all_fields_valid(cls, input_dict): # checks if all the fields defined are already in cls definition
    return all([k in cls._properties for k, v in input_dict.items()])

    @classmethod
    def check_if_all_required_fields_present(cls, input_dict):
    return all([k2 in input_dict for k2 in [k for k, v in cls._properties.items() if v.is_required()]])

    @classmethod
    def create_complex_field_from_dict(cls, field_class, input_dict):
    return field_class.create_object_from_dict(input_dict)

    @classmethod
    def is_domain_class(cls, field_class):
    return issubclass(field_class, Model)

    @classmethod
    def convert_alias(cls, input_dict):
    return {
    cls._alias_to_property_map[k] if k in cls._alias_to_property_map else k: v for k, v in input_dict.items()}

    @classmethod
    def apply_converters(cls, input_dict):
    current_dict = {}
    for key, val in input_dict.items():
    if val:
    val = cls._properties[key]._convert_value(val)
    current_dict[key] = val
    return current_dict

    @classmethod
    def validate_and_convert_input_dictionary(cls, input_dict):
    current_dict = cls.convert_alias(input_dict)

    if not cls.check_if_all_fields_valid(current_dict):
    raise IllegalFieldError("Fields present are outside the fields allowed")

    if not cls.check_if_all_required_fields_present(current_dict):
    raise RequiredFieldNotPresentError("All required fields are not present in the dictionary ")

    return cls.apply_converters(current_dict)


    # Example field classes
    class JobSpec(Model):
    job_name = Field(str, default=None)
    command_spec = Field(CommandSpec, repeated = True, default=None)

    class CommandSpec(Mode):
    command_name = Field(str, default=None)


    I'd like general comments on how I could improve the coding style. If I am reinventing the wheel, I am using bad coding practices etc.









    share







    New contributor




    Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
    Check out our Code of Conduct.







    $endgroup$















      0












      0








      0





      $begingroup$


      I am trying to create a custom type system in Python.



      from collections import Iterable
      import six

      class Field(object):
      """
      Specification of a field
      """

      def __init__(self,
      prop_type,
      required=False,
      repeated=False,
      choices=None,
      default=None,
      validators=None,
      alias=None,
      converter=None):
      self._type = prop_type
      self._required = required
      self._repeated = repeated
      if choices is not None:
      if not isinstance(choices, Iterable):
      raise IllegalTypeError("Expected iterable got '%s' (%r)" % (type(choices), choices))
      choices = frozenset(choices)
      self._choices = choices
      self._default = default
      self._validators = validators or ()
      self._alias = alias or None
      self._converter = converter

      def is_required(self):
      return self._required

      def is_repeated(self):
      return self._repeated

      # Workaround for not having __set_name__ hook in Python versions before 3.6
      def set_name(self, name):
      """
      Upon class creation, this method is called on all the attribute defined in the class with name of the attribute.
      """
      self._name = name

      def _convert_value(self, value):
      # run converter only if value is not already of defined type
      value_to_store = value
      if self._converter:
      if self.is_repeated() and isinstance(value_to_store, (list, set)):
      value_to_store = [
      self._converter(val) if (not isinstance(val, self._dtype())) else val for val in value_to_store]
      elif not isinstance(value_to_store, self._dtype()):
      value_to_store = self._converter(value_to_store)
      return value_to_store

      def _store_value(self, owner, value):
      """
      Owner is the object that holds the corresponding field
      """
      value_to_store = value
      if value_to_store:
      value_to_store = self._convert_value(value_to_store)
      if self._repeated:
      if isinstance(value_to_store, (list, set)):
      value_to_store = [self._run_validators(item) for item in value_to_store]
      else:
      value_to_store = [self._run_validators(value_to_store)]
      else:
      value_to_store = self._run_validators(value_to_store)
      owner._values[self._name] = value_to_store

      def _retrieve_value(self, owner):
      return owner._values.get(self._name)

      def _run_validators(self, value):
      if value:
      self.check_type(value)
      self.check_choices(value)
      for validator in self._validators:
      validator(value)
      # validator should raise the exception if value is invalid
      return value

      def _get_for_dict(self, owner):
      return self._retrieve_value(owner)

      def check_type(self, value):
      if not isinstance(value, self._dtype()):
      raise IllegalTypeError("Expected '%s' got '%s' (%r) for the field %s" % (self._dtype(),
      type(value),
      value,
      self._name))

      def check_choices(self, value):
      if self._choices is not None:
      if value not in self._choices:
      raise IllegalChoiceError("Value provided is outside the set of choices for the field : %s" % self._name)

      def _check_required_field(self, value):
      return self._required and value is None

      def _dtype(self):
      return self._type

      def __get__(self, owner, value):
      if owner is None:
      return self
      return self._retrieve_value(owner)

      def __set__(self, owner, value):
      self._store_value(owner, value)


      class ComplexField(Field):
      """
      Specification of a complex field
      """
      def __init__(self, model_class, **kwargs):
      super(ComplexField, self).__init__(model_class, **kwargs)
      self._model_class = model_class

      def __getattr__(self, name):
      return self._model_class._properties.get(name)

      def _get_for_dict(self, owner):
      value = self._retrieve_value(owner)
      if self._repeated:
      value = [item._to_dict() for item in value]
      else:
      value = value._todict()
      return value


      class BaseModel(type):
      """ """

      def __init__(cls, name, bases, classdict):
      def make_default(field, value):
      def default(self):
      if callable(value):
      return value(self)
      return value
      setattr(cls, 'default_%s' % field, default)
      super(BaseModel, cls).__init__(name, bases, classdict)
      cls._properties = {}
      cls._alias_to_property_map = {}
      for name in set(dir(cls)):
      attr = getattr(cls, name, None)
      if isinstance(attr, Field):
      attr.set_name(name)
      cls._properties[attr._name] = attr
      if not getattr(cls, 'default_%s' % attr._name, None):
      make_default(attr._name, attr._default)
      # Add alias to field class map
      if attr._alias:
      cls._alias_to_property_map[attr._alias] = attr._name

      def __repr__(cls):
      props =
      for _, prop in sorted(cls._properties.items()):
      props.append('%s=%r' % (prop._name, prop))
      return '%s<%s>' % (cls.__name__, ', '.join(props))


      @six.add_metaclass(BaseModel)
      class Model(object):
      """ """

      def __init__(self, **kwds):
      # add checks for required fields
      self._values = {}
      self._set_attributes(kwds)
      self._populate()
      self.__post_init__()

      def _set_attributes(self, kwds):
      cls = self.__class__
      for name, value in kwds.items():
      prop = getattr(cls, name)
      if not isinstance(prop, Field):
      raise IllegalTypeError('Cannot set non-field "%s" value' % name)
      prop._store_value(self, value)

      def _to_dict(self):
      values = {}
      for _, prop in self._properties.items():
      name = prop._name
      value = prop._get_for_dict(self)
      values[name] = value
      return values

      asdict = _to_dict

      def _populate(self):
      for name, prop in self._properties.items():
      if getattr(self, name) is None:
      prop._store_value(self, getattr(self, "default_%s" % name)())

      def __post_init__(self):
      pass

      def _check_required_fields(self):
      pass

      def __iter__(self):
      values = self._to_dict()
      for key, value in values.items():
      yield (key, value)

      def __getitem__(self, key):
      return getattr(self, key)

      def __setitem__(self, key, value):
      cls = self.__class__
      prop = getattr(cls, key)
      if not isinstance(prop, Field):
      raise IllegalTypeError('Cannot set non-field "%s" value' % key)
      return setattr(self, key, value)

      def __len__(self):
      return 1

      def __post__init__(self):
      pass

      @classmethod
      def create_object_from_dict(cls, input_dict):
      """ """

      # Step 1: Validate
      # Check if all dictionary fields are defined fields
      # Check if all required fields are present
      # Convert the aliases
      # Execute converters

      current_fields = cls.validate_and_convert_input_dictionary(input_dict)
      for key, value in current_fields.items():
      field_class = cls.get_field_class(key)
      is_repeated = cls.is_field_repeated(key)

      if not cls.is_domain_class(field_class):
      val = cls.create_simple_field(value, field_class, is_repeated)

      elif isinstance(value, dict):
      val = cls.create_complex_field_from_dict(field_class, value)

      elif isinstance(value, list):
      # currently list is assumed to be list of dicts this will be changed accordingly
      val = [cls.create_complex_field_from_dict(field_class, v) for v in value]

      else:
      raise IllegalTypeError('Improper type of value for field %s' % key)

      current_fields[key] = val

      return cls(**current_fields)

      @classmethod
      def is_field_repeated(cls, field_name):
      return cls._properties[field_name].is_repeated()

      @classmethod
      def create_simple_field(cls, value, field_class, is_repeated):
      val = None
      # if we have multiple arguments unwrap it and pass to the constructor
      if isinstance(value, dict):
      val = field_class(**value)
      elif is_repeated:
      if isinstance(value, (list, tuple, set)):
      val = [field_class(v) for v in value]
      else:
      val = [field_class(value)]
      else:
      if isinstance(value, (list, tuple, set)):
      val = field_class(*value)
      else:
      val = field_class(value)
      return val

      @classmethod
      def get_field_class(cls, field_name):
      # first we check if class is present in the field class map
      class_map = getattr(cls, "field_class_map", None)
      if class_map and field_name in class_map:
      return class_map[field_name]
      return cls._properties[field_name]._type

      @classmethod
      def check_if_all_fields_valid(cls, input_dict): # checks if all the fields defined are already in cls definition
      return all([k in cls._properties for k, v in input_dict.items()])

      @classmethod
      def check_if_all_required_fields_present(cls, input_dict):
      return all([k2 in input_dict for k2 in [k for k, v in cls._properties.items() if v.is_required()]])

      @classmethod
      def create_complex_field_from_dict(cls, field_class, input_dict):
      return field_class.create_object_from_dict(input_dict)

      @classmethod
      def is_domain_class(cls, field_class):
      return issubclass(field_class, Model)

      @classmethod
      def convert_alias(cls, input_dict):
      return {
      cls._alias_to_property_map[k] if k in cls._alias_to_property_map else k: v for k, v in input_dict.items()}

      @classmethod
      def apply_converters(cls, input_dict):
      current_dict = {}
      for key, val in input_dict.items():
      if val:
      val = cls._properties[key]._convert_value(val)
      current_dict[key] = val
      return current_dict

      @classmethod
      def validate_and_convert_input_dictionary(cls, input_dict):
      current_dict = cls.convert_alias(input_dict)

      if not cls.check_if_all_fields_valid(current_dict):
      raise IllegalFieldError("Fields present are outside the fields allowed")

      if not cls.check_if_all_required_fields_present(current_dict):
      raise RequiredFieldNotPresentError("All required fields are not present in the dictionary ")

      return cls.apply_converters(current_dict)


      # Example field classes
      class JobSpec(Model):
      job_name = Field(str, default=None)
      command_spec = Field(CommandSpec, repeated = True, default=None)

      class CommandSpec(Mode):
      command_name = Field(str, default=None)


      I'd like general comments on how I could improve the coding style. If I am reinventing the wheel, I am using bad coding practices etc.









      share







      New contributor




      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.







      $endgroup$




      I am trying to create a custom type system in Python.



      from collections import Iterable
      import six

      class Field(object):
      """
      Specification of a field
      """

      def __init__(self,
      prop_type,
      required=False,
      repeated=False,
      choices=None,
      default=None,
      validators=None,
      alias=None,
      converter=None):
      self._type = prop_type
      self._required = required
      self._repeated = repeated
      if choices is not None:
      if not isinstance(choices, Iterable):
      raise IllegalTypeError("Expected iterable got '%s' (%r)" % (type(choices), choices))
      choices = frozenset(choices)
      self._choices = choices
      self._default = default
      self._validators = validators or ()
      self._alias = alias or None
      self._converter = converter

      def is_required(self):
      return self._required

      def is_repeated(self):
      return self._repeated

      # Workaround for not having __set_name__ hook in Python versions before 3.6
      def set_name(self, name):
      """
      Upon class creation, this method is called on all the attribute defined in the class with name of the attribute.
      """
      self._name = name

      def _convert_value(self, value):
      # run converter only if value is not already of defined type
      value_to_store = value
      if self._converter:
      if self.is_repeated() and isinstance(value_to_store, (list, set)):
      value_to_store = [
      self._converter(val) if (not isinstance(val, self._dtype())) else val for val in value_to_store]
      elif not isinstance(value_to_store, self._dtype()):
      value_to_store = self._converter(value_to_store)
      return value_to_store

      def _store_value(self, owner, value):
      """
      Owner is the object that holds the corresponding field
      """
      value_to_store = value
      if value_to_store:
      value_to_store = self._convert_value(value_to_store)
      if self._repeated:
      if isinstance(value_to_store, (list, set)):
      value_to_store = [self._run_validators(item) for item in value_to_store]
      else:
      value_to_store = [self._run_validators(value_to_store)]
      else:
      value_to_store = self._run_validators(value_to_store)
      owner._values[self._name] = value_to_store

      def _retrieve_value(self, owner):
      return owner._values.get(self._name)

      def _run_validators(self, value):
      if value:
      self.check_type(value)
      self.check_choices(value)
      for validator in self._validators:
      validator(value)
      # validator should raise the exception if value is invalid
      return value

      def _get_for_dict(self, owner):
      return self._retrieve_value(owner)

      def check_type(self, value):
      if not isinstance(value, self._dtype()):
      raise IllegalTypeError("Expected '%s' got '%s' (%r) for the field %s" % (self._dtype(),
      type(value),
      value,
      self._name))

      def check_choices(self, value):
      if self._choices is not None:
      if value not in self._choices:
      raise IllegalChoiceError("Value provided is outside the set of choices for the field : %s" % self._name)

      def _check_required_field(self, value):
      return self._required and value is None

      def _dtype(self):
      return self._type

      def __get__(self, owner, value):
      if owner is None:
      return self
      return self._retrieve_value(owner)

      def __set__(self, owner, value):
      self._store_value(owner, value)


      class ComplexField(Field):
      """
      Specification of a complex field
      """
      def __init__(self, model_class, **kwargs):
      super(ComplexField, self).__init__(model_class, **kwargs)
      self._model_class = model_class

      def __getattr__(self, name):
      return self._model_class._properties.get(name)

      def _get_for_dict(self, owner):
      value = self._retrieve_value(owner)
      if self._repeated:
      value = [item._to_dict() for item in value]
      else:
      value = value._todict()
      return value


      class BaseModel(type):
      """ """

      def __init__(cls, name, bases, classdict):
      def make_default(field, value):
      def default(self):
      if callable(value):
      return value(self)
      return value
      setattr(cls, 'default_%s' % field, default)
      super(BaseModel, cls).__init__(name, bases, classdict)
      cls._properties = {}
      cls._alias_to_property_map = {}
      for name in set(dir(cls)):
      attr = getattr(cls, name, None)
      if isinstance(attr, Field):
      attr.set_name(name)
      cls._properties[attr._name] = attr
      if not getattr(cls, 'default_%s' % attr._name, None):
      make_default(attr._name, attr._default)
      # Add alias to field class map
      if attr._alias:
      cls._alias_to_property_map[attr._alias] = attr._name

      def __repr__(cls):
      props =
      for _, prop in sorted(cls._properties.items()):
      props.append('%s=%r' % (prop._name, prop))
      return '%s<%s>' % (cls.__name__, ', '.join(props))


      @six.add_metaclass(BaseModel)
      class Model(object):
      """ """

      def __init__(self, **kwds):
      # add checks for required fields
      self._values = {}
      self._set_attributes(kwds)
      self._populate()
      self.__post_init__()

      def _set_attributes(self, kwds):
      cls = self.__class__
      for name, value in kwds.items():
      prop = getattr(cls, name)
      if not isinstance(prop, Field):
      raise IllegalTypeError('Cannot set non-field "%s" value' % name)
      prop._store_value(self, value)

      def _to_dict(self):
      values = {}
      for _, prop in self._properties.items():
      name = prop._name
      value = prop._get_for_dict(self)
      values[name] = value
      return values

      asdict = _to_dict

      def _populate(self):
      for name, prop in self._properties.items():
      if getattr(self, name) is None:
      prop._store_value(self, getattr(self, "default_%s" % name)())

      def __post_init__(self):
      pass

      def _check_required_fields(self):
      pass

      def __iter__(self):
      values = self._to_dict()
      for key, value in values.items():
      yield (key, value)

      def __getitem__(self, key):
      return getattr(self, key)

      def __setitem__(self, key, value):
      cls = self.__class__
      prop = getattr(cls, key)
      if not isinstance(prop, Field):
      raise IllegalTypeError('Cannot set non-field "%s" value' % key)
      return setattr(self, key, value)

      def __len__(self):
      return 1

      def __post__init__(self):
      pass

      @classmethod
      def create_object_from_dict(cls, input_dict):
      """ """

      # Step 1: Validate
      # Check if all dictionary fields are defined fields
      # Check if all required fields are present
      # Convert the aliases
      # Execute converters

      current_fields = cls.validate_and_convert_input_dictionary(input_dict)
      for key, value in current_fields.items():
      field_class = cls.get_field_class(key)
      is_repeated = cls.is_field_repeated(key)

      if not cls.is_domain_class(field_class):
      val = cls.create_simple_field(value, field_class, is_repeated)

      elif isinstance(value, dict):
      val = cls.create_complex_field_from_dict(field_class, value)

      elif isinstance(value, list):
      # currently list is assumed to be list of dicts this will be changed accordingly
      val = [cls.create_complex_field_from_dict(field_class, v) for v in value]

      else:
      raise IllegalTypeError('Improper type of value for field %s' % key)

      current_fields[key] = val

      return cls(**current_fields)

      @classmethod
      def is_field_repeated(cls, field_name):
      return cls._properties[field_name].is_repeated()

      @classmethod
      def create_simple_field(cls, value, field_class, is_repeated):
      val = None
      # if we have multiple arguments unwrap it and pass to the constructor
      if isinstance(value, dict):
      val = field_class(**value)
      elif is_repeated:
      if isinstance(value, (list, tuple, set)):
      val = [field_class(v) for v in value]
      else:
      val = [field_class(value)]
      else:
      if isinstance(value, (list, tuple, set)):
      val = field_class(*value)
      else:
      val = field_class(value)
      return val

      @classmethod
      def get_field_class(cls, field_name):
      # first we check if class is present in the field class map
      class_map = getattr(cls, "field_class_map", None)
      if class_map and field_name in class_map:
      return class_map[field_name]
      return cls._properties[field_name]._type

      @classmethod
      def check_if_all_fields_valid(cls, input_dict): # checks if all the fields defined are already in cls definition
      return all([k in cls._properties for k, v in input_dict.items()])

      @classmethod
      def check_if_all_required_fields_present(cls, input_dict):
      return all([k2 in input_dict for k2 in [k for k, v in cls._properties.items() if v.is_required()]])

      @classmethod
      def create_complex_field_from_dict(cls, field_class, input_dict):
      return field_class.create_object_from_dict(input_dict)

      @classmethod
      def is_domain_class(cls, field_class):
      return issubclass(field_class, Model)

      @classmethod
      def convert_alias(cls, input_dict):
      return {
      cls._alias_to_property_map[k] if k in cls._alias_to_property_map else k: v for k, v in input_dict.items()}

      @classmethod
      def apply_converters(cls, input_dict):
      current_dict = {}
      for key, val in input_dict.items():
      if val:
      val = cls._properties[key]._convert_value(val)
      current_dict[key] = val
      return current_dict

      @classmethod
      def validate_and_convert_input_dictionary(cls, input_dict):
      current_dict = cls.convert_alias(input_dict)

      if not cls.check_if_all_fields_valid(current_dict):
      raise IllegalFieldError("Fields present are outside the fields allowed")

      if not cls.check_if_all_required_fields_present(current_dict):
      raise RequiredFieldNotPresentError("All required fields are not present in the dictionary ")

      return cls.apply_converters(current_dict)


      # Example field classes
      class JobSpec(Model):
      job_name = Field(str, default=None)
      command_spec = Field(CommandSpec, repeated = True, default=None)

      class CommandSpec(Mode):
      command_name = Field(str, default=None)


      I'd like general comments on how I could improve the coding style. If I am reinventing the wheel, I am using bad coding practices etc.







      python





      share







      New contributor




      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.










      share







      New contributor




      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.








      share



      share






      New contributor




      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.









      asked 4 mins ago









      Lokesh AgrawalLokesh Agrawal

      1011




      1011




      New contributor




      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.





      New contributor





      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






      Lokesh Agrawal is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
      Check out our Code of Conduct.






















          0






          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });






          Lokesh Agrawal is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213081%2fpython-type-system%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          0






          active

          oldest

          votes








          0






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          Lokesh Agrawal is a new contributor. Be nice, and check out our Code of Conduct.










          draft saved

          draft discarded


















          Lokesh Agrawal is a new contributor. Be nice, and check out our Code of Conduct.













          Lokesh Agrawal is a new contributor. Be nice, and check out our Code of Conduct.












          Lokesh Agrawal is a new contributor. Be nice, and check out our Code of Conduct.
















          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f213081%2fpython-type-system%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Список кардиналов, возведённых папой римским Каликстом III

          Deduzione

          Mysql.sock missing - “Can't connect to local MySQL server through socket”