text: from fireo.fields.errors import FieldNotFound, MissingFieldOptionError
  from fireo.managers import managers
  from fireo import fields
  from fireo.models.errors import NonAbstractModel, UnSupportedMeta
  from fireo.utils import utils
  
  
  class ModelMeta(type):
      """Create fields, manager and other stuff for Models
  
      Add `_meta` attribute to each Model which hold all the information about fields, managers and any
      other stuff related to model. Also responsible to generate collection name from model.
  
      Methods
      -------
      __new__(mcs, name, base, attrs):
          Create modified type for model class
      """
  
      def __new__(mcs, name, base, attrs):
          """Create modified type for model class
  
          Convert fields, managers and other stuff into `Meta` class and attached it with model class
  
          Attributes
          ----------
          _meta : Meta()
              Hold all information about you model, can be accessible via class **cls._meta**
  
          collection_name : str
              Name of collection which is saved in database if user not provided any collection name
              then `Meta` class generate it from Model name and attach it with class can be accessible
              via class **cls.collection_name**
  
          Returns
          -------
              Modified type for model class
          """
          cls = super().__new__(mcs, name, base, attrs)
  
          # check user specify any additional meta data for this model or not
          # For example collection name etc if there is not then assign None to it
          if 'Meta' not in attrs:
              cls.Meta = None
  
          class Meta:
              """Hold information about fields, manager and other model related stuff
  
              Meta class get fields, manager and other model related stuff and attach it to
              with model class.
  
              For example:
                  class User:
                      name = TextField()
                      age = NumberField()
  
                  u = User()
                  u.name = "Azeem"
                  u.age = 25
  
                  This Meta class is responsible to attach these fields with model class
                  later on these fields can be accessible via **cls._meta**
  
                  For example:
                      cls._meta.field_list() will return {name: <obj of TextField ox13>, age: <obj of NumberField 0x17>}
  
                  Later on you can use these fields to get corresponding value from model and use them further
                  field value can be get like this:
  
                      getattr(self, field_name)
  
                  Here `self` is instance of model class and `field_name` is one the name from `field_list()`
  
                  In above example if you run this
  
                      getattr(model_instance, name)
                      getattr(model_instance, age)
  
                  it will return **Azeem** and **25**
  
              Attributes
              ----------
              field_list : dict
                  Contain all model field
  
              id : str, optional
                  Model id if not specify then it return `None` and later create by firestore
                  and attach to model
  
              abstract: bool
                  Model is abstract or not
  
              missing_field: str
                  Model config what to do with fields that are comming from firestore and not in model
                  possible values are (merge, ignore, raise_error) merge is default
  
              to_lowercase: bool
                  Firestore is case sensitive convert all value in lowercase if set True. By default
                  it set False.
  
              collection : manager
                  Collection is class level attribute can be used to access the manager
  
              See Also
              --------
              fireo.managers.Manager :
                  Manager are responsible to run certain functions on firestore like creating collection
                  getting documents update etc
  
              Methods
              -------
              add_model_id(field):
                  If user specify any id then attach this id to model
  
              add_field(field):
                  All all user specified fields in Model class
  
              get_field(name):
                  Get field from model on the base of name
  
              get_field_by_column_name(name):
                  Get field according to firestore column name(field name)
  
              set_user_defined_meta(user_meta):
                  Set user defined meta in model class, These meta is actually the config for model
  
              Raises:
              -------
              FieldNotFound:
                  if there is no such in field in model class
  
              NonAbstractModel:
                  If any model is inherit from non abstract model
              """
              field_list = {}  # Hold all the model fields
              id = None  # Model id if user specify otherwise just None will generate late by firestore automatically
  
              def __init__(self):
                  # Convert Model class into collection name
                  # change it to lower case and snake case
                  # e.g UserCollection into user_collection
                  self.collection_name = utils.collection_name(cls.__name__)
                  self.abstract = False
                  self.missing_field = 'merge'
                  self.to_lowercase = False
                  self._referenceDoc = None
                  self._firestore_create_time = None
                  self._firestore_update_time = None
  
              # Attached manager to model class
              # later on manager can be accessible via class `collection` attribute
              # like this **cls.collection**
              if 'collection' not in cls.__dict__:
                  manager = managers.Manager()
                  manager.contribute_to_model(cls)
  
              def add_model_id(self, field):
                  """ Attach user define id to model
  
                  If user specify any custom id for this model
                  attach it to model this contains two thing custom id and if field
  
                  For Example:
                      class User:
                          user_id = IDField()
  
                      In this case it will hold (user_id, <obj of IDField 0x137>)
  
                      later `id` can be accessible via `_meta`
                      like this **cls._meta.id**
  
                  Parameters
                  ----------
                  field : IDField()
                      User defined custom id field
                  """
                  self.id = (field.name, field)
  
              def add_field(self, field):
                  """Add model fields into model meta class
  
                  These fields can be used later for getting corresponding values from
                  model instance and some other operations
  
                  can be accessible via `_meta`
                  like this **cls._meta.field_list()**
  
                  Parameters
                  ----------
                  field : Field
                      This can be any type which is derived from `Field` sub class
                      For example `TextField`, `NumberField` etc
                  """
                  self.field_list[field.name] = field
  
              def get_field(self, name):
                  """Get model field from field list
  
                  Get field on the base of it's name can be accessible via `_meta`
                  like this **cls._meta.get_field(name)**
  
                  Parameters
                  ---------
                  name:
                      Field name
  
                  Returns
                  ------
                      Model field
  
                  Raises
                  ------
                  FieldNotFound:
                      If field not found in model
                  """
                  if name in self.field_list:
                      return self.field_list[name]
                  raise FieldNotFound(f'Field "{name}" not found in model "{cls.__name__}"')
  
              def get_field_by_column_name(self, name):
                  """Get field by column name
  
                  User can also define different name for each field to save in firestore
                  Check if user specify any different name then get field corresponding to
                  this db_column_name. If no column name is specify for field then just
                  return the same field name
  
                  Parameters
                  ----------
                  name : str
                      column name (field name) coming form firestore
  
                  Raises
                  -------
                  FieldNotFound:
                      if field not found in model class and model config for `missing_field` is **raise_error**
                  """
                  for field in self.field_list.values():
                      if name in [field.name, field.db_column_name]:
                          return field
                  if self.missing_field == 'merge':
                      f = fields.Field()
                      f.name = name
                      return f
                  if self.missing_field == 'ignore':
                      return None
                  if self.missing_field == 'raise_error':
                      raise FieldNotFound(f'Field "{name}" not found in model "{cls.__name__}"')
  
              def set_reference_doc(self, referenceDoc):
                  """save the firestore reference doc for further processing"""
                  self._referenceDoc = referenceDoc
  
              def set_user_defined_meta(self, user_meta):
                  """Set user defined meta attributes for model
  
                  User can define config by using `Meta` class in models
  
                  Examples
                  --------
                  .. code-block:: python
                      class User(Model):
                          name = TextField()
  
                          class Meta:
                              collection_name = "user_test_collection"
  
                      # All the documents now save in *user_test_collection* for this model
  
                  Parameters
                  ---------
                  user_meta:
                      User defined meta for model class
  
                  Raises
                  -------
                  MissingFieldOptionError:
                      If option for missing_field is other than ignore, merge or raise_error
                  """
                  for name, val in user_meta.__dict__.items():
                      supported_meta = ['collection_name', 'abstract', 'to_lowercase', 'missing_field']
  
                      # check if name is supported by meta and name is not
                      # any special name for example '__main__, __doc__'
                      if name not in supported_meta and '__' not in name:
                          raise UnSupportedMeta(f'Meta "{name}" is not recognize in model "{cls.__name__}" '
                                                f'Possible value are {", ".join(supported_meta)}')
  
                      if name == 'collection_name':
                          self.collection_name = val
                      if name == 'abstract':
                          self.abstract = val
                      if name == 'to_lowercase':
                          self.to_lowercase = val
                      if name == 'missing_field':
                          if val in ['merge', 'ignore', 'raise_error']:
                              self.missing_field = val
                          else:
                              raise MissingFieldOptionError(
                                  f'Option "{val}" is not supported by missing_field. '
                                  f'Possible values are ignore, merge, raise_error')
  
          # Create instance of Meta class and set it to
          # Model class as _meta attribute
          _meta = Meta()
          setattr(cls, '_meta', _meta)
  
          # Get all fields from model and save them into `_meta.field_list()`
          # and then attach this `_meta` to model class
          for name, field in cls.__dict__.items():
              if isinstance(field, type) and name == 'Meta':
                  _meta.set_user_defined_meta(field)
              if isinstance(field, (managers.Manager, fields.Field)):
                  field.contribute_to_model(cls, name)
  
          # Get base Model if they are abstract then add these models field
          # in child classes
          #
          # For example:
          #   class AbstractModel(Model):
          #       name = TextField()
          #
          #       class Meta:
          #           abstract = True
          #
          #   class User(AbstractModel):
          #       age = NumberFiled()
          #
          #   class Student(AbstractModel):
          #       age = NumberFiled()
          #
          # Both Model user and student now contains the name field
          #
          for b in base:
              if not b._meta.abstract:
                  from fireo.models import Model
                  if b != Model:
                      raise NonAbstractModel(f'Model "{cls.__name__}" can not inherit from non abstract '
                                             f'model "{b.__name__}"')
                  continue
              for name, field in b._meta.field_list.items():
                  # Ignore the id field
                  if isinstance(field, fields.IDField): continue
                  field.contribute_to_model(cls, name)
  
          # Set collection name to model class that is generated from
          # Model class this name can be user defined or auto generated
          # from model class
          #
          # For example:
          #   class UserCollection:
          #       name = TextField()
          #
          # if user not defined collection name then it will auto generate
          # for this class
          #
          # UserCollection will become user_collection
          setattr(cls, "collection_name", cls._meta.collection_name)
  
          return cls
  