Exemple #1
0
class CellAreaKeyMixin(HashKeyMixin):

    _hashkey_cls = CellAreaKey

    radio = Column(TinyIntEnum(Radio), autoincrement=False)
    mcc = Column(SmallInteger, autoincrement=False)
    mnc = Column(SmallInteger, autoincrement=False)
    lac = Column(SmallInteger(unsigned=True), autoincrement=False)
Exemple #2
0
class CellAreaKeyMixin(HashKeyMixin):

    _hashkey_cls = CellAreaKey

    # mapped via RADIO_TYPE
    radio = Column(TinyInteger, autoincrement=False)
    mcc = Column(SmallInteger, autoincrement=False)
    mnc = Column(SmallInteger, autoincrement=False)
    lac = Column(SmallInteger(unsigned=True), autoincrement=False)
Exemple #3
0
class Cell(_Model):
    __tablename__ = 'cell'
    __table_args__ = (
        Index('cell_created_idx', 'created'),
        Index('cell_modified_idx', 'modified'),
        Index('cell_new_measures_idx', 'new_measures'),
        Index('cell_total_measures_idx', 'total_measures'),
        {
            'mysql_engine': 'InnoDB',
            'mysql_charset': 'utf8',
        }
    )

    created = Column(DateTime)
    modified = Column(DateTime)

    # lat/lon
    lat = Column(Double(asdecimal=False))
    max_lat = Column(Double(asdecimal=False))
    min_lat = Column(Double(asdecimal=False))

    lon = Column(Double(asdecimal=False))
    max_lon = Column(Double(asdecimal=False))
    min_lon = Column(Double(asdecimal=False))

    # mapped via RADIO_TYPE
    radio = Column(TinyInteger, autoincrement=False, primary_key=True)
    # int in the range 0-1000
    mcc = Column(SmallInteger, autoincrement=False, primary_key=True)
    # int in the range 0-1000 for gsm
    # int in the range 0-32767 for cdma (system id)
    mnc = Column(SmallInteger, autoincrement=False, primary_key=True)
    lac = Column(
        SmallInteger(unsigned=True), autoincrement=False, primary_key=True)
    cid = Column(Integer(unsigned=True), autoincrement=False, primary_key=True)
    psc = Column(SmallInteger)
    range = Column(Integer)
    new_measures = Column(Integer(unsigned=True))
    total_measures = Column(Integer(unsigned=True))

    def __init__(self, *args, **kw):
        if 'created' not in kw:
            kw['created'] = util.utcnow()
        if 'modified' not in kw:
            kw['modified'] = util.utcnow()
        if 'lac' not in kw or not kw['lac']:
            kw['lac'] = 0
        if 'cid' not in kw or not kw['cid']:
            kw['cid'] = 0
        if 'range' not in kw:
            kw['range'] = 0
        if 'new_measures' not in kw:
            kw['new_measures'] = 0
        if 'total_measures' not in kw:
            kw['total_measures'] = 0
        super(Cell, self).__init__(*args, **kw)
class CellAreaMixin(PositionMixin, TimeTrackingMixin, CreationMixin):

    _valid_schema = ValidCellAreaSchema()

    areaid = Column(CellAreaColumn(7))
    radio = Column(TinyIntEnum(Radio), nullable=False)
    mcc = Column(SmallInteger, nullable=False)
    mnc = Column(SmallInteger, nullable=False)
    lac = Column(SmallInteger(unsigned=True), nullable=False)

    radius = Column(Integer)
    region = Column(String(2))
    avg_cell_radius = Column(Integer(unsigned=True))
    num_cells = Column(Integer(unsigned=True))
    last_seen = Column(Date)

    @declared_attr
    def __table_args__(cls):  # NOQA
        prefix = cls.__tablename__
        _indices = (
            PrimaryKeyConstraint('areaid'),
            UniqueConstraint('radio',
                             'mcc',
                             'mnc',
                             'lac',
                             name='%s_areaid_unique' % prefix),
            Index('%s_region_radio_idx' % prefix, 'region', 'radio'),
            Index('%s_created_idx' % prefix, 'created'),
            Index('%s_modified_idx' % prefix, 'modified'),
            Index('%s_latlon_idx' % prefix, 'lat', 'lon'),
        )
        return _indices + (cls._settings, )

    @classmethod
    def validate(cls, entry, _raise_invalid=False, **kw):
        validated = super(CellAreaMixin,
                          cls).validate(entry,
                                        _raise_invalid=_raise_invalid,
                                        **kw)
        if validated is not None and 'areaid' not in validated:
            validated['areaid'] = (
                validated['radio'],
                validated['mcc'],
                validated['mnc'],
                validated['lac'],
            )

            if (('region' not in validated or not validated['region'])
                    and validated['lat'] is not None
                    and validated['lon'] is not None):
                validated['region'] = GEOCODER.region_for_cell(
                    validated['lat'], validated['lon'], validated['mcc'])

        return validated
Exemple #5
0
class CellAreaMixin(PositionMixin, TimeTrackingMixin, CreationMixin):

    _valid_schema = ValidCellAreaSchema()

    areaid = Column(CellAreaColumn(7))
    radio = Column(TinyIntEnum(Radio), nullable=False)
    mcc = Column(SmallInteger, nullable=False)
    mnc = Column(SmallInteger, nullable=False)
    lac = Column(SmallInteger(unsigned=True), nullable=False)

    radius = Column(Integer)
    region = Column(String(2))
    avg_cell_radius = Column(Integer(unsigned=True))
    num_cells = Column(Integer(unsigned=True))
    last_seen = Column(Date)

    @declared_attr
    def __table_args__(cls):
        prefix = cls.__tablename__
        _indices = (
            PrimaryKeyConstraint("areaid"),
            UniqueConstraint("radio",
                             "mcc",
                             "mnc",
                             "lac",
                             name="%s_areaid_unique" % prefix),
            Index("%s_region_radio_idx" % prefix, "region", "radio"),
            Index("%s_created_idx" % prefix, "created"),
            Index("%s_modified_idx" % prefix, "modified"),
            Index("%s_latlon_idx" % prefix, "lat", "lon"),
        )
        return _indices + (cls._settings, )

    @classmethod
    def validate(cls, entry, _raise_invalid=False, **kw):
        validated = super(CellAreaMixin,
                          cls).validate(entry,
                                        _raise_invalid=_raise_invalid,
                                        **kw)
        if validated is not None and "areaid" not in validated:
            validated["areaid"] = (
                validated["radio"],
                validated["mcc"],
                validated["mnc"],
                validated["lac"],
            )

            if (("region" not in validated or not validated["region"])
                    and validated["lat"] is not None
                    and validated["lon"] is not None):
                validated["region"] = GEOCODER.region_for_cell(
                    validated["lat"], validated["lon"], validated["mcc"])

        return validated
Exemple #6
0
class CellBlocklist(_Model):
    __tablename__ = 'cell_blacklist'

    _indices = (PrimaryKeyConstraint('radio', 'mcc', 'mnc', 'lac', 'cid'), )

    radio = Column(TinyIntEnum(Radio), autoincrement=False, default=None)
    mcc = Column(SmallInteger, autoincrement=False, default=None)
    mnc = Column(SmallInteger, autoincrement=False, default=None)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False,
                 default=None)
    cid = Column(Integer(unsigned=True), autoincrement=False, default=None)
    time = Column(DateTime)
    count = Column(Integer)
Exemple #7
0
class BaseCell(StationMixin):

    _valid_schema = ValidCellShardSchema()

    cellid = Column(CellIdColumn(11))
    radio = Column(TinyIntEnum(Radio), autoincrement=False, nullable=False)
    mcc = Column(SmallInteger, autoincrement=False, nullable=False)
    mnc = Column(SmallInteger, autoincrement=False, nullable=False)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False,
                 nullable=False)
    cid = Column(Integer(unsigned=True), autoincrement=False, nullable=False)
    psc = Column(SmallInteger, autoincrement=False)

    @classmethod
    def validate(cls, entry, _raise_invalid=False, **kw):
        validated = super(BaseCell,
                          cls).validate(entry,
                                        _raise_invalid=_raise_invalid,
                                        **kw)

        if validated is not None:
            if 'cellid' not in validated:
                validated['cellid'] = (
                    validated['radio'],
                    validated['mcc'],
                    validated['mnc'],
                    validated['lac'],
                    validated['cid'],
                )

            if (('region' not in validated or not validated['region'])
                    and validated['lat'] is not None
                    and validated['lon'] is not None):
                validated['region'] = GEOCODER.region_for_cell(
                    validated['lat'], validated['lon'], validated['mcc'])

        return validated

    @property
    def areaid(self):
        return encode_cellarea(self.radio, self.mcc, self.mnc, self.lac)

    @property
    def unique_key(self):
        return encode_cellid(*self.cellid)
Exemple #8
0
class OCIDCell(_Model):
    __tablename__ = 'ocid_cell'
    __table_args__ = (Index('ocid_cell_created_idx', 'created'), {
        'mysql_engine': 'InnoDB',
        'mysql_charset': 'utf8',
    })

    created = Column(DateTime)
    modified = Column(DateTime)

    # lat/lon
    lat = Column(Double(asdecimal=False))
    lon = Column(Double(asdecimal=False))

    # radio mapped via RADIO_TYPE
    radio = Column(TinyInteger, autoincrement=False, primary_key=True)
    mcc = Column(SmallInteger, autoincrement=False, primary_key=True)
    mnc = Column(SmallInteger, autoincrement=False, primary_key=True)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False,
                 primary_key=True)
    cid = Column(Integer(unsigned=True), autoincrement=False, primary_key=True)

    psc = Column(SmallInteger)
    range = Column(Integer)
    total_measures = Column(Integer(unsigned=True))
    changeable = Column(Boolean)

    def __init__(self, *args, **kw):
        if 'created' not in kw:
            kw['created'] = util.utcnow()
        if 'modified' not in kw:
            kw['modified'] = util.utcnow()
        if 'lac' not in kw:
            kw['lac'] = -1
        if 'cid' not in kw:
            kw['cid'] = -1
        if 'range' not in kw:
            kw['range'] = 0
        if 'total_measures' not in kw:
            kw['total_measures'] = 0
        if 'changeable' not in kw:
            kw['changeable'] = True
        super(OCIDCell, self).__init__(*args, **kw)
Exemple #9
0
class CellMeasure(_Model):
    __tablename__ = 'cell_measure'
    __table_args__ = (
        Index('cell_measure_created_idx', 'created'),
        Index('cell_measure_key_idx', 'radio', 'mcc', 'mnc', 'lac', 'cid'),
        {
            'mysql_engine': 'InnoDB',
            'mysql_charset': 'utf8',
        }
    )

    id = Column(BigInteger(unsigned=True),
                primary_key=True, autoincrement=True)
    report_id = Column(BINARY(length=16))
    created = Column(DateTime)  # the insert time of the record into the DB
    # lat/lon
    lat = Column(Double(asdecimal=False))
    lon = Column(Double(asdecimal=False))
    time = Column(DateTime)  # the time of observation of this data
    accuracy = Column(Integer)
    altitude = Column(Integer)
    altitude_accuracy = Column(Integer)

    # http://dev.w3.org/geo/api/spec-source.html#heading
    heading = Column(Float)

    # http://dev.w3.org/geo/api/spec-source.html#speed
    speed = Column(Float)

    # mapped via RADIO_TYPE
    radio = Column(TinyInteger)
    mcc = Column(SmallInteger)
    mnc = Column(SmallInteger)
    lac = Column(SmallInteger(unsigned=True))
    cid = Column(Integer(unsigned=True))
    psc = Column(SmallInteger)
    asu = Column(SmallInteger)
    signal = Column(SmallInteger)
    ta = Column(TinyInteger)

    def __init__(self, *args, **kw):
        if 'created' not in kw:
            kw['created'] = util.utcnow()
        super(CellMeasure, self).__init__(*args, **kw)
Exemple #10
0
class Cell(BboxMixin, PositionMixin, TimeTrackingMixin, _Model):  # BBB
    __tablename__ = 'cell'

    _indices = (
        PrimaryKeyConstraint('radio', 'mcc', 'mnc', 'lac', 'cid'),
        Index('cell_created_idx', 'created'),
        Index('cell_modified_idx', 'modified'),
    )

    radio = Column(TinyIntEnum(Radio), autoincrement=False, default=None)
    mcc = Column(SmallInteger, autoincrement=False, default=None)
    mnc = Column(SmallInteger, autoincrement=False, default=None)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False,
                 default=None)
    cid = Column(Integer(unsigned=True), autoincrement=False, default=None)
    psc = Column(SmallInteger, autoincrement=False)
    radius = Column(Integer)
    samples = Column(Integer(unsigned=True))
    new_measures = Column(Integer(unsigned=True))
Exemple #11
0
class CellBlacklist(_Model):
    __tablename__ = 'cell_blacklist'
    __table_args__ = ({
        'mysql_engine': 'InnoDB',
        'mysql_charset': 'utf8',
    })
    time = Column(DateTime)
    radio = Column(TinyInteger, autoincrement=False, primary_key=True)
    mcc = Column(SmallInteger, autoincrement=False, primary_key=True)
    mnc = Column(SmallInteger, autoincrement=False, primary_key=True)
    lac = Column(
        SmallInteger(unsigned=True), autoincrement=False, primary_key=True)
    cid = Column(Integer(unsigned=True), autoincrement=False, primary_key=True)
    count = Column(Integer)

    def __init__(self, *args, **kw):
        if 'time' not in kw:
            kw['time'] = util.utcnow()
        if 'count' not in kw:
            kw['count'] = 1
        super(CellBlacklist, self).__init__(*args, **kw)
Exemple #12
0
class CellArea(_Model):
    __tablename__ = 'cell_area'
    __table_args__ = {
        'mysql_engine': 'InnoDB',
        'mysql_charset': 'utf8',
    }

    created = Column(DateTime)
    modified = Column(DateTime)

    # lat/lon
    lat = Column(Double(asdecimal=False))
    lon = Column(Double(asdecimal=False))

    # radio mapped via RADIO_TYPE
    radio = Column(TinyInteger,
                   autoincrement=False, primary_key=True)
    mcc = Column(SmallInteger,
                 autoincrement=False, primary_key=True)
    mnc = Column(SmallInteger,
                 autoincrement=False, primary_key=True)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False, primary_key=True)

    range = Column(Integer)
    avg_cell_range = Column(Integer)
    num_cells = Column(Integer(unsigned=True))

    def __init__(self, *args, **kw):
        if 'created' not in kw:
            kw['created'] = util.utcnow()
        if 'modified' not in kw:
            kw['modified'] = util.utcnow()
        if 'range' not in kw:
            kw['range'] = 0
        if 'avg_cell_range' not in kw:
            kw['avg_cell_range'] = 0
        if 'num_cells' not in kw:
            kw['num_cells'] = 0
        super(CellArea, self).__init__(*args, **kw)
class CellShard(StationMixin):
    """Cell shard."""

    _shards = CELL_SHARDS
    _valid_schema = ValidCellShardSchema()

    cellid = Column(CellIdColumn(11))
    radio = Column(TinyIntEnum(Radio), nullable=False)
    mcc = Column(SmallInteger, nullable=False)
    mnc = Column(SmallInteger, nullable=False)
    lac = Column(SmallInteger(unsigned=True), nullable=False)
    cid = Column(Integer(unsigned=True), nullable=False)
    psc = Column(SmallInteger)

    @declared_attr
    def __table_args__(cls):  # NOQA
        _indices = (
            PrimaryKeyConstraint('cellid'),
            UniqueConstraint('radio',
                             'mcc',
                             'mnc',
                             'lac',
                             'cid',
                             name='%s_cellid_unique' % cls.__tablename__),
            Index('%s_region_idx' % cls.__tablename__, 'region'),
            Index('%s_created_idx' % cls.__tablename__, 'created'),
            Index('%s_modified_idx' % cls.__tablename__, 'modified'),
            Index('%s_latlon_idx' % cls.__tablename__, 'lat', 'lon'),
        )
        return _indices + (cls._settings, )

    @property
    def unique_key(self):
        return encode_cellid(*self.cellid)

    @classmethod
    def validate(cls, entry, _raise_invalid=False, **kw):
        validated = super(CellShard,
                          cls).validate(entry,
                                        _raise_invalid=_raise_invalid,
                                        **kw)

        if validated is not None:
            if 'cellid' not in validated:
                validated['cellid'] = (
                    validated['radio'],
                    validated['mcc'],
                    validated['mnc'],
                    validated['lac'],
                    validated['cid'],
                )

            if (('region' not in validated or not validated['region'])
                    and validated['lat'] is not None
                    and validated['lon'] is not None):
                validated['region'] = GEOCODER.region_for_cell(
                    validated['lat'], validated['lon'], validated['mcc'])

        return validated

    @classmethod
    def create(cls, _raise_invalid=False, **kw):
        """
        Returns an instance of the correct shard model class, if the
        passed in keyword arguments pass schema validation,
        otherwise returns None.
        """
        validated = cls.validate(kw, _raise_invalid=_raise_invalid)
        if validated is None:  # pragma: no cover
            return None
        shard = cls.shard_model(validated['radio'])
        return shard(**validated)

    @classmethod
    def shard_id(cls, radio):
        """
        Given a radio type return the correct shard id.
        """
        if type(radio) == bytes and len(radio) == 11:
            # extract radio from cellid
            radio = decode_cellid(radio)[0]
        if type(radio) == Radio:
            return radio.name
        if isinstance(radio, tuple) and len(radio) == 5:
            return radio[0].name
        try:
            return Radio[radio].name
        except KeyError:
            pass
        return None

    @classmethod
    def shard_model(cls, radio):
        """
        Given a radio type return the correct DB model class.
        """
        return cls._shards.get(cls.shard_id(radio), None)

    @classmethod
    def shards(cls):
        """Return a dict of shard id to model classes."""
        return cls._shards

    @classmethod
    def export_header(cls):
        return ('radio,mcc,mnc,lac,cid,psc,'
                'lat,lon,max_lat,min_lat,max_lon,min_lon,'
                'radius,region,samples,source,weight,'
                'created,modified,last_seen,'
                'block_first,block_last,block_count')

    @classmethod
    def export_stmt(cls):
        stmt = '''SELECT
`cellid` AS `export_key`,
CONCAT_WS(",",
    CASE radio
        WHEN 0 THEN "GSM"
        WHEN 2 THEN "WCDMA"
        WHEN 3 THEN "LTE"
        ELSE ""
    END,
    `mcc`,
    `mnc`,
    `lac`,
    `cid`,
    COALESCE(`psc`, ""),
    COALESCE(ROUND(`lat`, 7), ""),
    COALESCE(ROUND(`lon`, 7), ""),
    COALESCE(ROUND(`max_lat`, 7), ""),
    COALESCE(ROUND(`min_lat`, 7), ""),
    COALESCE(ROUND(`max_lon`, 7), ""),
    COALESCE(ROUND(`min_lon`, 7), ""),
    COALESCE(`radius`, "0"),
    COALESCE(`region`, ""),
    COALESCE(`samples`, "0"),
    COALESCE(`source`, ""),
    COALESCE(`weight`, "0"),
    COALESCE(UNIX_TIMESTAMP(`created`), ""),
    COALESCE(UNIX_TIMESTAMP(`modified`), ""),
    COALESCE(UNIX_TIMESTAMP(`last_seen`), ""),
    COALESCE(UNIX_TIMESTAMP(`block_first`), ""),
    COALESCE(UNIX_TIMESTAMP(`block_last`), ""),
    COALESCE(`block_count`, "0")
) AS `export_value`
FROM %s
WHERE `cellid` > :export_key
ORDER BY `cellid`
LIMIT :limit
''' % cls.__tablename__
        return stmt.replace('\n', ' ')
Exemple #14
0
class Account(Model, BaseModelMixin):
    __tablename__ = 'accounts'

    account_id = Column(Integer(unsigned=True),
                        primary_key=True,
                        autoincrement=True)
    account_name = Column(String(256), nullable=False, index=True, unique=True)
    account_type_id = Column(Integer(unsigned=True),
                             ForeignKey(AccountType.account_type_id,
                                        name='fk_account_account_type_id',
                                        ondelete='CASCADE'),
                             nullable=False,
                             index=True)
    contacts = Column(JSON, nullable=False)
    enabled = Column(SmallInteger(unsigned=True), nullable=False, default=1)
    required_roles = Column(JSON, nullable=True)
    properties = relationship('AccountProperty',
                              lazy='select',
                              uselist=True,
                              primaryjoin=account_id == foreign(
                                  AccountProperty.account_id),
                              cascade='all, delete-orphan')

    @staticmethod
    def get(account_id, account_type_id=None):
        """Return account by ID and type

        Args:
            account_id (`int`, `str`): Unique Account identifier
            account_type_id (str): Type of account to get

        Returns:
            :obj:`Account`: Returns an Account object if found, else None
        """
        if type(account_id) == str:
            args = {'account_name': account_id}
        else:
            args = {'account_id': account_id}

        if account_type_id:
            args['account_type_id'] = account_type_id

        return db.Account.find_one(**args)

    def user_has_access(self, user):
        """Check if a user has access to view information for the account

        Args:
            user (:obj:`User`): User object to check

        Returns:
            True if user has access to the account, else false
        """
        if ROLE_ADMIN in user.roles:
            return True

        # Non-admin users should only see active accounts
        if self.enabled:
            if not self.required_roles:
                return True

            for role in self.required_roles:
                if role in user.roles:
                    return True

        return False
Exemple #15
0
class CellAreaMixin(PositionMixin, TimeTrackingMixin, CreationMixin,
                    ScoreMixin):

    _valid_schema = ValidCellAreaSchema()

    areaid = Column(CellAreaColumn(7))
    radio = Column(TinyIntEnum(Radio), autoincrement=False, nullable=False)
    mcc = Column(SmallInteger, autoincrement=False, nullable=False)
    mnc = Column(SmallInteger, autoincrement=False, nullable=False)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False,
                 nullable=False)

    radius = Column(Integer)
    region = Column(String(2))
    avg_cell_radius = Column(Integer(unsigned=True))
    num_cells = Column(Integer(unsigned=True))

    def score_sample_weight(self):
        # treat areas for which we get the exact same
        # cells multiple times as if we only got 1 cell
        samples = self.num_cells
        if samples > 1 and not self.radius:
            samples = 1

        # sample_weight is a number between:
        # 1.0 for 1 sample
        # 1.41 for 2 samples
        # 10 for 100 samples
        # we use a sqrt scale instead of log2 here, as this represents
        # the number of cells in an area and not the sum of samples
        # from all cells in the area
        return min(math.sqrt(max(samples, 1)), 10.0)

    @declared_attr
    def __table_args__(cls):  # NOQA
        prefix = cls.__tablename__
        _indices = (
            PrimaryKeyConstraint('areaid'),
            UniqueConstraint('radio',
                             'mcc',
                             'mnc',
                             'lac',
                             name='%s_areaid_unique' % prefix),
            Index('%s_region_radio_idx' % prefix, 'region', 'radio'),
            Index('%s_created_idx' % prefix, 'created'),
            Index('%s_modified_idx' % prefix, 'modified'),
            Index('%s_latlon_idx' % prefix, 'lat', 'lon'),
        )
        return _indices + (cls._settings, )

    @classmethod
    def validate(cls, entry, _raise_invalid=False, **kw):
        validated = super(CellAreaMixin,
                          cls).validate(entry,
                                        _raise_invalid=_raise_invalid,
                                        **kw)
        if validated is not None and 'areaid' not in validated:
            validated['areaid'] = (
                validated['radio'],
                validated['mcc'],
                validated['mnc'],
                validated['lac'],
            )

            if (('region' not in validated or not validated['region'])
                    and validated['lat'] is not None
                    and validated['lon'] is not None):
                validated['region'] = GEOCODER.region_for_cell(
                    validated['lat'], validated['lon'], validated['mcc'])

        return validated
Exemple #16
0
class CellShard(StationMixin):
    """Cell shard."""

    _shards = CELL_SHARDS
    _valid_schema = ValidCellShardSchema()

    cellid = Column(CellIdColumn(11))
    radio = Column(TinyIntEnum(Radio), autoincrement=False, nullable=False)
    mcc = Column(SmallInteger, autoincrement=False, nullable=False)
    mnc = Column(SmallInteger, autoincrement=False, nullable=False)
    lac = Column(SmallInteger(unsigned=True),
                 autoincrement=False,
                 nullable=False)
    cid = Column(Integer(unsigned=True), autoincrement=False, nullable=False)
    psc = Column(SmallInteger, autoincrement=False)

    @declared_attr
    def __table_args__(cls):  # NOQA
        _indices = (
            PrimaryKeyConstraint('cellid'),
            UniqueConstraint('radio',
                             'mcc',
                             'mnc',
                             'lac',
                             'cid',
                             name='%s_cellid_unique' % cls.__tablename__),
            Index('%s_region_idx' % cls.__tablename__, 'region'),
            Index('%s_created_idx' % cls.__tablename__, 'created'),
            Index('%s_modified_idx' % cls.__tablename__, 'modified'),
            Index('%s_latlon_idx' % cls.__tablename__, 'lat', 'lon'),
        )
        return _indices + (cls._settings, )

    @property
    def areaid(self):
        return encode_cellarea(self.radio, self.mcc, self.mnc, self.lac)

    @property
    def unique_key(self):
        return encode_cellid(*self.cellid)

    @classmethod
    def validate(cls, entry, _raise_invalid=False, **kw):
        validated = super(CellShard,
                          cls).validate(entry,
                                        _raise_invalid=_raise_invalid,
                                        **kw)

        if validated is not None:
            if 'cellid' not in validated:
                validated['cellid'] = (
                    validated['radio'],
                    validated['mcc'],
                    validated['mnc'],
                    validated['lac'],
                    validated['cid'],
                )

            if (('region' not in validated or not validated['region'])
                    and validated['lat'] is not None
                    and validated['lon'] is not None):
                validated['region'] = GEOCODER.region_for_cell(
                    validated['lat'], validated['lon'], validated['mcc'])

        return validated

    @classmethod
    def create(cls, _raise_invalid=False, **kw):
        """
        Returns an instance of the correct shard model class, if the
        passed in keyword arguments pass schema validation,
        otherwise returns None.
        """
        validated = cls.validate(kw, _raise_invalid=_raise_invalid)
        if validated is None:  # pragma: no cover
            return None
        shard = cls.shard_model(validated['radio'])
        return shard(**validated)

    @classmethod
    def shard_id(cls, radio):
        """
        Given a radio type return the correct shard id.
        """
        if type(radio) == bytes and len(radio) == 11:
            # extract radio from cellid
            radio = decode_cellid(radio)[0]
        if type(radio) == Radio:
            return radio.name
        if isinstance(radio, tuple) and len(radio) == 5:
            return radio[0].name
        try:
            return Radio[radio].name
        except KeyError:
            pass
        return None

    @classmethod
    def shard_model(cls, radio):
        """
        Given a radio type return the correct DB model class.
        """
        return cls._shards.get(cls.shard_id(radio), None)

    @classmethod
    def shards(cls):
        """Return a dict of shard id to model classes."""
        return cls._shards