class DigitalTwinsClient(object): # type: ignore #pylint: disable=too-many-public-methods """Creates an instance of AzureDigitalTwinsAPI. :param str endpoint: The URL endpoint of an Azure search service :param ~azure.core.credentials.AzureKeyCredential credential: A credential to authenticate requests to the service """ def __init__(self, endpoint, credential, **kwargs): # type: (str, AzureKeyCredential, **Any) -> None client_models = { k: v for k, v in models.__dict__.items() if isinstance(v, type) } self._serialize = Serializer(client_models) self._deserialize = Deserializer(client_models) self.endpoint = endpoint #type: str self.credential = credential #type AzureKeyCredential self._client = AzureDigitalTwinsAPI( credential=credential, base_url=endpoint, **kwargs) #type: AzureDigitalTwinsAPI async def close(self) -> None: await self._client.close() async def __aenter__(self) -> "DigitalTwinsClient": await self._client.__aenter__() return self async def __aexit__(self, *exc_details) -> None: await self._client.__aexit__(*exc_details) @distributed_trace_async async def get_digital_twin(self, digital_twin_id, **kwargs): # type: (str, **Any) -> Dict[str, object] """Get a digital twin. :param str digital_twin_id: The Id of the digital twin. :return: Dictionary containing the twin. :rtype: Dict[str, object] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If the digital twin doesn't exist. """ return await self._client.digital_twins.get_by_id( digital_twin_id, **kwargs) @distributed_trace_async async def upsert_digital_twin(self, digital_twin_id, digital_twin, **kwargs): # type: (str, Dict[str, object], **Any) -> Dict[str, object] """Create or update a digital twin. :param str digital_twin_id: The Id of the digital twin. :param Dict[str, object] digital_twin: Dictionary containing the twin to create or update. :return: Dictionary containing the created or updated twin. :rtype: Dict[str, object] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceExistsError`: If the digital twin is already exist. """ return await self._client.digital_twins.add(digital_twin_id, digital_twin, **kwargs) @distributed_trace_async async def update_digital_twin(self, digital_twin_id, json_patch, **kwargs): # type: (str, Dict[str, object], **Any) -> None """Update a digital twin using a json patch. :param str digital_twin_id: The Id of the digital twin. :param Dict[str, object] json_patch: An update specification described by JSON Patch. Updates to property values and $model elements may happen in the same request. Operations are limited to add, replace and remove. :keyword str etag: Only perform the operation if the entity's etag matches one of the etags provided or * is provided. :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no digital twin with the provided id. """ etag = kwargs.get("etag", None) match_condition = kwargs.get("match_condition", MatchConditions.Unconditionally) return await self._client.digital_twins.update(digital_twin_id, json_patch, if_match=prep_if_match( etag, match_condition), **kwargs) @distributed_trace_async async def delete_digital_twin(self, digital_twin_id, **kwargs): # type: (str, **Any) -> None """Delete a digital twin. :param str digital_twin_id: The Id of the digital twin. :keyword str etag: Only perform the operation if the entity's etag matches one of the etags provided or * is provided. :keyword ~azure.core.MatchConditions match_condition: the match condition to use upon the etag :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no digital twin with the provided id. """ etag = kwargs.get("etag", None) match_condition = kwargs.get("match_condition", MatchConditions.Unconditionally) return await self._client.digital_twins.delete(digital_twin_id, if_match=prep_if_match( etag, match_condition), **kwargs) @distributed_trace_async async def get_component(self, digital_twin_id, component_path, **kwargs): # type: (str, str, **Any) -> Dict[str, object] """Get a component on a digital twin. :param str digital_twin_id: The Id of the digital twin. :param str component_path: The component being retrieved. :return: Dictionary containing the component. :rtype: Dict[str, object] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is either no digital twin with the provided id or the component path is invalid. """ return await self._client.digital_twins.get_component( digital_twin_id, component_path, **kwargs) @distributed_trace_async async def update_component(self, digital_twin_id, component_path, json_patch, **kwargs): # type: (str, str, Dict[str, object], **Any) -> None """Update properties of a component on a digital twin using a JSON patch. :param str digital_twin_id: The Id of the digital twin. :param str component_path: The component being updated. :param Dict[str, object] json_patch: An update specification described by JSON Patch. :keyword str etag: Only perform the operation if the entity's etag matches one of the etags provided or * is provided. :keyword ~azure.core.MatchConditions match_condition: the match condition to use upon the etag :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is either no digital twin with the provided id or the component path is invalid. """ etag = kwargs.get("etag", None) match_condition = kwargs.get("match_condition", MatchConditions.Unconditionally) return await self._client.digital_twins.update_component( digital_twin_id, component_path, patch_document=json_patch, if_match=prep_if_match(etag, match_condition), **kwargs) @distributed_trace_async async def get_relationship(self, digital_twin_id, relationship_id, **kwargs): # type: (str, str, **Any) -> Dict[str, object] """Get a relationship on a digital twin. :param str digital_twin_id: The Id of the digital twin. :param str relationship_id: The Id of the relationship to retrieve. :return: Dictionary containing the relationship. :rtype: Dict[str, object] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is either no digital twin or relationship with the provided id. """ return await self._client.digital_twins.get_relationship_by_id( digital_twin_id, relationship_id, **kwargs) @distributed_trace_async async def upsert_relationship(self, digital_twin_id, relationship_id, relationship=None, **kwargs): # type: (str, str, Optional[Dict[str, object]], **Any) -> Dict[str, object] """Create or update a relationship on a digital twin. :param str digital_twin_id: The Id of the digital twin. :param str relationship_id: The Id of the relationship to retrieve. :param Dict[str, object] relationship: Dictionary containing the relationship. :return: The created or updated relationship. :rtype: Dict[str, object] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is either no digital twin, target digital twin or relationship with the provided id. """ return await self._client.digital_twins.add_relationship( id=digital_twin_id, relationship_id=relationship_id, relationship=relationship, **kwargs) @distributed_trace_async async def update_relationship(self, digital_twin_id, relationship_id, json_patch=None, **kwargs): # type: (str, str, Dict[str, object], **Any) -> None """Updates the properties of a relationship on a digital twin using a JSON patch. :param str digital_twin_id: The Id of the digital twin. :param str relationship_id: The Id of the relationship to retrieve. :param Dict[str, object] json_patch: JSON Patch description of the update to the relationship properties. :keyword str etag: Only perform the operation if the entity's etag matches one of the etags provided or * is provided. :keyword ~azure.core.MatchConditions match_condition: the match condition to use upon the etag :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is either no digital twin or relationship with the provided id. """ etag = kwargs.get("etag", None) match_condition = kwargs.get("match_condition", MatchConditions.Unconditionally) return await self._client.digital_twins.update_relationship( id=digital_twin_id, relationship_id=relationship_id, json_patch=json_patch, if_match=prep_if_match(etag, match_condition), **kwargs) @distributed_trace_async async def delete_relationship(self, digital_twin_id, relationship_id, **kwargs): # type: (str, str, **Any) -> None """Delete a digital twin. :param str digital_twin_id: The Id of the digital twin. :param str relationship_id: The Id of the relationship to delete. :keyword str etag: Only perform the operation if the entity's etag matches one of the etags provided or * is provided. :keyword ~azure.core.MatchConditions match_condition: The match condition to use upon the etag. :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is either no digital twin or relationship with the provided id. """ etag = kwargs.get("etag", None) match_condition = kwargs.get("match_condition", MatchConditions.Unconditionally) return await self._client.digital_twins.delete_relationship( digital_twin_id, relationship_id, if_match=prep_if_match(etag, match_condition), **kwargs) @distributed_trace_async async def list_relationships(self, digital_twin_id, relationship_id=None, **kwargs): # type: (str, Optional[str], **Any) -> ~AsyncItemPaged[~azure.digitaltwins.models.Relationship] """Retrieve relationships for a digital twin. :param str digital_twin_id: The Id of the digital twin. :param str relationship_id: The Id of the relationship to get (if None all the relationship will be retrieved). :return: An iterator instance of list of Relationship :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.digitaltwins.models.Relationship] :raises: ~azure.core.exceptions.HttpResponseError :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no digital twin with the provided id. """ return await self._client.digital_twins.list_relationships( digital_twin_id, relationship_name=relationship_id, **kwargs) @distributed_trace_async async def list_incoming_relationships(self, digital_twin_id, **kwargs): # type: (str, str, **Any) -> ~azure.core.paging.AsyncItemPaged[~azure.digitaltwins.models.IncomingRelationship] """Retrieve all incoming relationships for a digital twin. :param str digital_twin_id: The Id of the digital twin. :return: An iterator like instance of either Relationship. :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.digitaltwins.models.IncomingRelationship] :raises: ~azure.core.exceptions.HttpResponseError :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no digital twin with the provided id. """ return await self._client.digital_twins.list_incoming_relationships( digital_twin_id, **kwargs) @distributed_trace_async async def publish_telemetry(self, digital_twin_id, payload, message_id=None, **kwargs): # type: (str, object, Optional[str], **Any) -> None """Publish telemetry from a digital twin, which is then consumed by one or many destination endpoints (subscribers) defined under. :param str digital_twin_id: The Id of the digital twin :param object payload: The telemetry payload to be sent :param str message_id: The message Id :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no digital twin with the provided id. """ if not message_id: message_id = uuid.UUID timestamp = datetime.now return await self._client.digital_twins.send_telemetry( digital_twin_id, message_id, telemetry=payload, dt_timestamp=timestamp, **kwargs) @distributed_trace_async async def publish_component_telemetry(self, digital_twin_id, component_path, payload, message_id=None, **kwargs): # type: (str, str, object, Optional[str], **Any) -> None """Publish telemetry from a digital twin's component, which is then consumed by one or many destination endpoints (subscribers) defined under. :param str digital_twin_id: The Id of the digital twin. :param str component_path: The name of the DTDL component. :param object payload: The telemetry payload to be sent. :param str message_id: The message Id. :return: None :rtype: None :raises: ~azure.core.exceptions.HttpResponseError :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no digital twin with the provided id or the component path is invalid. """ if not message_id: message_id = uuid.UUID timestamp = datetime.now return await self._client.digital_twins.send_component_telemetry( digital_twin_id, component_path, dt_id=message_id, telemetry=payload, dt_timestamp=timestamp, **kwargs) @distributed_trace_async async def get_model(self, model_id, **kwargs): # type: (str, **Any) -> ~azure.digitaltwins.models.ModelData """Get a model, including the model metadata and the model definition. :param str model_id: The Id of the model. :keyword bool include_model_definition: When true the model definition will be returned as part of the result. :return: The ModelDate object. :rtype: ~azure.digitaltwins.models.ModelData :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: If there is no model with the provided id. """ include_model_definition = kwargs.get("include_model_definition", False) return await self._client.digital_twin_models.get_by_id( model_id, include_model_definition, **kwargs) @distributed_trace_async async def list_models(self, dependencies_for, **kwargs): # type: (str, bool, int, **Any) -> ~azure.core.paging.AsyncItemPaged[~azure.digitaltwins.models.ModelData] """Get the list of models. :param List[str] dependencies_for: The model Ids to have dependencies retrieved. If omitted, all models are retrieved. :keyword bool include_model_definition: When true the model definition will be returned as part of the result. :keyword int results_per_page: The maximum number of items to retrieve per request. The server may choose to return less than the requested max. :return: An iterator instance of list of ModelData. :rtype: ~azure.core.paging.AsyncItemPaged[~azure.digitaltwins.models.ModelData] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. """ include_model_definition = kwargs.pop('include_model_definition', False) results_per_page = kwargs.pop('results_per_page', None) digital_twin_models_list_options = None if results_per_page is not None: digital_twin_models_list_options = { 'max_item_count': results_per_page } return await self._client.digital_twin_models.list( dependencies_for=dependencies_for, include_model_definition=include_model_definition, digital_twin_models_list_options=digital_twin_models_list_options, **kwargs) @distributed_trace_async async def create_models(self, model_list=None, **kwargs): # type: (Optional[List[object]], **Any) -> List[~azure.digitaltwins.models.ModelData] """Create one or more models. When any error occurs, no models are uploaded. :param List[object] model_list: The set of models to create. Each string corresponds to exactly one model. :return: The list of ModelData :rtype: List[~azure.digitaltwins.models.ModelData] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: One or more of the provided models already exist. """ return await self._client.digital_twin_models.add(model_list, **kwargs) @distributed_trace_async async def decommission_model(self, model_id, **kwargs): # type: (str, **Any) -> None """Decommissions a model. :param str model_id: The id for the model. The id is globally unique and case sensitive. :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: There is no model with the provided id. """ json_patch = "{ 'op': 'replace', 'path': '/decommissioned', 'value': true }" return await self._client.digital_twin_models.update( model_id, json_patch, **kwargs) @distributed_trace_async async def delete_model(self, model_id, **kwargs): # type: (str, **Any) -> None """Decommission a model using a json patch. :param str model_id: The Id of the model to decommission. :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: If the request is invalid. :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: There is no model with the provided id. :raises :class: `~azure.core.exceptions.ResourceExistsError`: There are dependencies on the model that prevent it from being deleted. """ return await self._client.digital_twin_models.delete( model_id, **kwargs) @distributed_trace_async async def get_event_route(self, event_route_id, **kwargs): # type: (str, **Any) -> ~azure.digitaltwins.models.EventRoute """Get an event route. :param str event_route_id: The Id of the event route. :return: The EventRoute object. :rtype: ~azure.digitaltwins.models.EventRoute :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: There is no event route with the provided id. """ return await self._client.event_routes.get_by_id( event_route_id, **kwargs) @distributed_trace_async async def list_event_routes(self, **kwargs): # type: (**Any) -> ~azure.core.paging.AsyncItemPaged[~azure.digitaltwins.models.EventRoute] """Retrieves all event routes. :keyword int results_per_page: The maximum number of items to retrieve per request. The server may choose to return less than the requested max. :return: An iterator instance of list of EventRoute. :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.digitaltwins.models.EventRoute] :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: The request is invalid. """ event_routes_list_options = None results_per_page = kwargs.pop('results_per_page', None) if results_per_page is not None: event_routes_list_options = {'max_item_count': results_per_page} return await self._client.event_routes.list( event_routes_list_options=event_routes_list_options, **kwargs) @distributed_trace_async async def upsert_event_route(self, event_route_id, event_route, **kwargs): # type: (str, "models.EventRoute", **Any) -> None """Create or update an event route. :param str event_route_id: The Id of the event route to create or update. :param ~azure.digitaltwins.models.EventRoute event_route: The event route data. :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ServiceRequestError`: The request is invalid. """ return await self._client.event_routes.add(event_route_id, event_route, **kwargs) @distributed_trace_async async def delete_event_route(self, event_route_id, **kwargs): # type: (str, **Any) -> None """Delete an event route. :param str event_route_id: The Id of the event route to delete. :return: None :rtype: None :raises :class: `~azure.core.exceptions.HttpResponseError` :raises :class: `~azure.core.exceptions.ResourceNotFoundError`: There is no event route with the provided id. """ return await self._client.event_routes.delete(event_route_id, **kwargs) @distributed_trace_async async def query_twins(self, query_expression, **kwargs): # type: (str, **Any) -> ~azure.core.async_paging.AsyncItemPaged[Dict[str, object]] """Query for digital twins. :param str query_expression: The query expression to execute. :return: The QueryResult object. :rtype: ~azure.core.async_paging.AsyncItemPaged[Dict[str, object]] :raises :class: `~azure.core.exceptions.HttpResponseError` """ def extract_data(pipeline_response): deserialized = self._deserialize('QueryResult', pipeline_response) list_of_elem = deserialized.value return deserialized.continuation_token or None, iter(list_of_elem) async def get_next(continuation_token=None): query_spec = self._serialize.serialize_dict( { 'query': query_expression, 'continuation_token': continuation_token }, 'QuerySpecification') pipeline_response = await self._client.query.query_twins( query_spec, **kwargs) return pipeline_response return AsyncItemPaged(get_next, extract_data)
class TestRuntimeSerialized(unittest.TestCase): class TestObj(Model): _validation = {} _attribute_map = { 'attr_a': { 'key': 'id', 'type': 'str' }, 'attr_b': { 'key': 'AttrB', 'type': 'int' }, 'attr_c': { 'key': 'Key_C', 'type': 'bool' }, 'attr_d': { 'key': 'AttrD', 'type': '[int]' }, 'attr_e': { 'key': 'AttrE', 'type': '{float}' } } def __init__(self): self.attr_a = None self.attr_b = None self.attr_c = None self.attr_d = None self.attr_e = None def __str__(self): return "Test_Object" def setUp(self): self.s = Serializer() return super(TestRuntimeSerialized, self).setUp() def test_validate(self): # Assert not necessary, should not raise exception self.s.validate("simplestring", "StringForLog", pattern="^[a-z]+$") self.s.validate(u"UTF8ééééé", "StringForLog", pattern=r"^[\w]+$") def test_obj_serialize_none(self): """Test that serialize None in object is still None. """ obj = self.s.serialize_object({'test': None}) self.assertIsNone(obj['test']) def test_obj_without_attr_map(self): """ Test serializing an object with no attribute_map. """ test_obj = type("BadTestObj", (), {}) with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_obj_with_malformed_map(self): """ Test serializing an object with a malformed attribute_map. """ test_obj = type("BadTestObj", (Model, ), {"_attribute_map": None}) with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj._attribute_map = {"attr": "val"} with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj._attribute_map = {"attr": {"val": 1}} with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_obj_with_mismatched_map(self): """ Test serializing an object with mismatching attributes and map. """ test_obj = type("BadTestObj", (Model, ), {"_attribute_map": None}) test_obj._attribute_map = {"abc": {"key": "ABC", "type": "str"}} with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_attr_none(self): """ Test serializing an object with None attributes. """ test_obj = self.TestObj() message = self.s._serialize(test_obj) self.assertIsInstance(message, dict) self.assertFalse('id' in message) def test_attr_int(self): """ Test serializing an object with Int attributes. """ test_obj = self.TestObj() self.TestObj._validation = { 'attr_b': { 'required': True }, } test_obj.attr_b = None with self.assertRaises(ValidationError): self.s._serialize(test_obj) test_obj.attr_b = 25 message = self.s._serialize(test_obj) self.assertEqual(message['AttrB'], int(test_obj.attr_b)) test_obj.attr_b = "34534" message = self.s._serialize(test_obj) self.assertEqual(message['AttrB'], int(test_obj.attr_b)) test_obj.attr_b = "NotANumber" with self.assertRaises(SerializationError): self.s._serialize(test_obj) self.TestObj._validation = {} def test_attr_str(self): """ Test serializing an object with Str attributes. """ test_obj = self.TestObj() self.TestObj._validation = { 'attr_a': { 'required': True }, } test_obj.attr_a = None with self.assertRaises(ValidationError): self.s._serialize(test_obj) self.TestObj._validation = {} test_obj.attr_a = "TestString" message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) test_obj.attr_a = 1234 message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) test_obj.attr_a = list() message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) test_obj.attr_a = [1] message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) def test_attr_bool(self): """ Test serializing an object with bool attributes. """ test_obj = self.TestObj() test_obj.attr_c = True message = self.s._serialize(test_obj) self.assertEqual(message['Key_C'], True) test_obj.attr_c = "" message = self.s._serialize(test_obj) self.assertTrue('Key_C' in message) test_obj.attr_c = None message = self.s._serialize(test_obj) self.assertFalse('Key_C' in message) test_obj.attr_c = "NotEmpty" message = self.s._serialize(test_obj) self.assertEqual(message['Key_C'], True) def test_attr_sequence(self): """ Test serializing a sequence. """ test_obj = ["A", "B", "C"] output = self.s._serialize(test_obj, '[str]', div='|') self.assertEqual(output, "|".join(test_obj)) test_obj = [1, 2, 3] output = self.s._serialize(test_obj, '[str]', div=',') self.assertEqual(output, ",".join([str(i) for i in test_obj])) def test_attr_list_simple(self): """ Test serializing an object with simple-typed list attributes """ test_obj = self.TestObj() test_obj.attr_d = [] message = self.s._serialize(test_obj) self.assertEqual(message['AttrD'], test_obj.attr_d) test_obj.attr_d = [1, 2, 3] message = self.s._serialize(test_obj) self.assertEqual(message['AttrD'], test_obj.attr_d) test_obj.attr_d = ["1", "2", "3"] message = self.s._serialize(test_obj) self.assertEqual(message['AttrD'], [int(i) for i in test_obj.attr_d]) test_obj.attr_d = ["test", "test2", "test3"] with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj.attr_d = "NotAList" with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_empty_list(self): input = [] output = self.s._serialize(input, '[str]') self.assertEqual(output, input) def test_attr_list_complex(self): """ Test serializing an object with a list of complex objects as an attribute. """ list_obj = type("ListObj", (Model, ), { "_attribute_map": None, "_validation": {}, "abc": None }) list_obj._attribute_map = {"abc": {"key": "ABC", "type": "int"}} list_obj.abc = "123" test_obj = type("CmplxTestObj", (Model, ), { "_attribute_map": None, "_validation": {}, "test_list": None }) test_obj._attribute_map = { "test_list": { "key": "_list", "type": "[ListObj]" } } test_obj.test_list = [list_obj] message = self.s._serialize(test_obj) self.assertEqual(message, {'_list': [{'ABC': 123}]}) list_obj = type("BadListObj", (Model, ), {"map": None}) test_obj._attribute_map = { "test_list": { "key": "_list", "type": "[BadListObj]" } } test_obj.test_list = [list_obj] s = self.s._serialize(test_obj) self.assertEqual(s, {'_list': [{}]}) def test_attr_dict_simple(self): """ Test serializing an object with a simple dictionary attribute. """ test_obj = self.TestObj() test_obj.attr_e = {"value": 3.14} message = self.s._serialize(test_obj) self.assertEqual(message['AttrE']['value'], float(test_obj.attr_e["value"])) test_obj.attr_e = {1: "3.14"} message = self.s._serialize(test_obj) self.assertEqual(message['AttrE']['1'], float(test_obj.attr_e[1])) test_obj.attr_e = "NotADict" with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj.attr_e = {"value": "NotAFloat"} with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_serialize_datetime(self): date_obj = isodate.parse_datetime('2015-01-01T00:00:00') date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '2015-01-01T00:00:00.000Z') date_obj = isodate.parse_datetime('1999-12-31T23:59:59-12:00') date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '2000-01-01T11:59:59.000Z') with self.assertRaises(SerializationError): date_obj = isodate.parse_datetime('9999-12-31T23:59:59-12:00') date_str = Serializer.serialize_iso(date_obj) with self.assertRaises(SerializationError): date_obj = isodate.parse_datetime('0001-01-01T00:00:00+23:59') date_str = Serializer.serialize_iso(date_obj) date_obj = isodate.parse_datetime("2015-06-01T16:10:08.0121-07:00") date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '2015-06-01T23:10:08.0121Z') date_obj = datetime.min date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '0001-01-01T00:00:00.000Z') date_obj = datetime.max date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '9999-12-31T23:59:59.999999Z') def test_serialize_primitive_types(self): a = self.s.serialize_data(1, 'int') self.assertEqual(a, 1) b = self.s.serialize_data(True, 'bool') self.assertEqual(b, True) c = self.s.serialize_data('True', 'str') self.assertEqual(c, 'True') d = self.s.serialize_data(100.0123, 'float') self.assertEqual(d, 100.0123) def test_serialize_object(self): a = self.s.body(1, 'object') self.assertEqual(a, 1) b = self.s.body(True, 'object') self.assertEqual(b, True) c = self.s.serialize_data('True', 'object') self.assertEqual(c, 'True') d = self.s.serialize_data(100.0123, 'object') self.assertEqual(d, 100.0123) e = self.s.serialize_data({}, 'object') self.assertEqual(e, {}) f = self.s.body({"test": "data"}, 'object') self.assertEqual(f, {"test": "data"}) g = self.s.body({"test": {"value": "data"}}, 'object') self.assertEqual(g, {"test": {"value": "data"}}) h = self.s.serialize_data({"test": self.TestObj()}, 'object') self.assertEqual(h, {"test": "Test_Object"}) i = self.s.serialize_data({"test": [1, 2, 3, 4, 5]}, 'object') self.assertEqual(i, {"test": [1, 2, 3, 4, 5]}) def test_serialize_empty_iter(self): a = self.s.serialize_dict({}, 'int') self.assertEqual(a, {}) b = self.s.serialize_iter([], 'int') self.assertEqual(b, []) def test_serialize_json_obj(self): class ComplexId(Model): _validation = {} _attribute_map = { 'id': { 'key': 'id', 'type': 'int' }, 'name': { 'key': 'name', 'type': 'str' }, 'age': { 'key': 'age', 'type': 'float' }, 'male': { 'key': 'male', 'type': 'bool' }, 'birthday': { 'key': 'birthday', 'type': 'iso-8601' }, 'anniversary': { 'key': 'anniversary', 'type': 'iso-8601' } } id = 1 name = "Joey" age = 23.36 male = True birthday = '1992-01-01T00:00:00.000Z' anniversary = isodate.parse_datetime('2013-12-08T00:00:00') class ComplexJson(Model): _validation = {} _attribute_map = { 'p1': { 'key': 'p1', 'type': 'str' }, 'p2': { 'key': 'p2', 'type': 'str' }, 'top_date': { 'key': 'top_date', 'type': 'iso-8601' }, 'top_dates': { 'key': 'top_dates', 'type': '[iso-8601]' }, 'insider': { 'key': 'insider', 'type': '{iso-8601}' }, 'top_complex': { 'key': 'top_complex', 'type': 'ComplexId' } } p1 = 'value1' p2 = 'value2' top_date = isodate.parse_datetime('2014-01-01T00:00:00') top_dates = [ isodate.parse_datetime('1900-01-01T00:00:00'), isodate.parse_datetime('1901-01-01T00:00:00') ] insider = { 'k1': isodate.parse_datetime('2015-01-01T00:00:00'), 'k2': isodate.parse_datetime('2016-01-01T00:00:00'), 'k3': isodate.parse_datetime('2017-01-01T00:00:00') } top_complex = ComplexId() message = self.s._serialize(ComplexJson()) output = { 'p1': 'value1', 'p2': 'value2', 'top_date': '2014-01-01T00:00:00.000Z', 'top_dates': ['1900-01-01T00:00:00.000Z', '1901-01-01T00:00:00.000Z'], 'insider': { 'k1': '2015-01-01T00:00:00.000Z', 'k2': '2016-01-01T00:00:00.000Z', 'k3': '2017-01-01T00:00:00.000Z' }, 'top_complex': { 'id': 1, 'name': 'Joey', 'age': 23.36, 'male': True, 'birthday': '1992-01-01T00:00:00.000Z', 'anniversary': '2013-12-08T00:00:00.000Z', } } self.maxDiff = None self.assertEqual(message, output) def test_polymorphic_serialization(self): self.maxDiff = None class Zoo(Model): _attribute_map = { "animals": { "key": "Animals", "type": "[Animal]" }, } def __init__(self, animals=None): self.animals = animals class Animal(Model): _attribute_map = { "name": { "key": "Name", "type": "str" }, "d_type": { "key": "dType", "type": "str" } } _subtype_map = {'d_type': {"cat": "Cat", "dog": "Dog"}} def __init__(self, name=None): self.name = name class Dog(Animal): _attribute_map = { "name": { "key": "Name", "type": "str" }, "likes_dog_food": { "key": "likesDogFood", "type": "bool" }, "d_type": { "key": "dType", "type": "str" } } def __init__(self, name=None, likes_dog_food=None): self.likes_dog_food = likes_dog_food super(Dog, self).__init__(name) self.d_type = 'dog' class Cat(Animal): _attribute_map = { "name": { "key": "Name", "type": "str" }, "likes_mice": { "key": "likesMice", "type": "bool" }, "dislikes": { "key": "dislikes", "type": "Animal" }, "d_type": { "key": "dType", "type": "str" } } _subtype_map = {"d_type": {"siamese": "Siamese"}} def __init__(self, name=None, likes_mice=None, dislikes=None): self.likes_mice = likes_mice self.dislikes = dislikes super(Cat, self).__init__(name) self.d_type = 'cat' class Siamese(Cat): _attribute_map = { "name": { "key": "Name", "type": "str" }, "likes_mice": { "key": "likesMice", "type": "bool" }, "dislikes": { "key": "dislikes", "type": "Animal" }, "color": { "key": "Color", "type": "str" }, "d_type": { "key": "dType", "type": "str" } } def __init__(self, name=None, likes_mice=None, dislikes=None, color=None): self.color = color super(Siamese, self).__init__(name, likes_mice, dislikes) self.d_type = 'siamese' message = { "Animals": [{ "dType": "dog", "likesDogFood": True, "Name": "Fido" }, { "dType": "cat", "likesMice": False, "dislikes": { "dType": "dog", "likesDogFood": True, "Name": "Angry" }, "Name": "Felix" }, { "dType": "siamese", "Color": "grey", "likesMice": True, "Name": "Finch" }] } zoo = Zoo() angry = Dog() angry.name = "Angry" angry.likes_dog_food = True fido = Dog() fido.name = "Fido" fido.likes_dog_food = True felix = Cat() felix.name = "Felix" felix.likes_mice = False felix.dislikes = angry finch = Siamese() finch.name = "Finch" finch.color = "grey" finch.likes_mice = True zoo.animals = [fido, felix, finch] serialized = self.s._serialize(zoo) self.assertEqual(serialized, message) old_dependencies = self.s.dependencies self.s.dependencies = { 'Zoo': Zoo, 'Animal': Animal, 'Dog': Dog, 'Cat': Cat, 'Siamese': Siamese } serialized = self.s.body( { "animals": [{ "dType": "dog", "likes_dog_food": True, "name": "Fido" }, { "dType": "cat", "likes_mice": False, "dislikes": { "dType": "dog", "likes_dog_food": True, "name": "Angry" }, "name": "Felix" }, { "dType": "siamese", "color": "grey", "likes_mice": True, "name": "Finch" }] }, "Zoo") self.assertEqual(serialized, message) self.s.dependencies = old_dependencies
class TestRuntimeSerialized(unittest.TestCase): class TestObj(Model): _validation = {} _attribute_map = { 'attr_a': {'key':'id', 'type':'str'}, 'attr_b': {'key':'AttrB', 'type':'int'}, 'attr_c': {'key':'Key_C', 'type': 'bool'}, 'attr_d': {'key':'AttrD', 'type':'[int]'}, 'attr_e': {'key':'AttrE', 'type': '{float}'} } def __init__(self): self.attr_a = None self.attr_b = None self.attr_c = None self.attr_d = None self.attr_e = None def __str__(self): return "Test_Object" def setUp(self): self.s = Serializer() return super(TestRuntimeSerialized, self).setUp() def test_obj_without_attr_map(self): """ Test serializing an object with no attribute_map. """ test_obj = type("BadTestObj", (), {}) with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_obj_with_malformed_map(self): """ Test serializing an object with a malformed attribute_map. """ test_obj = type("BadTestObj", (Model,), {"_attribute_map":None}) with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj._attribute_map = {"attr":"val"} with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj._attribute_map = {"attr":{"val":1}} with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_obj_with_mismatched_map(self): """ Test serializing an object with mismatching attributes and map. """ test_obj = type("BadTestObj", (Model,), {"_attribute_map":None}) test_obj._attribute_map = {"abc":{"key":"ABC", "type":"str"}} with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_attr_none(self): """ Test serializing an object with None attributes. """ test_obj = self.TestObj() message = self.s._serialize(test_obj) self.assertIsInstance(message, dict) self.assertFalse('id' in message) def test_attr_int(self): """ Test serializing an object with Int attributes. """ test_obj = self.TestObj() self.TestObj._validation = { 'attr_b': {'required': True}, } test_obj.attr_b = None with self.assertRaises(ValidationError): self.s._serialize(test_obj) test_obj.attr_b = 25 message = self.s._serialize(test_obj) self.assertEqual(message['AttrB'], int(test_obj.attr_b)) test_obj.attr_b = "34534" message = self.s._serialize(test_obj) self.assertEqual(message['AttrB'], int(test_obj.attr_b)) test_obj.attr_b = "NotANumber" with self.assertRaises(SerializationError): self.s._serialize(test_obj) self.TestObj._validation = {} def test_attr_str(self): """ Test serializing an object with Str attributes. """ test_obj = self.TestObj() self.TestObj._validation = { 'attr_a': {'required': True}, } test_obj.attr_a = None with self.assertRaises(ValidationError): self.s._serialize(test_obj) self.TestObj._validation = {} test_obj.attr_a = "TestString" message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) test_obj.attr_a = 1234 message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) test_obj.attr_a = list() message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) test_obj.attr_a = [1] message = self.s._serialize(test_obj) self.assertEqual(message['id'], str(test_obj.attr_a)) def test_attr_bool(self): """ Test serializing an object with bool attributes. """ test_obj = self.TestObj() test_obj.attr_c = True message = self.s._serialize(test_obj) self.assertEqual(message['Key_C'], True) test_obj.attr_c = "" message = self.s._serialize(test_obj) self.assertTrue('Key_C' in message) test_obj.attr_c = None message = self.s._serialize(test_obj) self.assertFalse('Key_C' in message) test_obj.attr_c = "NotEmpty" message = self.s._serialize(test_obj) self.assertEqual(message['Key_C'], True) def test_attr_sequence(self): """ Test serializing a sequence. """ test_obj = ["A", "B", "C"] output = self.s._serialize(test_obj, '[str]', div='|') self.assertEqual(output, "|".join(test_obj)) test_obj = [1,2,3] output = self.s._serialize(test_obj, '[str]', div=',') self.assertEqual(output, ",".join([str(i) for i in test_obj])) def test_attr_list_simple(self): """ Test serializing an object with simple-typed list attributes """ test_obj = self.TestObj() test_obj.attr_d = [] message = self.s._serialize(test_obj) self.assertEqual(message['AttrD'], test_obj.attr_d) test_obj.attr_d = [1,2,3] message = self.s._serialize(test_obj) self.assertEqual(message['AttrD'], test_obj.attr_d) test_obj.attr_d = ["1","2","3"] message = self.s._serialize(test_obj) self.assertEqual(message['AttrD'], [int(i) for i in test_obj.attr_d]) test_obj.attr_d = ["test","test2","test3"] with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj.attr_d = "NotAList" with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_empty_list(self): input = [] output = self.s._serialize(input, '[str]') self.assertEqual(output, input) def test_attr_list_complex(self): """ Test serializing an object with a list of complex objects as an attribute. """ list_obj = type("ListObj", (Model,), {"_attribute_map":None, "_validation":{}, "abc":None}) list_obj._attribute_map = {"abc":{"key":"ABC", "type":"int"}} list_obj.abc = "123" test_obj = type("CmplxTestObj", (Model,), {"_attribute_map":None, "_validation":{}, "test_list":None}) test_obj._attribute_map = {"test_list":{"key":"_list", "type":"[ListObj]"}} test_obj.test_list = [list_obj] message = self.s._serialize(test_obj) self.assertEqual(message, {'_list':[{'ABC':123}]}) list_obj = type("BadListObj", (Model,), {"map":None}) test_obj._attribute_map = {"test_list":{"key":"_list", "type":"[BadListObj]"}} test_obj.test_list = [list_obj] s = self.s._serialize(test_obj) self.assertEqual(s, {'_list':[{}]}) def test_attr_dict_simple(self): """ Test serializing an object with a simple dictionary attribute. """ test_obj = self.TestObj() test_obj.attr_e = {"value": 3.14} message = self.s._serialize(test_obj) self.assertEqual(message['AttrE']['value'], float(test_obj.attr_e["value"])) test_obj.attr_e = {1: "3.14"} message = self.s._serialize(test_obj) self.assertEqual(message['AttrE']['1'], float(test_obj.attr_e[1])) test_obj.attr_e = "NotADict" with self.assertRaises(SerializationError): self.s._serialize(test_obj) test_obj.attr_e = {"value": "NotAFloat"} with self.assertRaises(SerializationError): self.s._serialize(test_obj) def test_serialize_datetime(self): date_obj = isodate.parse_datetime('2015-01-01T00:00:00') date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '2015-01-01T00:00:00.000Z') date_obj = isodate.parse_datetime('1999-12-31T23:59:59-12:00') date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '2000-01-01T11:59:59.000Z') with self.assertRaises(SerializationError): date_obj = isodate.parse_datetime('9999-12-31T23:59:59-12:00') date_str = Serializer.serialize_iso(date_obj) with self.assertRaises(SerializationError): date_obj = isodate.parse_datetime('0001-01-01T00:00:00+23:59') date_str = Serializer.serialize_iso(date_obj) date_obj = isodate.parse_datetime("2015-06-01T16:10:08.0121-07:00") date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '2015-06-01T23:10:08.0121Z') date_obj = datetime.min date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '0001-01-01T00:00:00.000Z') date_obj = datetime.max date_str = Serializer.serialize_iso(date_obj) self.assertEqual(date_str, '9999-12-31T23:59:59.999999Z') def test_serialize_primitive_types(self): a = self.s.serialize_data(1, 'int') self.assertEqual(a, 1) b = self.s.serialize_data(True, 'bool') self.assertEqual(b, True) c = self.s.serialize_data('True', 'str') self.assertEqual(c, 'True') d = self.s.serialize_data(100.0123, 'float') self.assertEqual(d, 100.0123) def test_serialize_object(self): a = self.s.body(1, 'object') self.assertEqual(a, 1) b = self.s.body(True, 'object') self.assertEqual(b, True) c = self.s.serialize_data('True', 'object') self.assertEqual(c, 'True') d = self.s.serialize_data(100.0123, 'object') self.assertEqual(d, 100.0123) e = self.s.serialize_data({}, 'object') self.assertEqual(e, {}) f = self.s.body({"test":"data"}, 'object') self.assertEqual(f, {"test":"data"}) g = self.s.body({"test":{"value":"data"}}, 'object') self.assertEqual(g, {"test":{"value":"data"}}) h = self.s.serialize_data({"test":self.TestObj()}, 'object') self.assertEqual(h, {"test":"Test_Object"}) i = self.s.serialize_data({"test":[1,2,3,4,5]}, 'object') self.assertEqual(i, {"test":[1,2,3,4,5]}) def test_serialize_empty_iter(self): a = self.s.serialize_dict({}, 'int') self.assertEqual(a, {}) b = self.s.serialize_iter([], 'int') self.assertEqual(b, []) def test_serialize_json_obj(self): class ComplexId(Model): _validation = {} _attribute_map = {'id':{'key':'id','type':'int'}, 'name':{'key':'name','type':'str'}, 'age':{'key':'age','type':'float'}, 'male':{'key':'male','type':'bool'}, 'birthday':{'key':'birthday','type':'iso-8601'}, 'anniversary':{'key':'anniversary', 'type':'iso-8601'}} id = 1 name = "Joey" age = 23.36 male = True birthday = '1992-01-01T00:00:00.000Z' anniversary = isodate.parse_datetime('2013-12-08T00:00:00') class ComplexJson(Model): _validation = {} _attribute_map = {'p1':{'key':'p1','type':'str'}, 'p2':{'key':'p2','type':'str'}, 'top_date':{'key':'top_date', 'type':'iso-8601'}, 'top_dates':{'key':'top_dates', 'type':'[iso-8601]'}, 'insider':{'key':'insider','type':'{iso-8601}'}, 'top_complex':{'key':'top_complex','type':'ComplexId'}} p1 = 'value1' p2 = 'value2' top_date = isodate.parse_datetime('2014-01-01T00:00:00') top_dates = [isodate.parse_datetime('1900-01-01T00:00:00'), isodate.parse_datetime('1901-01-01T00:00:00')] insider = { 'k1': isodate.parse_datetime('2015-01-01T00:00:00'), 'k2': isodate.parse_datetime('2016-01-01T00:00:00'), 'k3': isodate.parse_datetime('2017-01-01T00:00:00')} top_complex = ComplexId() message =self.s._serialize(ComplexJson()) output = { 'p1': 'value1', 'p2': 'value2', 'top_date': '2014-01-01T00:00:00.000Z', 'top_dates': [ '1900-01-01T00:00:00.000Z', '1901-01-01T00:00:00.000Z' ], 'insider': { 'k1': '2015-01-01T00:00:00.000Z', 'k2': '2016-01-01T00:00:00.000Z', 'k3': '2017-01-01T00:00:00.000Z' }, 'top_complex': { 'id': 1, 'name': 'Joey', 'age': 23.36, 'male': True, 'birthday': '1992-01-01T00:00:00.000Z', 'anniversary': '2013-12-08T00:00:00.000Z', } } self.maxDiff = None self.assertEqual(message, output) def test_polymorphic_serialization(self): self.maxDiff = None class Zoo(Model): _attribute_map = { "animals":{"key":"Animals", "type":"[Animal]"}, } def __init__(self): self.animals = None class Animal(Model): _attribute_map = { "name":{"key":"Name", "type":"str"} } _subtype_map = { 'dType': {"cat":"Cat", "dog":"Dog"} } def __init__(self): self.name = None class Dog(Animal): _attribute_map = { "name":{"key":"Name", "type":"str"}, "likes_dog_food":{"key":"likesDogFood","type":"bool"} } def __init__(self): self.likes_dog_food = None super(Dog, self).__init__() class Cat(Animal): _attribute_map = { "name":{"key":"Name", "type":"str"}, "likes_mice":{"key":"likesMice","type":"bool"}, "dislikes":{"key":"dislikes","type":"Animal"} } _subtype_map = { "dType":{"siamese":"Siamese"} } def __init__(self): self.likes_mice = None self.dislikes = None super(Cat, self).__init__() class Siamese(Cat): _attribute_map = { "name":{"key":"Name", "type":"str"}, "likes_mice":{"key":"likesMice","type":"bool"}, "dislikes":{"key":"dislikes","type":"Animal"}, "color":{"key":"Color", "type":"str"} } def __init__(self): self.color = None super(Siamese, self).__init__() message = { "Animals": [ { "dType": "dog", "likesDogFood": True, "Name": "Fido" }, { "dType": "cat", "likesMice": False, "dislikes": { "dType": "dog", "likesDogFood": True, "Name": "Angry" }, "Name": "Felix" }, { "dType": "siamese", "Color": "grey", "likesMice": True, "Name": "Finch" }]} zoo = Zoo() angry = Dog() angry.name = "Angry" angry.likes_dog_food = True fido = Dog() fido.name = "Fido" fido.likes_dog_food = True felix = Cat() felix.name = "Felix" felix.likes_mice = False felix.dislikes = angry finch = Siamese() finch.name = "Finch" finch.color = "grey" finch.likes_mice = True zoo.animals = [fido, felix, finch] serialized = self.s._serialize(zoo) self.assertEqual(serialized, message)