class TestNestedModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) list_list = columns.List(columns.List(columns.Integer), required=False) map_list = columns.Map(columns.Text, columns.List(columns.Text), required=False) set_tuple = columns.Set(columns.Tuple(columns.Integer, columns.Integer), required=False)
def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor """ column = columns.List(columns.Text) self.assertIsInstance(column.value_col, columns.Text)
def test_to_python(self): """ Tests that to_python of value column is called """ column = columns.List(JsonTestColumn) val = [1, 2, 3] db_val = column.to_database(val) self.assertEqual(db_val, [json.dumps(v) for v in val]) py_val = column.to_python(db_val) self.assertEqual(py_val, val)
class TestModelSave(Model): partition = columns.UUID(primary_key=True, default=uuid4) cluster = columns.Integer(primary_key=True) count = columns.Integer(required=False) text = columns.Text(required=False, index=True) text_set = columns.Set(columns.Text, required=False) text_list = columns.List(columns.Text, required=False) text_map = columns.Map(columns.Text, columns.Text, required=False)
class TestIndexSetModel(Model): partition = columns.UUID(primary_key=True) int_set = columns.Set(columns.Integer, index=True) int_list = columns.List(columns.Integer, index=True) text_map = columns.Map(columns.Text, columns.DateTime, index=True) mixed_tuple = columns.Tuple(columns.Text, columns.Integer, columns.Text, index=True)
def test_instantiation_with_column_class(self): """ Tests that columns instantiated with a column class work properly and that the class is instantiated in the constructor @since 3.1 @jira_ticket PYTHON-478 @expected_result types are instantiated correctly @test_category object_mapper """ list_list = columns.List(columns.List(columns.Integer), required=False) map_list = columns.Map(columns.Text, columns.List(columns.Text), required=False) set_tuple = columns.Set(columns.Tuple(columns.Integer, columns.Integer), required=False) self.assertIsInstance(list_list, columns.List) self.assertIsInstance(list_list.types[0], columns.List) self.assertIsInstance(map_list.types[0], columns.Text) self.assertIsInstance(map_list.types[1], columns.List) self.assertIsInstance(set_tuple.types[0], columns.Tuple)
class OuterModel(Model): name = columns.Text(primary_key=True) first_name = columns.Text() nested = columns.List(columns.UserDefinedType(NestedUdt)) simple = columns.UserDefinedType(NestedUdt)
class Container(Model): id = columns.UUID(primary_key=True, default=uuid4) names = columns.List(columns.UserDefinedType(Name))
situation = columns.Text(required=True) short_description = columns.Text() long_description = columns.Text() # The date when the company was founded foundation_date = columns.Date() # Country of the company # ISO 3166-1 alpha 3 code country_code = columns.Text(min_length=3, max_length=3) # A list of specialties of the company specialties = columns.List(value_type=columns.Text) # A field that represent a map of key-value # We use caravaggio KeyEncodedMap that appends the field name # to each of the keys in order to make them indexable by the # Search Indexer. websites = KeyEncodedMap( key_type=columns.Text, value_type=columns.Text) # A field that represents a raw JSON content extra_data = columns.Text() latitude = columns.Float() longitude = columns.Float() coordinates = columns.Text()
class Company(CustomDjangoCassandraModel): """ A public traded company """ __table_name__ = "company" # A unique identifier of the entity _id = columns.UUID(partition_key=True, default=uuid.uuid4) # The owner of the data. Who own's the company data persisted user = columns.Text(primary_key=True) # When was created the entity and the last modification date created_at = columns.DateTime(default=datetime.utcnow) updated_at = columns.DateTime(default=datetime.utcnow) # Controls if the entity is active or has been deleted is_deleted = columns.Boolean(default=False) deleted_reason = columns.Text() # The name of the company name = columns.Text(required=True) # A short description about the company short_description = columns.Text() # The company domain (e.g. preseries.com) domain = columns.Text(max_length=50) # The date when the company was founded foundation_date = columns.Date() # The date of the latest funding round last_round = columns.Date() # The total number of funding rounds round_notes = columns.Text() # Country of the company # ISO 3166-1 alpha 3 code country_code = columns.Text(min_length=3, max_length=3) # The stock trading symbol stock_symbol = columns.Text() # Contact email of the company contact_email = columns.Text() # The IDs of the founders of the company founders = columns.List(value_type=columns.UUID) # Address of the headquarters of the company address = UserDefinedType(Address) # A list of specialties of the company specialties = columns.List(value_type=columns.Text) # The counters of the latest followers in twitter # (example of list of integers) latest_twitter_followers = columns.List(value_type=columns.Integer) # A field that represent a map of key-value # We use caravaggio KeyEncodedMap that appends the field name # to each of the keys in order to make them indexable by the # Search Indexer. websites = KeyEncodedMap(key_type=columns.Text, value_type=columns.Text) # A field that represents a raw JSON with the crawler configurations, each # key is a reference to a crawler crawler_config = columns.Text() # A field that represents a raw JSON content extra_data = columns.Text() latitude = columns.Float() longitude = columns.Float() coordinates = columns.Text() class Meta: get_pk_field = '_id' def validate(self): super(Company, self).validate() if self.name == "test": raise ValidationError('The company name cannot be test')
class Task(CustomDjangoCassandraModel): """ Represents a task that could be an on demand task or a batch task. Args: task_id: the task id that is the unique partition key. user: The user that asked for the task, if it is an ondemand task. created_at: the date of the creation of the task. updated_at: the date that we last updated the task. is_deleted: controls if the data is deleted. status: representes the actual status of the task, could be: - 0 (Created) - 1 (Queued) - 2 (In Progress) - 3 (Finished) - 4 (Faulty) - 5 (Unknown) kind: the name of the crawler that will execute the task. params: the set of params used to execute the crawler command, this will be saved as Text. params_map: the exactly same content as `params` but saved on a way that we can search using solr (KeyEncodedMap). options: the set of options that is used to guide the crawler during the execution, this will be saved as text. options_map: the exactly same content as `options` but saved on a way that we can search using solr (KeyEncodedMap). times_performed: keep track on how many times the task was run. type: the type of the task, could be OnDemand(1) or Batch(2) """ __table_name__ = "davinci_task" _cassandra_consistency_level_read = ConsistencyLevel.ONE _cassandra_consistency_level_write = ConsistencyLevel.ALL # Force that all the values will reside in the seam node of the cluster task_id = columns.UUID(partition_key=True, default=uuid.uuid4) # The owner of the data. Who own's the company data persisted user = columns.Text() # When was created the entity and the last modification date created_at = columns.DateTime(default=timezone.now, primary_key=True, clustering_order="DESC") updated_at = columns.DateTime(default=timezone.now) # Controls if the entity is active or has been deleted is_deleted = columns.Boolean(default=False) status = columns.SmallInt(default=STATUS_CREATED) kind = columns.Text(required=True) params_map = KeyEncodedMap(key_type=columns.Text, value_type=columns.Text) params = columns.Text(required=True) options_map = KeyEncodedMap(key_type=columns.Text, value_type=columns.Text) options = columns.Text(required=False) times_performed = columns.SmallInt(default=0) type = columns.SmallInt(default=ON_DEMAND_TASK) more_info = columns.List(value_type=UserDefinedType(TaskMoreInfo)) differences_from_last_version = columns.Text() inserted_fields = columns.List(value_type=columns.Text) updated_fields = columns.List(value_type=columns.Text) deleted_fields = columns.List(value_type=columns.Text) changed_fields = columns.List(value_type=columns.Text) logging_task = columns.Boolean(default=False) class Meta: get_pk_field = "task_id" def validate(self): super().validate() if self.type not in ALL_TASK_TYPES: raise ValidationError("Invalid task type [{0}]. Valid types are: " "{1}.".format(self.type, ALL_TASK_TYPES)) if self.status not in ALL_STATUS: raise ValidationError( "Invalid task status [{0}]. Valid status are: " "{1}.".format(self.status, ALL_STATUS))
def test_instantiation_with_column_instance(self): """ Tests that columns instantiated with a column instance work properly """ column = columns.List(columns.Text(min_length=100)) self.assertIsInstance(column.value_col, columns.Text)
class TestListModel(Model): partition = columns.UUID(primary_key=True, default=uuid4) int_list = columns.List(columns.Integer, required=False) text_list = columns.List(columns.Text, required=False)
class User(Model): """User Model This is used to store a Radon user :param uuid: A uuid associated to the user :type uuid: :class:`columns.Text` :param name: The user name, used as the primary key :type name: :class:`columns.Text` :param email: The user email :type email: :class:`columns.Text` :param password: The user password, stored hashed :type password: :class:`columns.Text` :param administrator: A boolean if the user has admin access :type administrator: :class:`columns.Boolean` :param active: A boolean if the user is active or not :type active: :class:`columns.Boolean` :param ldap: A boolean if the user password has to be checked on a LDAP server :type ldap: :class:`columns.Boolean` :param groups: A list of group names :type groups: :class:`columns.List` """ uuid = columns.Text(default=default_uuid) name = columns.Text(primary_key=True, required=True) email = columns.Text(required=True) password = columns.Text(required=True) administrator = columns.Boolean(required=True, default=False) active = columns.Boolean(required=True, default=True) ldap = columns.Boolean(required=True, default=False) groups = columns.List(columns.Text, index=True) def add_group(self, groupname, username=None): """ Add the user to a group :param groupname: The group to be added to :type groupname: str :param username: the name of the user who made the action :type username: str, optional """ self.add_groups([groupname], username) def add_groups(self, ls_group, username=None): """ Add the user to a list of groups :param ls_group: The groups to be added to :type groupname: List[str] :param username: the name of the user who made the action :type username: str, optional """ new_groups = self.get_groups() + ls_group # remove duplicate new_groups = list(set(new_groups)) self.update(groups=new_groups, username=username) def authenticate(self, password): """ Check user password against an existing hash (hash) :param password: the password we want to test (plain) :type password: str :return: a boolean which indicate if the password is correct :rtype: bool """ if self.active: if self.ldap: return verify_ldap_password(self.name, password) else: return verify_password(password, self.password) return False @classmethod def create(cls, **kwargs): """Create a user We intercept the create call so that we can correctly hash the password into an unreadable form :param name: the name of the user :type name: str :param password: The plain password to encrypt :type password: str :param username: the name of the user who made the action :type username: str, optional :return: The new created user :rtype: :class:`radon.model.User` """ # username is the name of the user who initiated the call, it has to # be removed for the Cassandra call if "username" in kwargs: username = kwargs["username"] del kwargs["username"] else: username = radon.cfg.sys_lib_user kwargs["password"] = encrypt_password(kwargs["password"]) if cls.objects.filter(name=kwargs["name"]).count(): raise UserConflictError(kwargs["name"]) user = super(User, cls).create(**kwargs) state = user.mqtt_get_state() payload = user.mqtt_payload({}, state) Notification.create_user(username, user.name, payload) return user def delete(self, username=None): """ Delete the user in the database. :param username: the name of the user who made the action :type username: str, optional """ state = self.mqtt_get_state() super(User, self).delete() payload = self.mqtt_payload(state, {}) # username is the id of the user who did the operation # user.uuid is the id of the new user Notification.delete_user(username, self.name, payload) @classmethod def find(cls, name): """ Find a user from his name. :param name: the name of the user :type name: str :return: The user which has been found :rtype: :class:`radon.model.User` """ return cls.objects.filter(name=name).first() def get_groups(self): """ Return the list of group names for the user :return: The list of groups :rtype: List[str] """ return self.groups def is_active(self): """ Check if the user is active :return: The user status :rtype: bool """ return self.active def is_authenticated(self): """ Check if the user is authenticated :return: The user status :rtype: bool """ return True def mqtt_get_state(self): """ Get the user state that will be used in the payload :return: The user state as a dictionary :rtype: dict """ payload = dict() payload["uuid"] = self.uuid payload["name"] = self.name payload["email"] = self.email payload["active"] = self.active payload["groups"] = [g.name for g in Group.find_all(self.groups)] return payload def mqtt_payload(self, pre_state, post_state): """ Get a string version of the payload of the message, with the pre and post states. The pre and post states are stored in a dictionary and dumped in a JSON string. :param pre_state: The dictionary which describes the state of the user before a modification :type pre_state: dict :param post_state: The dictionary which describes the state of the user after a modification :type post_state: dict :return: The payload as a JSON string :rtype: str """ payload = dict() payload["pre"] = pre_state payload["post"] = post_state return json.dumps(payload, default=datetime_serializer) def rm_group(self, groupname, username=None): """ Remove the user from a group. :param groupname: The group to be removed from :type groupname: str :param username: the name of the user who made the action :type username: str, optional """ self.rm_groups([groupname]) def rm_groups(self, ls_group, username=None): """ Remove the user from a list of groups. :param groupname: The groups to be removed from :type groupname: List[str] :param username: the name of the user who made the action :type username: str, optional """ new_groups = set(self.get_groups()) - set(ls_group) # remove duplicate new_groups = list(set(new_groups)) self.update(groups=new_groups, username=username) def to_dict(self): """ Return a dictionary which describes a resource for the web ui :return: The dictionary with the information needed for the UI :rtype: dict """ return { "uuid": self.uuid, "name": self.name, "email": self.email, "administrator": self.administrator, "active": self.active, "ldap": self.ldap, "groups": [g.to_dict() for g in Group.find_all(self.groups)], } def update(self, **kwargs): """ Update a user. We intercept the call to encrypt the password if we modify it. :param username: the name of the user who made the action :type username: str, optional :param password: The plain password to encrypt :type password: str :return: The modified user :rtype: :class:`radon.model.User` """ pre_state = self.mqtt_get_state() # If we want to update the password we need to encrypt it first if "password" in kwargs: kwargs["password"] = encrypt_password(kwargs["password"]) if "username" in kwargs: username = kwargs["username"] del kwargs["username"] else: username = None super(User, self).update(**kwargs) user = User.find(self.name) post_state = user.mqtt_get_state() payload = user.mqtt_payload(pre_state, post_state) Notification.update_user(username, user.name, payload) return self