def _create_complex_properties_from_class(self,
                                              schema_class: dict) -> None:
        """
        Add crossreferences to already existing class.

        Parameters
        ----------
        schema_class : dict
            Description of the class that should be added.

        Raises
        ------
        requests.ConnectionError
            If the network connection to weaviate fails.
        weaviate.UnexpectedStatusCodeException
            If weaviate reports a none OK status.
        """

        if "properties" not in schema_class:
            # Class has no properties nothing to do
            return
        for property_ in schema_class["properties"]:

            if _property_is_primitive(property_["dataType"]):
                continue

            # create the property object
            ## All complex dataTypes should be capitalized.
            schema_property = {
                "dataType": [
                    _capitalize_first_letter(dtype)
                    for dtype in property_["dataType"]
                ],
                "description":
                property_["description"],
                "name":
                property_["name"]
            }

            if "indexInverted" in property_:
                schema_property["indexInverted"] = property_["indexInverted"]

            if "moduleConfig" in property_:
                schema_property["moduleConfig"] = property_["moduleConfig"]

            path = "/schema/" + _capitalize_first_letter(
                schema_class["class"]) + "/properties"
            try:
                response = self._connection.post(
                    path=path, weaviate_object=schema_property)
            except RequestsConnectionError as conn_err:
                raise RequestsConnectionError('Property may not have been created properly.')\
                    from conn_err
            if response.status_code != 200:
                raise UnexpectedStatusCodeException(
                    "Add properties to classes", response)
Example #2
0
 def helper_test(nr_calls=1):
     mock_rest = mock_connection_method('post')
     schema = Schema(mock_rest)
     schema._create_complex_properties_from_class(properties)
     self.assertEqual(mock_rest.post.call_count, nr_calls)
     properties_copy = deepcopy(properties['properties'])
     for prop in properties_copy:
         prop['dataType'] = [
             _capitalize_first_letter(dt) for dt in prop['dataType']
         ]
     mock_rest.post.assert_called_with(
         path="/schema/" +
         _capitalize_first_letter(properties["class"]) + "/properties",
         weaviate_object=properties_copy[0])
    def add_reference(self, from_object_uuid: str, from_object_class_name: str,
                      from_property_name: str, to_object_uuid: str) -> None:
        """
        Add one reference to this batch.

        Parameters
        ----------
        from_object_uuid : str
            The UUID or URL of the object that should reference another object.
        from_object_class_name : str
            The name of the class that should reference another object.
        from_property_name : str
            The name of the property that contains the reference.
        to_object_uuid : str
            The UUID or URL of the object that is actually referenced.

        Raises
        ------
        TypeError
            If arguments are not of type str.
        ValueError
            If 'uuid' is not valid or cannot be extracted.
        """

        self._reference_batch.add(
            from_object_class_name=_capitalize_first_letter(
                from_object_class_name),
            from_object_uuid=from_object_uuid,
            from_property_name=from_property_name,
            to_object_uuid=to_object_uuid,
        )

        if self._batching_type:
            self._auto_create()
    def update_config(self, class_name: str, config: dict) -> None:
        """
        Update a schema configuration for a specific class.

        Parameters
        ----------
        class_name : str
            The class for which to update the schema configuration.
        config : dict
            The configurations to update (MUST follow schema format).

        Raises
        ------
        requests.ConnectionError
            If the network connection to weaviate fails.
        weaviate.UnexpectedStatusCodeException
            If weaviate reports a none OK status.
        """

        class_name = _capitalize_first_letter(class_name)
        class_schema = self.get(class_name)
        new_class_schema = _update_nested_dict(class_schema, config)
        check_class(new_class_schema)

        path = "/schema/" + class_name
        try:
            response = self._connection.put(path=path,
                                            weaviate_object=new_class_schema)
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError('Class schema configuration could not be updated.')\
                from conn_err
        if response.status_code != 200:
            raise UnexpectedStatusCodeException(
                "Update class schema configuration", response)
    def create(self, schema_class_name: str, schema_property: dict) -> None:
        """
        Create a class property.

        Parameters
        ----------
        schema_class_name : str
            The name of the class in the schema to which the property
            should be added.
        schema_property : dict
            The property that should be added.

        Examples
        --------
        >>> property_age = {
        ...     "dataType": [
        ...         "int"
        ...     ],
        ...     "description": "The Author's age",
        ...     "name": "age"
        ... }
        >>> client.schema.property.create('Author', property_age)

        Raises
        ------
        TypeError
            If 'schema_class_name' is of wrong type.
        weaviate.exceptions.UnexpectedStatusCodeException
            If weaviate reports a none OK status.
        requests.ConnectionError
            If the network connection to weaviate fails.
        weaviate.SchemaValidationException
            If the 'schema_property' is not valid.
        """

        if not isinstance(schema_class_name, str):
            raise TypeError(
                f"Class name must be of type str but is {type(schema_class_name)}"
            )

        loaded_schema_property = _get_dict_from_object(schema_property)

        # check if valid property
        check_property(loaded_schema_property)

        schema_class_name = _capitalize_first_letter(schema_class_name)

        path = f"/schema/{schema_class_name}/properties"
        try:
            response = self._connection.post(
                path=path, weaviate_object=loaded_schema_property)
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError(
                'Property was created properly.') from conn_err
        if response.status_code != 200:
            raise UnexpectedStatusCodeException("Add property to class",
                                                response)
    def _create_class_with_premitives(self, weaviate_class: dict) -> None:
        """
        Create class with only primitives.

        Parameters
        ----------
        weaviate_class : dict
            A single weaviate formated class

        Raises
        ------
        requests.ConnectionError
            If the network connection to weaviate fails.
        weaviate.UnexpectedStatusCodeException
            If weaviate reports a none OK status.
        """

        # Create the class
        schema_class = {
            "class": _capitalize_first_letter(weaviate_class['class']),
            "properties": []
        }

        if "description" in weaviate_class:
            schema_class['description'] = weaviate_class['description']

        if "vectorIndexType" in weaviate_class:
            schema_class['vectorIndexType'] = weaviate_class['vectorIndexType']

        if "vectorIndexConfig" in weaviate_class:
            schema_class['vectorIndexConfig'] = weaviate_class[
                'vectorIndexConfig']

        if "vectorizer" in weaviate_class:
            schema_class['vectorizer'] = weaviate_class['vectorizer']

        if "moduleConfig" in weaviate_class:
            schema_class["moduleConfig"] = weaviate_class["moduleConfig"]

        if "shardingConfig" in weaviate_class:
            schema_class["shardingConfig"] = weaviate_class["shardingConfig"]

        if "properties" in weaviate_class:
            schema_class["properties"] = _get_primitive_properties(
                weaviate_class["properties"])

        # Add the item
        try:
            response = self._connection.post(path="/schema",
                                             weaviate_object=schema_class)
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError('Class may not have been created properly.')\
                from conn_err
        if response.status_code != 200:
            raise UnexpectedStatusCodeException("Create class", response)
    def __init__(self, class_name: str, properties: Union[List[str], str],
                 connection: Connection):
        """
        Initialize a GetBuilder class instance.

        Parameters
        ----------
        class_name : str
            Class name of the objects to interact with.
        properties : str or list of str
            Properties of the objects to interact with.
        connection : weaviate.connect.Connection
            Connection object to an active and running Weaviate instance.

        Raises
        ------
        TypeError
            If argument/s is/are of wrong type.
        """

        super().__init__(connection)

        if not isinstance(class_name, str):
            raise TypeError(
                f"class name must be of type str but was {type(class_name)}")
        if not isinstance(properties, (list, str)):
            raise TypeError("properties must be of type str or "
                            f"list of str but was {type(properties)}")
        if isinstance(properties, str):
            properties = [properties]
        for prop in properties:
            if not isinstance(prop, str):
                raise TypeError("All the `properties` must be of type `str`!")

        self._class_name: str = _capitalize_first_letter(class_name)
        self._properties: List[str] = properties
        self._additional: dict = {'__one_level': set()}
        # '__one_level' refers to the additional properties that are just a single word, not a dict
        # thus '__one_level', only one level of complexity
        self._where: Optional[
            Where] = None  # To store the where filter if it is added
        self._limit: Optional[
            str] = None  # To store the limit filter if it is added
        self._offset: Optional[
            str] = None  # To store the offset filter if it is added
        self._near_ask: Optional[
            Filter] = None  # To store the `near`/`ask` clause if it is added
        self._contains_filter = False  # true if any filter is added
    def with_class_name(self, class_name: str) -> 'ConfigBuilder':
        """
        What Object type to classify.

        Parameters
        ----------
        class_name : str
            Name of the class to be classified.

        Returns
        -------
        ConfigBuilder
            Updated ConfigBuilder.
        """

        self._config["class"] = _capitalize_first_letter(class_name)
        return self
    def __init__(self, class_name: str, connection: Connection):
        """
        Initialize a AggregateBuilder class instance.

        Parameters
        ----------
        class_name : str
            Class name of the objects to be aggregated.
        connection : weaviate.connect.Connection
            Connection object to an active and running Weaviate instance.
        """

        super().__init__(connection)
        self._class_name = _capitalize_first_letter(class_name)
        self._with_meta_count = False
        self._fields: List[str] = []
        self._where: Optional[Where] = None
        self._group_by_properties: Optional[List[str]] = None
        self._uses_filter = False
    def add_data_object(self,
                        data_object: dict,
                        class_name: str,
                        uuid: Optional[str] = None,
                        vector: Optional[Sequence] = None) -> None:
        """
        Add one object to this batch.
        NOTE: If the UUID of one of the objects already exists then the existing object will be
        replaced by the new object.

        Parameters
        ----------
        data_object : dict
            Object to be added as a dict datatype.
        class_name : str
            The name of the class this object belongs to.
        uuid : str, optional
            UUID of the object as a string, by default None
        vector: Sequence, optional
            The embedding of the object that should be created. Used only class objects that do not
            have a vectorization module. Supported types are `list`, 'numpy.ndarray`,
            `torch.Tensor` and `tf.Tensor`,
            by default None.

        Raises
        ------
        TypeError
            If an argument passed is not of an appropriate type.
        ValueError
            If 'uuid' is not of a propper form.
        """

        self._objects_batch.add(
            class_name=_capitalize_first_letter(class_name),
            data_object=data_object,
            uuid=uuid,
            vector=vector,
        )

        if self._batching_type:
            self._auto_create()
    def validate(self,
            data_object: Union[dict, str],
            class_name: str,
            uuid: Union[str, uuid_lib.UUID, None]=None,
            vector: Sequence=None
        ) -> dict:
        """
        Validate an object against weaviate.

        Parameters
        ----------
        data_object : dict or str
            Object to be validated.
            If type is str it should be either an URL or a file.
        class_name : str
            Name of the class of the object that should be validated.
        uuid : str, uuid.UUID or None, optional
            The UUID of the object that should be validated against weaviate.
            by default None.
        vector: Sequence, optional
            The embedding of the object that should be validated. Used only class objects that
            do not have a vectorization module. Supported types are `list`, 'numpy.ndarray`,
            `torch.Tensor` and `tf.Tensor`,
            by default None.

        Examples
        --------
        Assume we have a Author class only 'name' property, NO 'age'.

        >>> client1.data_object.validate(
        ...     data_object = {'name': 'H. Lovecraft'},
        ...     class_name = 'Author'
        ... )
        {'error': None, 'valid': True}
        >>> client1.data_object.validate(
        ...     data_object = {'name': 'H. Lovecraft', 'age': 46},
        ...     class_name = 'Author'
        ... )
        {
            "error": [
                {
                "message": "invalid object: no such prop with name 'age' found in class 'Author'
                    in the schema. Check your schema files for which properties in this class are
                    available"
                }
            ],
            "valid": false
        }

        Returns
        -------
        dict
            Validation result. E.g. {"valid": bool, "error": None or list}

        Raises
        ------
        TypeError
            If argument is of wrong type.
        ValueError
            If argument contains an invalid value.
        weaviate.UnexpectedStatusCodeException
            If validating the object against Weaviate failed with a different reason.
        requests.ConnectionError
            If the network connection to weaviate fails.
        """

        loaded_data_object = _get_dict_from_object(data_object)
        if not isinstance(class_name, str):
            raise TypeError(f"Expected class_name of type `str` but was: {type(class_name)}")

        weaviate_obj = {
            "class": _capitalize_first_letter(class_name),
            "properties": loaded_data_object
        }

        if uuid is not None:
            weaviate_obj['id'] = get_valid_uuid(uuid)

        if vector is not None:
            weaviate_obj['vector'] = get_vector(vector)

        path = "/objects/validate"
        try:
            response = self._connection.post(
                path=path,
                weaviate_object=weaviate_obj
            )
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError('Object was not validated against weaviate.')\
                from conn_err

        result: dict = {
            "error": None
        }

        if response.status_code == 200:
            result["valid"] = True
            return result
        if response.status_code == 422:
            result["valid"] = False
            result["error"] = response.json()["error"]
            return result
        raise UnexpectedStatusCodeException("Validate object", response)
    def create(self,
            data_object: Union[dict, str],
            class_name: str,
            uuid: Union[str, uuid_lib.UUID, None]=None,
            vector: Sequence=None
        ) -> str:
        """
        Takes a dict describing the object and adds it to weaviate.

        Parameters
        ----------
        data_object : dict or str
            Object to be added.
            If type is str it should be either an URL or a file.
        class_name : str
            Class name associated with the object given.
        uuid : str, uuid.UUID or None, optional
            Object will be created under this uuid if it is provided.
            Otherwise weaviate will generate a uuid for this object,
            by default None.
        vector: Sequence, optional
            The embedding of the object that should be created. Used only class objects that do not
            have a vectorization module. Supported types are `list`, 'numpy.ndarray`,
            `torch.Tensor` and `tf.Tensor`,
            by default None.

        Examples
        --------
        Schema contains a class Author with only 'name' and 'age' primitive property.

        >>> client.data_object.create(
        ...     data_object = {'name': 'Neil Gaiman', 'age': 60},
        ...     class_name = 'Author',
        ... )
        '46091506-e3a0-41a4-9597-10e3064d8e2d'
        >>> client.data_object.create(
        ...     data_object = {'name': 'Andrzej Sapkowski', 'age': 72},
        ...     class_name = 'Author',
        ...     uuid = 'e067f671-1202-42c6-848b-ff4d1eb804ab'
        ... )
        'e067f671-1202-42c6-848b-ff4d1eb804ab'

        Returns
        -------
        str
            Returns the UUID of the created object if successful.

        Raises
        ------
        TypeError
            If argument is of wrong type.
        ValueError
            If argument contains an invalid value.
        weaviate.ObjectAlreadyExistsException
            If an object with the given uuid already exists within weaviate.
        weaviate.UnexpectedStatusCodeException
            If creating the object in Weaviate failed for a different reason,
            more information is given in the exception.
        requests.ConnectionError
            If the network connection to weaviate fails.
        """

        if not isinstance(class_name, str):
            raise TypeError("Expected class_name of type str but was: "\
                            + str(type(class_name)))
        loaded_data_object = _get_dict_from_object(data_object)

        weaviate_obj = {
            "class": _capitalize_first_letter(class_name),
            "properties": loaded_data_object
        }
        if uuid is not None:
            weaviate_obj["id"] = get_valid_uuid(uuid)

        if vector is not None:
            weaviate_obj["vector"] = get_vector(vector)

        path = "/objects"
        try:
            response = self._connection.post(
                path=path,
                weaviate_object=weaviate_obj
            )
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError('Object was not added to Weaviate.') from conn_err
        if response.status_code == 200:
            return str(response.json()["id"])

        object_does_already_exist = False
        try:
            if 'already exists' in response.json()['error'][0]['message']:
                object_does_already_exist = True
        except KeyError:
            pass
        if object_does_already_exist:
            raise ObjectAlreadyExistsException(str(uuid))
        raise UnexpectedStatusCodeException("Creating object", response)
    def replace(self,
            data_object: Union[dict, str],
            class_name: str,
            uuid: Union[str, uuid_lib.UUID],
            vector: Sequence=None
        ) -> None:
        """
        Replace an already existing object with the given data object.
        This method replaces the whole object.

        Parameters
        ----------
        data_object : dict or str
            Describes the new values. It may be an URL or path to a json
            or a python dict describing the new values.
        class_name : str
            Name of the class of the object that should be updated.
        uuid : str or uuid.UUID
            The UUID of the object that should be changed.
        vector: Sequence, optional
            The embedding of the object that should be replaced. Used only class objects that do not
            have a vectorization module. Supported types are `list`, 'numpy.ndarray`,
            `torch.Tensor` and `tf.Tensor`,
            by default None.

        Examples
        --------
        >>> author_id = client.data_object.create(
        ...     data_object = {'name': 'H. Lovecraft', 'age': 46},
        ...     class_name = 'Author'
        ... )
        >>> client.data_object.get(author_id)
        {
            "additional": {},
            "class": "Author",
            "creationTimeUnix": 1617112817487,
            "id": "d842a0f4-ad8c-40eb-80b4-bfefc7b1b530",
            "lastUpdateTimeUnix": 1617112817487,
            "properties": {
                "age": 46,
                "name": "H. Lovecraft"
            },
            "vectorWeights": null
        }
        >>> client.data_object.replace(
        ...     data_object = {'name': 'H.P. Lovecraft'},
        ...     class_name = 'Author',
        ...     uuid = author_id
        ... )
        >>> client.data_object.get(author_id)
        {
            "additional": {},
            "class": "Author",
            "id": "d842a0f4-ad8c-40eb-80b4-bfefc7b1b530",
            "lastUpdateTimeUnix": 1617112838668,
            "properties": {
                "name": "H.P. Lovecraft"
            },
            "vectorWeights": null
        }

        Raises
        ------
        TypeError
            If argument is of wrong type.
        ValueError
            If argument contains an invalid value.
        requests.ConnectionError
            If the network connection to weaviate fails.
        weaviate.UnexpectedStatusCodeException
            If weaviate reports a none OK status.
        """

        parsed_object = _get_dict_from_object(data_object)
        uuid = get_valid_uuid(uuid)

        weaviate_obj = {
            "id": uuid,
            "class": _capitalize_first_letter(class_name),
            "properties": parsed_object
        }

        if vector is not None:
            weaviate_obj['vector'] = get_vector(vector)

        path = f"/objects/{uuid}"
        try:
            response = self._connection.put(
                path=path,
                weaviate_object=weaviate_obj
            )
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError('Object was not replaced.') from conn_err
        if response.status_code == 200:
            # Successful update
            return
        raise UnexpectedStatusCodeException("Replace object", response)
    def update(self,
            data_object: Union[dict, str],
            class_name: str,
            uuid: Union[str, uuid_lib.UUID],
            vector: Sequence=None
        ) -> None:
        """
        Update the given object with the already existing object in weaviate.
        Overwrites only the specified fields, the unspecified ones remain unchanged.

        Parameters
        ----------
        data_object : dict or str
            The object states the fields that should be updated.
            Fields not specified by in the 'data_object' remain unchanged.
            Fields that are None will not be changed.
            If type is str it should be either an URL or a file.
        class_name : str
            The class name of the object.
        uuid : str or uuid.UUID
            The ID of the object that should be changed.
        vector: Sequence, optional
            The embedding of the object that should be updated. Used only class objects that do not
            have a vectorization module. Supported types are `list`, 'numpy.ndarray`,
            `torch.Tensor` and `tf.Tensor`,
            by default None.

        Examples
        --------
        >>> author_id = client.data_object.create(
        ...     data_object = {'name': 'Philip Pullman', 'age': 64},
        ...     class_name = 'Author'
        ... )
        >>> client.data_object.get(author_id)
        {
            "additional": {},
            "class": "Author",
            "creationTimeUnix": 1617111215172,
            "id": "bec2bca7-264f-452a-a5bb-427eb4add068",
            "lastUpdateTimeUnix": 1617111215172,
            "properties": {
                "age": 64,
                "name": "Philip Pullman"
            },
            "vectorWeights": null
        }
        >>> client.data_object.update(
        ...     data_object = {'age': 74},
        ...     class_name = 'Author',
        ...     uuid = author_id
        ... )
        >>> client.data_object.get(author_id)
        {
            "additional": {},
            "class": "Author",
            "creationTimeUnix": 1617111215172,
            "id": "bec2bca7-264f-452a-a5bb-427eb4add068",
            "lastUpdateTimeUnix": 1617111215172,
            "properties": {
                "age": 74,
                "name": "Philip Pullman"
            },
            "vectorWeights": null
        }

        Raises
        ------
        TypeError
            If argument is of wrong type.
        ValueError
            If argument contains an invalid value.
        requests.ConnectionError
            If the network connection to weaviate fails.
        weaviate.UnexpectedStatusCodeException
            If weaviate reports a none successful status.
        """

        if not isinstance(class_name, str):
            raise TypeError("Class must be type str")

        uuid = get_valid_uuid(uuid)

        object_dict = _get_dict_from_object(data_object)

        weaviate_obj = {
            "id": uuid,
            "class": _capitalize_first_letter(class_name),
            "properties": object_dict
        }

        if vector is not None:
            weaviate_obj['vector'] = get_vector(vector)

        path = f"/objects/{uuid}"

        try:
            response = self._connection.patch(
                path=path,
                weaviate_object=weaviate_obj
            )
        except RequestsConnectionError as conn_err:
            raise RequestsConnectionError('Object was not updated.') from conn_err
        if response.status_code == 204:
            # Successful merge
            return
        raise UnexpectedStatusCodeException("Update of the object not successful", response)