diff --git a/sdk/digitaltwins/azure-digitaltwins-core/README.md b/sdk/digitaltwins/azure-digitaltwins-core/README.md index 29c7e59d3ee3..4fb2a9dbb200 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/README.md +++ b/sdk/digitaltwins/azure-digitaltwins-core/README.md @@ -17,7 +17,7 @@ The guides mentioned above can help you get started with key elements of Azure D Install [azure-digitaltwins-core][pypi_package_keys] and [azure-identity][azure_identity_pypi] with [pip][pip]: ```Bash -pip install azure-digitaltiwns-core azure-identity +pip install azure-digitaltwins-core azure-identity ``` [azure-identity][azure_identity] is used for Azure Active Directory authentication as demonstrated below. @@ -43,7 +43,7 @@ It attempts to use multiple credential types in an order until it finds a workin # DefaultAzureCredential supports different authentication mechanisms and determines the appropriate credential type based of the environment it is executing in. # It attempts to use multiple credential types in an order until it finds a working credential. -# - AZURE_URL: The tenant ID in Azure Active Directory +# - AZURE_URL: The URL to the ADT in Azure url = os.getenv("AZURE_URL") # DefaultAzureCredential expects the following three environment variables: @@ -51,7 +51,7 @@ url = os.getenv("AZURE_URL") # - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant # - AZURE_CLIENT_SECRET: The client secret for the registered application credential = DefaultAzureCredential() -serviceClient = DigitalTwinsClient(url, credential) +service_client = DigitalTwinsClient(url, credential) ``` ## Key concepts @@ -83,7 +83,7 @@ Let's create models using the code below. You need to pass an array containing l temporary_component = { "@id": component_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "Component1", "contents": [ { @@ -102,7 +102,7 @@ temporary_component = { temporary_model = { "@id": model_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "TempModel", "contents": [ { @@ -133,9 +133,9 @@ print(models) Using `list_models` to retrieve all created models ```Python Snippet:dt_models_lifecycle -listed_models = service_client.list_models(model_id) +listed_models = service_client.list_models() for model in listed_models: - print(model + '\n') + print(model) ``` ### Get model @@ -171,10 +171,15 @@ For Creating Twin you will need to provide Id of a digital Twin such as `my_twin ```Python Snippet:dt_digitaltwins_lifecycle digital_twin_id = 'digitalTwin-' + str(uuid.uuid4()) -with open(r"dtdl\digital_twins_\buildingTwin.json") as f: - dtdl_digital_twins_building_twin = json.load(f) +temporary_twin = { + "$metadata": { + "$model": model_id + }, + "$dtId": digital_twin_id, + "Prop1": 42 +} -created_twin = service_client.upsert_digital_twin(digital_twin_id, dtdl_digital_twins_building_twin) +created_twin = service_client.upsert_digital_twin(digital_twin_id, temporary_twin) print('Created Digital Twin:') print(created_twin) ``` @@ -190,7 +195,7 @@ print(get_twin) ### Query digital twins -Query the Azure Digital Twins instance for digital twins using the [Azure Digital Twins Query Store lanaguage](https://review.docs.microsoft.com/azure/digital-twins/concepts-query-language). Query calls support paging. Here's an example of how to query for digital twins and how to iterate over the results. +Query the Azure Digital Twins instance for digital twins using the [Azure Digital Twins Query Store lanaguage](https://docs.microsoft.com/azure/digital-twins/concepts-query-language). Query calls support paging. Here's an example of how to query for digital twins and how to iterate over the results. Note that there may be a delay between before changes in your instance are reflected in queries. For more details on query limitations, see (https://docs.microsoft.com/azure/digital-twins/how-to-query-graph#query-limitations) @@ -200,7 +205,7 @@ query_expression = 'SELECT * FROM digitaltwins' query_result = service_client.query_twins(query_expression) print('DigitalTwins:') for twin in query_result: - print(" -: {}".format(twin["$dtId"])) + print(twin) ``` ### Delete digital twins @@ -218,13 +223,15 @@ service_client.delete_digital_twin(digital_twin_id) To update a component or in other words to replace, remove and/or add a component property or subproperty within Digital Twin, you would need Id of a digital twin, component name and application/json-patch+json operations to be performed on the specified digital twin's component. Here is the sample code on how to do it. ```Python Snippet:dt_component_lifecycle -component_path = "Component1" -options = { - "patchDocument": { - "ComponentProp1": "value2" +component_name = "Component1" +patch = [ + { + "op": "replace", + "path": "/ComponentProp1", + "value": "value2" } -} -service_client.update_component(digital_twin_id, component_path, options) +] +service_client.update_component(digital_twin_id, component_name, patch) ``` ### Get digital twin components @@ -232,7 +239,7 @@ service_client.update_component(digital_twin_id, component_path, options) Get a component by providing name of a component and Id of digital twin to which it belongs. ```Python Snippet:dt_component_lifecycle -get_component = service_client.get_component(digital_twin_id, component_path) +get_component = service_client.get_component(digital_twin_id, component_name) print('Get Component:') print(get_component) ``` @@ -244,9 +251,35 @@ print(get_component) `upsert_relationship` creates a relationship on a digital twin provided with Id of a digital twin, name of relationship such as "contains", Id of an relationship such as "FloorContainsRoom" and an application/json relationship to be created. Must contain property with key "\$targetId" to specify the target of the relationship. Sample payloads for relationships can be found [here](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/digitaltwins/azure-digitaltwins-core/samples/dtdl/relationships/hospitalRelationships.json). ```Python Snippet:dt_scenario -with open(r"dtdl\relationships\hospitalRelationships.json") as f: - dtdl_relationships = json.load(f) -for relationship in dtdl_relationships: +hospital_relationships = [ + { + "$relationshipId": "BuildingHasFloor", + "$sourceId": building_twin_id, + "$relationshipName": "has", + "$targetId": floor_twin_id, + "isAccessRestricted": False + }, + { + "$relationshipId": "BuildingIsEquippedWithHVAC", + "$sourceId": building_twin_id, + "$relationshipName": "isEquippedWith", + "$targetId": hvac_twin_id + }, + { + "$relationshipId": "HVACCoolsFloor", + "$sourceId": hvac_twin_id, + "$relationshipName": "controlsTemperature", + "$targetId": floor_twin_id + }, + { + "$relationshipId": "FloorContainsRoom", + "$sourceId": floor_twin_id, + "$relationshipName": "contains", + "$targetId": room_twin_id + } +] + +for relationship in hospital_relationships: service_client.upsert_relationship( relationship["$sourceId"], relationship["$relationshipId"], @@ -261,13 +294,13 @@ for relationship in dtdl_relationships: ```Python Snippet:dt_relationships_list relationships = service_client.list_relationships(digital_twint_id) for relationship in relationships: - print(relationship + '\n') + print(relationship) ``` ```Python Snippet:dt_incoming_relationships_list incoming_relationships = service_client.list_incoming_relationships(digital_twin_id) for incoming_relationship in incoming_relationships: - print(incoming_relationship + '\n') + print(incoming_relationship) ``` ## Create, list, and delete event routes of digital twins @@ -279,11 +312,11 @@ To create an event route, provide an Id of an event route such as "myEventRouteI ```Python Snippet:dt_scenario event_route_id = 'eventRoute-' + str(uuid.uuid4()) event_filter = "$eventType = 'DigitalTwinTelemetryMessages' or $eventType = 'DigitalTwinLifecycleNotification'" -service_client.upsert_event_route( - event_route_id, - event_hub_endpoint_name, - **{"filter": event_filter} -) +route = DigitalTwinsEventRoute( + endpoint_name=event_hub_endpoint_name, + filter=event_filter +) +service_client.upsert_event_route(event_route_id, route) ``` For more information on the event route filter language, see the "how to manage routes" [filter events documentation](https://github.com/Azure/azure-digital-twins/blob/private-preview/Documentation/how-to-manage-routes.md#filter-events). @@ -295,7 +328,7 @@ List a specific event route given event route Id or all event routes setting opt ```Python Snippet:dt_event_routes_list event_routes = service_client.list_event_routes() for event_route in event_routes: - print(event_route + '\n') + print(event_route) ``` ### Delete event routes @@ -323,11 +356,11 @@ You can also publish a telemetry message for a specific component in a digital t ```Python Snippet:dt_publish_component_telemetry digita_twin_id = "" -component_path = "" +component_name = "" telemetry_payload = '{"Telemetry1": 5}' service_client.publish_component_telemetry( digita_twin_id, - component_path, + component_name, telemetry_payload ) ``` diff --git a/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_digitaltwins_client.py b/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_digitaltwins_client.py index bae2247b11d7..6860f7f23fc3 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_digitaltwins_client.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_digitaltwins_client.py @@ -10,6 +10,7 @@ from azure.core.paging import ItemPaged from azure.core.tracing.decorator import distributed_trace from azure.core import MatchConditions +from ._version import SDK_MONIKER from ._utils import ( prep_if_match, @@ -47,6 +48,7 @@ def __init__(self, endpoint, credential, **kwargs): self._client = AzureDigitalTwinsAPI( credential=credential, base_url=endpoint, + sdk_moniker=SDK_MONIKER, **kwargs ) @@ -73,7 +75,7 @@ def upsert_digital_twin(self, digital_twin_id, digital_twin, **kwargs): """Create or update a digital twin. :param str digital_twin_id: The ID of the digital twin. - :param Dict[str, object] digital_twin: + :param Dict[str,object] digital_twin: Dictionary containing the twin to create or update. :keyword ~azure.core.MatchConditions match_condition: The condition under which to perform the operation. @@ -82,7 +84,7 @@ def upsert_digital_twin(self, digital_twin_id, digital_twin, **kwargs): according to the `match_condition`. :return: Dictionary containing the created or updated twin. :rtype: Dict[str, object] - :raises ~azure.core.exceptions.HttpResponseError + :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceExistsError: If the digital twin already exists. """ options = None @@ -102,10 +104,10 @@ def upsert_digital_twin(self, digital_twin_id, digital_twin, **kwargs): @distributed_trace def update_digital_twin(self, digital_twin_id, json_patch, **kwargs): # type: (str, List[Dict[str, object]], **Any) -> None - """Update a digital twin using a json patch. + """Update a digital twin using a JSON patch. :param str digital_twin_id: The ID of the digital twin. - :param List[Dict[str, object]] json_patch: An update specification described by JSON Patch. + :param List[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 ~azure.core.MatchConditions match_condition: @@ -174,7 +176,7 @@ def get_component(self, digital_twin_id, component_name, **kwargs): :rtype: Dict[str, object] :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is either no - digital twin with the provided ID or the component path is invalid. + digital twin with the provided ID or the component name is invalid. """ return self._client.digital_twins.get_component( digital_twin_id, @@ -195,7 +197,7 @@ def update_component( :param str digital_twin_id: The ID of the digital twin. :param str component_name: The component being updated. - :param List[Dict[str, object]] json_patch: An update specification described by JSON Patch. + :param List[Dict[str,object]] json_patch: An update specification described by JSON Patch. :keyword ~azure.core.MatchConditions match_condition: The condition under which to perform the operation. :keyword str etag: @@ -205,7 +207,7 @@ def update_component( :rtype: None :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is either no - digital twin with the provided ID or the component path is invalid. + digital twin with the provided ID or the component name is invalid. """ options = None etag = kwargs.pop("etag", None) @@ -248,7 +250,7 @@ def upsert_relationship(self, digital_twin_id, relationship_id, relationship, ** :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. + :param Dict[str,object] relationship: Dictionary containing the relationship. :keyword ~azure.core.MatchConditions match_condition: The condition under which to perform the operation. :keyword str etag: @@ -288,7 +290,7 @@ def update_relationship( :param str digital_twin_id: The ID of the digital twin. :param str relationship_id: The ID of the relationship to retrieve. - :param List[Dict[str, object]] json_patch: JSON Patch description of the update + :param List[Dict[str,object]] json_patch: JSON Patch description of the update to the relationship properties. :keyword ~azure.core.MatchConditions match_condition: The condition under which to perform the operation. @@ -380,7 +382,7 @@ def list_incoming_relationships(self, digital_twin_id, **kwargs): :param str digital_twin_id: The ID of the digital twin. :return: An iterator like instance of either Relationship. - :rtype: ~azure.core.paging.ItemPaged[~azure.digitaltwins.IncomingRelationship] + :rtype: ~azure.core.paging.ItemPaged[~azure.digitaltwins.core.IncomingRelationship] :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no digital twin with the provided ID. @@ -394,17 +396,16 @@ def list_incoming_relationships(self, digital_twin_id, **kwargs): def publish_telemetry(self, digital_twin_id, payload, **kwargs): # type: (str, object, **Any) -> None """Publish telemetry from a digital twin, which is then consumed by - one or many destination endpoints (subscribers) defined under. + one or many destination endpoints (subscribers) defined under. - :param str digital_twin_id: The Id of the digital twin + :param str digital_twin_id: The ID of the digital twin :param object payload: The telemetry payload to be sent :keyword str message_id: The message ID. If not specified, a UUID will be generated. :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. + :raises ~azure.core.exceptions.HttpResponseError: + :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no + digital twin with the provided ID. """ message_id = kwargs.pop('message_id', None) or str(uuid.uuid4()) timestamp = datetime.now() @@ -426,17 +427,17 @@ def publish_component_telemetry( ): # type: (str, str, object, **Any) -> None """Publish telemetry from a digital twin's component, which is then consumed by - one or many destination endpoints (subscribers) defined under. + one or many destination endpoints (subscribers) defined under. - :param str digital_twin_id: The Id of the digital twin. + :param str digital_twin_id: The ID of the digital twin. :param str component_name: The name of the DTDL component. :param object payload: The telemetry payload to be sent. :keyword str message_id: The message ID. If not specified, a UUID will be generated. :return: None :rtype: None - :raises: ~azure.core.exceptions.HttpResponseError: + :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no - digital twin with the provided ID or the component path is invalid. + digital twin with the provided ID or the component name is invalid. """ message_id = kwargs.pop('message_id', None) or str(uuid.uuid4()) timestamp = datetime.now() @@ -458,7 +459,7 @@ def get_model(self, model_id, **kwargs): :keyword bool include_model_definition: Include the model definition as part of the result. The default value is False. :return: The model data. - :rtype: ~azure.digitaltwins.DigitalTwinsModelData + :rtype: ~azure.digitaltwins.core.DigitalTwinsModelData :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no model with the provided ID. @@ -482,7 +483,7 @@ def list_models(self, dependencies_for=None, **kwargs): :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 model data. - :rtype: ~azure.core.paging.ItemPaged[~azure.digitaltwins.DigitalTwinsModelData] + :rtype: ~azure.core.paging.ItemPaged[~azure.digitaltwins.core.DigitalTwinsModelData] :raises ~azure.core.exceptions.HttpResponseError: """ include_model_definition = kwargs.pop('include_model_definition', False) @@ -506,7 +507,7 @@ def create_models(self, dtdl_models, **kwargs): :param List[Dict[str,object]] model_list: The set of models to create. Each dict corresponds to exactly one model. :return: The list of created models. - :rtype: List[~azure.digitaltwins.DigitalTwinsModelData] + :rtype: List[~azure.digitaltwins.core.DigitalTwinsModelData] :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceExistsError: One or more of the provided models already exist. @@ -526,7 +527,7 @@ def decommission_model(self, model_id, **kwargs): :rtype: None :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: There is no model - with the provided id. + with the provided ID. """ json_patch = [{'op': 'replace', 'path': '/decommissioned', 'value': True}] return self._client.digital_twin_models.update( @@ -545,7 +546,7 @@ def delete_model(self, model_id, **kwargs): :rtype: None :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: There is no model - with the provided id. + with the provided ID. :raises ~azure.core.exceptions.ResourceExistsError: There are dependencies on the model that prevent it from being deleted. """ @@ -598,11 +599,10 @@ def upsert_event_route(self, event_route_id, event_route, **kwargs): """Create or update an event route. :param str event_route_id: The ID of the event route to create or update. - :param DigitalTwinsEventRoute event_route: The event route data. + :param ~azure.digitaltwins.core.DigitalTwinsEventRoute event_route: The event route data. :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: - :raises ~azure.core.exceptions.ServiceRequestError: The request is invalid. """ return self._client.event_routes.add( event_route_id, @@ -634,11 +634,11 @@ def query_twins(self, query_expression, **kwargs): Note: that there may be a delay between before changes in your instance are reflected in queries. For more details on query limitations, see - https://docs.microsoft.com/en-us/azure/digital-twins/how-to-query-graph#query-limitations + https://docs.microsoft.com/azure/digital-twins/how-to-query-graph#query-limitations :param str query_expression: The query expression to execute. :return: An iterable of query results. - :rtype: ~azure.core.ItemPaged[Dict[str, object]] + :rtype: ~azure.core.paging.ItemPaged[Dict[str, object]] :raises ~azure.core.exceptions.HttpResponseError: """ def extract_data(deserialized): diff --git a/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_version.py b/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_version.py index 8eedef9ba349..b0b51f10d98b 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_version.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/_version.py @@ -4,3 +4,5 @@ # ------------------------------------ VERSION = "1.0.0" + +SDK_MONIKER = "digitaltwins-core/{}".format(VERSION) # type: str diff --git a/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/aio/_digitaltwins_client_async.py b/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/aio/_digitaltwins_client_async.py index c99b02dc0052..5612ff7eb521 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/aio/_digitaltwins_client_async.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/azure/digitaltwins/core/aio/_digitaltwins_client_async.py @@ -12,6 +12,7 @@ from azure.core.tracing.decorator_async import distributed_trace_async from azure.core.tracing.decorator import distributed_trace from azure.core import MatchConditions +from .._version import SDK_MONIKER from .._utils import ( prep_if_match, @@ -21,13 +22,13 @@ from .._generated.models import ( QuerySpecification, DigitalTwinsAddOptions, - DigitalTwinsUpdateOptions, DigitalTwinsDeleteOptions, - DigitalTwinsModelData, + DigitalTwinsUpdateOptions, DigitalTwinsUpdateComponentOptions, DigitalTwinsDeleteRelationshipOptions, DigitalTwinsUpdateRelationshipOptions, DigitalTwinsAddRelationshipOptions, + DigitalTwinsModelData ) if TYPE_CHECKING: @@ -51,6 +52,7 @@ def __init__(self, endpoint: str, credential: "AsyncTokenCredential", **kwargs) self._client = AzureDigitalTwinsAPI( credential=credential, base_url=endpoint, + sdk_moniker=SDK_MONIKER, **kwargs ) @@ -90,7 +92,7 @@ async def upsert_digital_twin( """Create or update a digital twin. :param str digital_twin_id: The ID of the digital twin. - :param Dict[str, object] digital_twin: + :param Dict[str,object] digital_twin: Dictionary containing the twin to create or update. :keyword ~azure.core.MatchConditions match_condition: The condition under which to perform the operation. @@ -127,7 +129,7 @@ async def update_digital_twin( """Update a digital twin using a JSON patch. :param str digital_twin_id: The ID of the digital twin. - :param List[Dict[str, object]] json_patch: An update specification described by JSON Patch. + :param List[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 ~azure.core.MatchConditions match_condition: @@ -198,7 +200,7 @@ async def get_component(self, digital_twin_id: str, component_name: str, **kwarg :rtype: Dict[str, object] :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is either no - digital twin with the provided ID or the component path is invalid. + digital twin with the provided ID or the component name is invalid. """ return await self._client.digital_twins.get_component( digital_twin_id, @@ -218,7 +220,7 @@ async def update_component( :param str digital_twin_id: The ID of the digital twin. :param str component_name: The component being updated. - :param List[Dict[str, object]] json_patch: An update specification described by JSON Patch. + :param List[Dict[str,object]] json_patch: An update specification described by JSON Patch. :keyword ~azure.core.MatchConditions match_condition: The condition under which to perform the operation. :keyword str etag: @@ -228,7 +230,7 @@ async def update_component( :rtype: None :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is either no - digital twin with the provided ID or the component path is invalid. + digital twin with the provided ID or the component name is invalid. """ options = None etag = kwargs.pop("etag", None) @@ -354,7 +356,7 @@ async def delete_relationship( relationship_id: str, **kwargs ) -> None: - """Delete a digital twin. + """Delete 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 delete. @@ -395,7 +397,7 @@ def list_relationships( :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 relationships. + :return: An iterator instance of list of relationships. :rtype: ~azure.core.async_paging.AsyncItemPaged[Dict[str,object]] :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no @@ -416,8 +418,8 @@ def list_incoming_relationships( """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 relationships. - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.digitaltwins.IncomingRelationship] + :return: An iterator instance of list of incoming relationships. + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.digitaltwins.core.IncomingRelationship] :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no digital twin with the provided ID. @@ -435,9 +437,9 @@ async def publish_telemetry( **kwargs ) -> None: """Publish telemetry from a digital twin, which is then consumed by - one or many destination endpoints (subscribers) defined under. + one or many destination endpoints (subscribers) defined under. - :param str digital_twin_id: The Id of the digital twin + :param str digital_twin_id: The ID of the digital twin :param object payload: The telemetry payload to be sent :keyword str message_id: The message ID. If not specified, a UUID will be generated. :return: None @@ -464,8 +466,8 @@ async def publish_component_telemetry( payload: object, **kwargs ) -> None: - """Publish telemetry from a digital twin's component, which is then consumed by - one or many destination endpoints (subscribers) defined under. + """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_name: The name of the DTDL component. @@ -473,9 +475,9 @@ async def publish_component_telemetry( :keyword str message_id: The message ID. If not specified, a UUID will be generated. :return: None :rtype: None - :raises: ~azure.core.exceptions.HttpResponseError: + :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no - digital twin with the provided ID or the component path is invalid. + digital twin with the provided ID or the component name is invalid. """ message_id = kwargs.pop('message_id', None) or str(uuid.uuid4()) timestamp = datetime.now() @@ -496,7 +498,7 @@ async def get_model(self, model_id: str, **kwargs) -> DigitalTwinsModelData: :keyword bool include_model_definition: Include the model definition as part of the result. The default value is False. :return: The model data. - :rtype: ~azure.digitaltwins.DigitalTwinsModelData + :rtype: ~azure.digitaltwins.core.DigitalTwinsModelData :raises ~azure.core.exceptions.HttpResponseError: :raises ~azure.core.exceptions.ResourceNotFoundError: If there is no model with the provided ID. @@ -524,7 +526,7 @@ def list_models( :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 model data. - :rtype: ~azure.core.paging.AsyncItemPaged[~azure.digitaltwins.DigitalTwinsModelData] + :rtype: ~azure.core.paging.AsyncItemPaged[~azure.digitaltwins.core.DigitalTwinsModelData] :raises ~azure.core.exceptions.HttpResponseError: """ include_model_definition = kwargs.pop('include_model_definition', False) @@ -544,11 +546,12 @@ def list_models( async def create_models(self, dtdl_models: List[object], **kwargs) -> List[DigitalTwinsModelData]: """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. + :param List[object] model_list: The set of models to create. + Each dict corresponds to exactly one model. :return: The list of created models - :rtype: List[~azure.digitaltwins.DigitalTwinsModelData] + :rtype: List[~azure.digitaltwins.core.DigitalTwinsModelData] :raises ~azure.core.exceptions.HttpResponseError: - :raises ~azure.core.exceptions.ResourceNotFoundError: One or more of + :raises ~azure.core.exceptions.ResourceExistsError: One or more of the provided models already exist. """ return await self._client.digital_twin_models.add( @@ -560,7 +563,7 @@ async def create_models(self, dtdl_models: List[object], **kwargs) -> List[Digit async def decommission_model(self, model_id: str, **kwargs) -> None: """Decommissions a model. - :param str model_id: The id for the model. The id is globally unique and case sensitive. + :param str model_id: The ID for the model. The ID is globally unique and case sensitive. :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: @@ -576,9 +579,9 @@ async def decommission_model(self, model_id: str, **kwargs) -> None: @distributed_trace_async async def delete_model(self, model_id: str, **kwargs) -> None: - """Decommission a model using a JSON patch. + """Delete a model. - :param str model_id: The ID of the model to decommission. + :param str model_id: The ID of the model to delete. :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: @@ -644,7 +647,8 @@ async def upsert_event_route( :raises ~azure.core.exceptions.HttpResponseError: """ return await self._client.event_routes.add( - event_route_id, event_route, + event_route_id, + event_route=event_route, **kwargs ) @@ -667,13 +671,14 @@ async def delete_event_route(self, event_route_id: str, **kwargs) -> None: @distributed_trace def query_twins(self, query_expression: str, **kwargs) -> AsyncItemPaged[Dict[str, object]]: """Query for digital twins. - Note: that there may be a delay between before changes in your instance are reflected in queries. - For more details on query limitations, see - https://docs.microsoft.com/en-us/azure/digital-twins/how-to-query-graph#query-limitations + + Note: that there may be a delay between before changes in your instance are reflected in queries. + For more details on query limitations, see + https://docs.microsoft.com/azure/digital-twins/how-to-query-graph#query-limitations :param str query_expression: The query expression to execute. :return: An iterable of query results. - :rtype: ~azure.core.AsyncItemPaged[Dict[str, object]] + :rtype: ~azure.core.async_paging.AsyncItemPaged[Dict[str, object]] :raises ~azure.core.exceptions.HttpResponseError: """ async def extract_data(deserialized): diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/Readme.md b/sdk/digitaltwins/azure-digitaltwins-core/samples/Readme.md index cc9d390eb056..4fb2a9dbb200 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/Readme.md +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/Readme.md @@ -1,4 +1,10 @@ -# Introduction +# Azure Azure Digital Twins Core client library for Python + +This package contains an SDK for Azure Digital Twins API to provide access to the Azure Digital Twins service for managing twins, models, relationships, etc. + +## Getting started + +### Introduction Azure Digital Twins is a developer platform for next-generation IoT solutions that lets you create, run, and manage digital representations of your business environment, securely and efficiently in the cloud. With Azure Digital Twins, creating live operational state representations is quick and cost-effective, and digital representations stay current with real-time data from IoT and other data sources. If you are new to Azure Digital Twins and would like to learn more about the platform, please make sure you check out the Azure Digital Twins [official documentation page](https://docs.microsoft.com/azure/digital-twins/overview). @@ -6,6 +12,53 @@ For an introduction on how to program against the Azure Digital Twins service, v The guides mentioned above can help you get started with key elements of Azure Digital Twins, such as creating Azure Digital Twins instances, models, twin graphs, etc. Use this samples guide below to familiarize yourself with the various APIs that help you program against Azure Digital Twins. +### How to Install + +Install [azure-digitaltwins-core][pypi_package_keys] and +[azure-identity][azure_identity_pypi] with [pip][pip]: +```Bash +pip install azure-digitaltwins-core azure-identity +``` +[azure-identity][azure_identity] is used for Azure Active Directory +authentication as demonstrated below. + +### How to use + +#### Authentication, permission + +To create a new digital twins client, you need the endpoint to an Azure Digital Twin instance and credentials. +For the samples below, the `AZURE_URL`, `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, and `AZURE_CLIENT_SECRET` environment variables have to be set. +The client requires an instance of [TokenCredential](https://docs.microsoft.com/dotnet/api/azure.core.tokencredential?view=azure-dotnet) or [ServiceClientCredentials](https://docs.microsoft.com/dotnet/api/microsoft.rest.serviceclientcredentials?view=azure-dotnet). +In this samples, we illustrate how to use one derived class: [DefaultAzureCredentials](https://docs.microsoft.com/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet). + +> Note: In order to access the data plane for the Digital Twins service, the entity must be given permissions. +> To do this, use the Azure CLI command: `az dt rbac assign-role --assignee '' --role owner -n ''` + +DefaultAzureCredential supports different authentication mechanisms and determines the appropriate credential type based of the environment it is executing in. +It attempts to use multiple credential types in an order until it finds a working credential. + +##### Sample code + +```python Snippet:dt_create_digitaltwins_service_client.py +# DefaultAzureCredential supports different authentication mechanisms and determines the appropriate credential type based of the environment it is executing in. +# It attempts to use multiple credential types in an order until it finds a working credential. + +# - AZURE_URL: The URL to the ADT in Azure +url = os.getenv("AZURE_URL") + +# DefaultAzureCredential expects the following three environment variables: +# - AZURE_TENANT_ID: The tenant ID in Azure Active Directory +# - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant +# - AZURE_CLIENT_SECRET: The client secret for the registered application +credential = DefaultAzureCredential() +service_client = DigitalTwinsClient(url, credential) +``` + +## Key concepts + +Azure Digital Twins is an Azure IoT service that creates comprehensive models of the physical environment. It can create spatial intelligence graphs to model the relationships and interactions between people, spaces, and devices. +You can learn more about Azure Digital Twins by visiting [Azure Digital Twins Documentation](https://docs.microsoft.com/azure/digital-twins/). + ## Examples You can explore the digital twins APIs (using the client library) using the samples project. @@ -30,7 +83,7 @@ Let's create models using the code below. You need to pass an array containing l temporary_component = { "@id": component_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "Component1", "contents": [ { @@ -49,7 +102,7 @@ temporary_component = { temporary_model = { "@id": model_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "TempModel", "contents": [ { @@ -80,9 +133,9 @@ print(models) Using `list_models` to retrieve all created models ```Python Snippet:dt_models_lifecycle -listed_models = service_client.list_models(model_id) +listed_models = service_client.list_models() for model in listed_models: - print(model + '\n') + print(model) ``` ### Get model @@ -118,10 +171,15 @@ For Creating Twin you will need to provide Id of a digital Twin such as `my_twin ```Python Snippet:dt_digitaltwins_lifecycle digital_twin_id = 'digitalTwin-' + str(uuid.uuid4()) -with open(r"dtdl\digital_twins_\buildingTwin.json") as f: - dtdl_digital_twins_building_twin = json.load(f) +temporary_twin = { + "$metadata": { + "$model": model_id + }, + "$dtId": digital_twin_id, + "Prop1": 42 +} -created_twin = service_client.upsert_digital_twin(digital_twin_id, dtdl_digital_twins_building_twin) +created_twin = service_client.upsert_digital_twin(digital_twin_id, temporary_twin) print('Created Digital Twin:') print(created_twin) ``` @@ -137,7 +195,7 @@ print(get_twin) ### Query digital twins -Query the Azure Digital Twins instance for digital twins using the [Azure Digital Twins Query Store lanaguage](https://review.docs.microsoft.com/azure/digital-twins/concepts-query-language). Query calls support paging. Here's an example of how to query for digital twins and how to iterate over the results. +Query the Azure Digital Twins instance for digital twins using the [Azure Digital Twins Query Store lanaguage](https://docs.microsoft.com/azure/digital-twins/concepts-query-language). Query calls support paging. Here's an example of how to query for digital twins and how to iterate over the results. Note that there may be a delay between before changes in your instance are reflected in queries. For more details on query limitations, see (https://docs.microsoft.com/azure/digital-twins/how-to-query-graph#query-limitations) @@ -147,7 +205,7 @@ query_expression = 'SELECT * FROM digitaltwins' query_result = service_client.query_twins(query_expression) print('DigitalTwins:') for twin in query_result: - print(" -: {}".format(twin["$dtId"])) + print(twin) ``` ### Delete digital twins @@ -165,13 +223,15 @@ service_client.delete_digital_twin(digital_twin_id) To update a component or in other words to replace, remove and/or add a component property or subproperty within Digital Twin, you would need Id of a digital twin, component name and application/json-patch+json operations to be performed on the specified digital twin's component. Here is the sample code on how to do it. ```Python Snippet:dt_component_lifecycle -component_path = "Component1" -options = { - "patchDocument": { - "ComponentProp1": "value2" +component_name = "Component1" +patch = [ + { + "op": "replace", + "path": "/ComponentProp1", + "value": "value2" } -} -service_client.update_component(digital_twin_id, component_path, options) +] +service_client.update_component(digital_twin_id, component_name, patch) ``` ### Get digital twin components @@ -179,7 +239,7 @@ service_client.update_component(digital_twin_id, component_path, options) Get a component by providing name of a component and Id of digital twin to which it belongs. ```Python Snippet:dt_component_lifecycle -get_component = service_client.get_component(digital_twin_id, component_path) +get_component = service_client.get_component(digital_twin_id, component_name) print('Get Component:') print(get_component) ``` @@ -191,9 +251,35 @@ print(get_component) `upsert_relationship` creates a relationship on a digital twin provided with Id of a digital twin, name of relationship such as "contains", Id of an relationship such as "FloorContainsRoom" and an application/json relationship to be created. Must contain property with key "\$targetId" to specify the target of the relationship. Sample payloads for relationships can be found [here](https://github.com/Azure/azure-sdk-for-python/blob/master/sdk/digitaltwins/azure-digitaltwins-core/samples/dtdl/relationships/hospitalRelationships.json). ```Python Snippet:dt_scenario -with open(r"dtdl\relationships\hospitalRelationships.json") as f: - dtdl_relationships = json.load(f) -for relationship in dtdl_relationships: +hospital_relationships = [ + { + "$relationshipId": "BuildingHasFloor", + "$sourceId": building_twin_id, + "$relationshipName": "has", + "$targetId": floor_twin_id, + "isAccessRestricted": False + }, + { + "$relationshipId": "BuildingIsEquippedWithHVAC", + "$sourceId": building_twin_id, + "$relationshipName": "isEquippedWith", + "$targetId": hvac_twin_id + }, + { + "$relationshipId": "HVACCoolsFloor", + "$sourceId": hvac_twin_id, + "$relationshipName": "controlsTemperature", + "$targetId": floor_twin_id + }, + { + "$relationshipId": "FloorContainsRoom", + "$sourceId": floor_twin_id, + "$relationshipName": "contains", + "$targetId": room_twin_id + } +] + +for relationship in hospital_relationships: service_client.upsert_relationship( relationship["$sourceId"], relationship["$relationshipId"], @@ -208,13 +294,13 @@ for relationship in dtdl_relationships: ```Python Snippet:dt_relationships_list relationships = service_client.list_relationships(digital_twint_id) for relationship in relationships: - print(relationship + '\n') + print(relationship) ``` ```Python Snippet:dt_incoming_relationships_list incoming_relationships = service_client.list_incoming_relationships(digital_twin_id) for incoming_relationship in incoming_relationships: - print(incoming_relationship + '\n') + print(incoming_relationship) ``` ## Create, list, and delete event routes of digital twins @@ -226,11 +312,11 @@ To create an event route, provide an Id of an event route such as "myEventRouteI ```Python Snippet:dt_scenario event_route_id = 'eventRoute-' + str(uuid.uuid4()) event_filter = "$eventType = 'DigitalTwinTelemetryMessages' or $eventType = 'DigitalTwinLifecycleNotification'" -service_client.upsert_event_route( - event_route_id, - event_hub_endpoint_name, - **{"filter": event_filter} -) +route = DigitalTwinsEventRoute( + endpoint_name=event_hub_endpoint_name, + filter=event_filter +) +service_client.upsert_event_route(event_route_id, route) ``` For more information on the event route filter language, see the "how to manage routes" [filter events documentation](https://github.com/Azure/azure-digital-twins/blob/private-preview/Documentation/how-to-manage-routes.md#filter-events). @@ -242,7 +328,7 @@ List a specific event route given event route Id or all event routes setting opt ```Python Snippet:dt_event_routes_list event_routes = service_client.list_event_routes() for event_route in event_routes: - print(event_route + '\n') + print(event_route) ``` ### Delete event routes @@ -270,11 +356,78 @@ You can also publish a telemetry message for a specific component in a digital t ```Python Snippet:dt_publish_component_telemetry digita_twin_id = "" -component_path = "" +component_name = "" telemetry_payload = '{"Telemetry1": 5}' service_client.publish_component_telemetry( digita_twin_id, - component_path, + component_name, telemetry_payload ) ``` + +## Troubleshooting + +## Logging +This library uses the standard logging library for logging. Basic information about HTTP sessions (URLs, headers, etc.) is logged at INFO level. + +Detailed DEBUG level logging, including request/response bodies and unredacted headers, can be enabled on a client with the logging_enable keyword argument: + +### Client level logging +```python Snippet:dt_digitaltwins_get.py +import sys +import logging + +# Create logger +logger = logging.getLogger('azure') +logger.setLevel(logging.DEBUG) +handler = logging.StreamHandler(stream=sys.stdout) +logger.addHandler(handler) + +# Create service client and enable logging for all operations +service_client = DigitalTwinsClient(url, credential, logging_enable=True) +``` + +### Per-operation level logging +```python Snippet:dt_models_get.py +import sys +import logging + +# Create logger +logger = logging.getLogger('azure') +logger.setLevel(logging.DEBUG) +handler = logging.StreamHandler(stream=sys.stdout) +logger.addHandler(handler) + +# Get model with logging enabled +model = service_client.get_model(model_id, logging_enable=True) +``` + +### Optional Configuration +Optional keyword arguments can be passed in at the client and per-operation level. The azure-core [reference documentation](https://azuresdkdocs.blob.core.windows.net/$web/python/azure-core/latest/azure.core.html) describes available configurations for retries, logging, transport protocols, and more. + +[azure_identity]: https://github.com/Azure/azure-sdk-for-python/tree/master/sdk/identity/azure-identity +[azure_identity_pypi]: https://pypi.org/project/azure-identity/ +[default_cred_ref]: https://aka.ms/azsdk/python/identity/docs#azure.identity.DefaultAzureCredential +[pip]: https://pypi.org/project/pip/ + + +## Next steps + +### Provide Feedback + +If you encounter bugs or have suggestions, please +[open an issue](https://github.com/Azure/azure-sdk-for-python/issues). + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_component_lifecycle.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_component_lifecycle.py index 33203962c907..6f24bfbcf3fa 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_component_lifecycle.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_component_lifecycle.py @@ -24,14 +24,14 @@ # For the purpose of this example we will create temporary digital twin using random Ids. # We have to make sure these model Ids are unique within the DT instance so we use generated UUIDs. try: - model_id = 'model-' + str(uuid.uuid4()) - component_id = 'component-' + str(uuid.uuid4()) + model_id = 'dtmi:samples:componentlifecyclemodel;1' + component_id = 'dtmi:samples:componentlifecycle;1' digital_twin_id = 'digitalTwin-' + str(uuid.uuid4()) temporary_component = { "@id": component_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "Component1", "contents": [ { @@ -45,7 +45,7 @@ temporary_model = { "@id": model_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "TempModel", "contents": [ { @@ -62,13 +62,13 @@ } temporary_twin = { - "@id": digital_twin_id, "$metadata": { - "@model": model_id + "$model": model_id }, + "$dtId": digital_twin_id, "Prop1": 42, "Component1": { - "$metadata": {}, + "$metadata": {}, "ComponentProp1": "value1" } } @@ -99,27 +99,31 @@ print(created_twin) # Update component - component_path = "Component1" - options = { - "patchDocument": { - "ComponentProp1": "value2" + component_name = "Component1" + patch = [ + { + "op": "replace", + "path": "/ComponentProp1", + "value": "value2" } - } - service_client.update_component(digital_twin_id, component_path, options) + ] + service_client.update_component(digital_twin_id, component_name, patch) # Get component - get_component = service_client.get_component(digital_twin_id, component_path) + get_component = service_client.get_component(digital_twin_id, component_name) print('Get Component:') print(get_component) # Delete digital twin service_client.delete_digital_twin(digital_twin_id) - # Decomission model + # Decomission models service_client.decommission_model(model_id) + service_client.decommission_model(component_id) - # Delete model + # Delete models service_client.delete_model(model_id) + service_client.delete_model(component_id) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_lifecycle.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_lifecycle.py index eb64d4c3abfc..9e9bcf45d218 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_lifecycle.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_lifecycle.py @@ -3,7 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ import os -import json import uuid from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError @@ -23,6 +22,31 @@ # For the purpose of this example we will create temporary digital twin using random Ids. # We have to make sure these Ids are unique within the DT instance so we use generated UUIDs. try: + model_id = 'dtmi:samples:digitaltwinlifecyclemodel;1' + digital_twin_id = 'digitalTwin-' + str(uuid.uuid4()) + + temporary_model = { + "@id": model_id, + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "TempModel", + "contents": [ + { + "@type": "Property", + "name": "Prop1", + "schema": "double" + } + ] + } + + temporary_twin = { + "$metadata": { + "$model": model_id + }, + "$dtId": digital_twin_id, + "Prop1": 42 + } + # DefaultAzureCredential supports different authentication mechanisms and determines # the appropriate credential type based of the environment it is executing in. # It attempts to use multiple credential types in an order until it finds a working credential. @@ -38,20 +62,13 @@ service_client = DigitalTwinsClient(url, credential) # Create model first from sample dtdl - with open(r"dtdl\models\building.json") as f: - dtdl_model_building = json.load(f) - new_model_list = [] - new_model_list.append(dtdl_model_building) + new_model_list = [temporary_model] model = service_client.create_models(new_model_list) print('Created Model:') print(model) # Create digital twin based on the created model - digital_twin_id = 'digitalTwin-' + str(uuid.uuid4()) - with open(r"dtdl\digital_twins_\buildingTwin.json") as f: - dtdl_digital_twins_building_twin = json.load(f) - - created_twin = service_client.upsert_digital_twin(digital_twin_id, dtdl_digital_twins_building_twin) + created_twin = service_client.upsert_digital_twin(digital_twin_id, temporary_twin) print('Created Digital Twin:') print(created_twin) @@ -61,15 +78,25 @@ print(get_twin) # Update digital twin - twin_patch = { - "AverageTemperature": 42 - } - updated_twin = service_client.update_digital_twin(digital_twin_id, twin_patch) + patch = [ + { + "op": "replace", + "path": "/Prop1", + "value": 13 + } + ] + updated_twin = service_client.update_digital_twin(digital_twin_id, patch) print('Updated Digital Twin:') print(updated_twin) # Delete digital twin service_client.delete_digital_twin(digital_twin_id) + # Decomission model + service_client.decommission_model(model_id) + + # Delete model + service_client.delete_model(model_id) + except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_query.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_query.py index 07b8ccf864e2..db2d3133adc2 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_query.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_digitaltwins_query.py @@ -34,7 +34,7 @@ query_result = service_client.query_twins(query_expression) print('DigitalTwins:') for twin in query_result: - print(" -: {}".format(twin["$dtId"])) + print(twin) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_event_routes_list.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_event_routes_list.py index 3233ce613d16..5bff2b099680 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_event_routes_list.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_event_routes_list.py @@ -32,7 +32,7 @@ # List event routes event_routes = service_client.list_event_routes() for event_route in event_routes: - print(event_route + '\n') + print(event_route) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_incoming_relationships_list.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_incoming_relationships_list.py index 780f86ad9341..c238eaadd9ed 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_incoming_relationships_list.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_incoming_relationships_list.py @@ -33,7 +33,7 @@ digital_twin_id = "" # from the samples: BuildingTwin, FloorTwin, HVACTwin, RoomTwin incoming_relationships = service_client.list_incoming_relationships(digital_twin_id) for incoming_relationship in incoming_relationships: - print(incoming_relationship + '\n') + print(incoming_relationship) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_get.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_get.py index fd5ba7ded206..672afc49f90b 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_get.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_get.py @@ -33,10 +33,10 @@ service_client = DigitalTwinsClient(url, credential) # ModelId from the samples: - # dtmi:samples:Room1 - # dtmi:samples:Wifi1 - # dtmi:samples:Floor1 - # dtmi:samples:Building1 + # dtmi:samples:Room;1 + # dtmi:samples:Wifi;1 + # dtmi:samples:Floor;1 + # dtmi:samples:Building;1 model_id = "" # Create logger diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_lifecycle.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_lifecycle.py index fcf33c59e3d6..25c49298cdc3 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_lifecycle.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_lifecycle.py @@ -3,7 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ import os -import uuid from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError from azure.digitaltwins.core import DigitalTwinsClient @@ -23,13 +22,13 @@ # For the purpose of this example we will create temporary model and a temporay component model using random Ids. # We have to make sure these model Ids are unique within the DT instance so we use generated UUIDs. try: - model_id = 'model-' + str(uuid.uuid4()) - component_id = 'component-' + str(uuid.uuid4()) + model_id = 'dtmi:samples:examplemodel;1' + component_id = 'dtmi:samples:examplecomponent;1' temporary_component = { "@id": component_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "Component1", "contents": [ { @@ -48,7 +47,7 @@ temporary_model = { "@id": model_id, "@type": "Interface", - "@context": "dtmi:dtdl:context2", + "@context": "dtmi:dtdl:context;2", "displayName": "TempModel", "contents": [ { @@ -99,9 +98,9 @@ print(get_model) # List all models - listed_models = service_client.list_models(model_id) + listed_models = service_client.list_models() for model in listed_models: - print(model + '\n') + print(model) # Decomission models service_client.decommission_model(model_id) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_list.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_list.py index efd1a18b2650..d4defe0e9158 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_list.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_models_list.py @@ -30,11 +30,9 @@ service_client = DigitalTwinsClient(url, credential) # List models - # from the samples: dtmi:samples:Room1, dtmi:samples:Wifi1, dtmi:samples:Floor1, dtmi:samples:Building1 - dependecies_for = [""] - models = service_client.list_models(dependecies_for) + models = service_client.list_models() for model in models: - print(model + '\n') + print(model) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_publish_component_telemetry.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_publish_component_telemetry.py index 82594203651b..50c4bd3a294d 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_publish_component_telemetry.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_publish_component_telemetry.py @@ -31,11 +31,11 @@ # Publish telemetry message digita_twin_id = "" - component_path = "" + component_name = "" telemetry_payload = '{"Telemetry1": 5}' service_client.publish_component_telemetry( digita_twin_id, - component_path, + component_name, telemetry_payload ) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_relationships_list.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_relationships_list.py index d01c4a8be7a3..4da7ac0470dc 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_relationships_list.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_relationships_list.py @@ -33,7 +33,7 @@ digital_twint_id = "" # from the samples: BuildingTwin, FloorTwin, HVACTwin, RoomTwin relationships = service_client.list_relationships(digital_twint_id) for relationship in relationships: - print(relationship + '\n') + print(relationship) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message)) diff --git a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_scenario.py b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_scenario.py index 932ccb8c2adc..87150a657f3d 100644 --- a/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_scenario.py +++ b/sdk/digitaltwins/azure-digitaltwins-core/samples/dt_scenario.py @@ -3,11 +3,10 @@ # Licensed under the MIT License. # ------------------------------------ import os -import json import uuid from azure.identity import DefaultAzureCredential from azure.core.exceptions import HttpResponseError -from azure.digitaltwins.core import DigitalTwinsClient +from azure.digitaltwins.core import DigitalTwinsClient, DigitalTwinsEventRoute # # This sample creates all the models in \DTDL\Models folder in the ADT service instance @@ -51,6 +50,220 @@ # For the purpose of this example we will create temporary model and a temporay component model using random Ids. # We have to make sure these model Ids are unique within the DT instance so we use generated UUIDs. try: + building_model_id = 'dtmi:samples:Building;1' + building_model = { + "@id": building_model_id, + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Building", + "contents": [ + { + "@type": "Relationship", + "name": "has", + "target": "dtmi:samples:Floor;1", + "properties": [ + { + "@type": "Property", + "name": "isAccessRestricted", + "schema": "boolean" + } + ] + }, + { + "@type": "Relationship", + "name": "isEquippedWith", + "target": "dtmi:samples:HVAC;1" + }, + { + "@type": "Property", + "name": "AverageTemperature", + "schema": "double" + }, + { + "@type": "Property", + "name": "TemperatureUnit", + "schema": "string" + } + ] + } + + floor_model_id = 'dtmi:samples:Floor;1' + floor_model = { + "@id": floor_model_id, + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Floor", + "contents": [ + { + "@type": "Relationship", + "name": "contains", + "target": "dtmi:samples:Room;1" + }, + { + "@type": "Property", + "name": "AverageTemperature", + "schema": "double" + } + ] + } + + hvac_model_id = 'dtmi:samples:HVAC;1' + hvac_model = { + "@id": hvac_model_id, + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "HVAC", + "contents": [ + { + "@type": "Property", + "name": "Efficiency", + "schema": "double" + }, + { + "@type": "Property", + "name": "TargetTemperature", + "schema": "double" + }, + { + "@type": "Property", + "name": "TargetHumidity", + "schema": "double" + }, + { + "@type": "Relationship", + "name": "controlsTemperature", + "target": "dtmi:samples:Floor;1" + } + ] + } + + room_model_id = 'dtmi:samples:Room;1' + room_model = { + "@id": room_model_id, + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Room", + "contents": [ + { + "@type": "Property", + "name": "Temperature", + "schema": "double" + }, + { + "@type": "Property", + "name": "Humidity", + "schema": "double" + }, + { + "@type": "Property", + "name": "IsOccupied", + "schema": "boolean" + }, + { + "@type": "Property", + "name": "EmployeeId", + "schema": "string" + }, + { + "@type": "Component", + "name": "wifiAccessPoint", + "schema": "dtmi:samples:Wifi;1" + } + ] + } + + wifi_model_id = 'dtmi:samples:Wifi;1' + wifi_model = { + "@id": wifi_model_id, + "@type": "Interface", + "@context": "dtmi:dtdl:context;2", + "displayName": "Wifi", + "contents": [ + { + "@type": "Property", + "name": "RouterName", + "schema": "string" + }, + { + "@type": "Property", + "name": "Network", + "schema": "string" + } + ] + } + + building_twin_id = 'BuildingTwin-' + str(uuid.uuid4()) + building_twin = { + "$metadata": { + "$model": 'dtmi:samples:Building;1' + }, + "$dtId": building_twin_id, + "AverageTemperature": 68, + "TemperatureUnit": "Celsius" + } + + floor_twin_id = 'FloorTwin-' + str(uuid.uuid4()) + floor_twin = { + "$metadata": { + "$model": "dtmi:samples:Floor;1" + }, + "AverageTemperature": 75 + } + + hvac_twin_id = 'HVACTwin-' + str(uuid.uuid4()) + hvac_twin = { + "$metadata": { + "$model": "dtmi:samples:HVAC;1" + }, + "Efficiency": 94, + "TargetTemperature": 72, + "TargetHumidity": 30 + } + + room_twin_id = 'RoomTwin-' + str(uuid.uuid4()) + room_twin = { + "$metadata": { + "$model": "dtmi:samples:Room;1" + }, + "Temperature": 80, + "Humidity": 25, + "IsOccupied": True, + "EmployeeId": "Employee1", + "wifiAccessPoint": { + "$metadata": {}, + "RouterName": "Cisco1", + "Network": "Room1" + } + } + + hospital_relationships = [ + { + "$relationshipId": "BuildingHasFloor", + "$sourceId": building_twin_id, + "$relationshipName": "has", + "$targetId": floor_twin_id, + "isAccessRestricted": False + }, + { + "$relationshipId": "BuildingIsEquippedWithHVAC", + "$sourceId": building_twin_id, + "$relationshipName": "isEquippedWith", + "$targetId": hvac_twin_id + }, + { + "$relationshipId": "HVACCoolsFloor", + "$sourceId": hvac_twin_id, + "$relationshipName": "controlsTemperature", + "$targetId": floor_twin_id + }, + { + "$relationshipId": "FloorContainsRoom", + "$sourceId": floor_twin_id, + "$relationshipName": "contains", + "$targetId": room_twin_id + } + ] + # DefaultAzureCredential supports different authentication mechanisms and determines # the appropriate credential type based of the environment it is executing in. # It attempts to use multiple credential types in an order until it finds a working credential. @@ -67,63 +280,31 @@ credential = DefaultAzureCredential() service_client = DigitalTwinsClient(url, credential) - # Create models from the sample dtdls - with open(r"dtdl\models\building.json") as f: - dtdl_model_building = json.load(f) - - with open(r"dtdl\models\floor.json") as f: - dtdl_model_floor = json.load(f) - - with open(r"dtdl\models\hvac.json") as f: - dtdl_model_hvac = json.load(f) - - with open(r"dtdl\models\room.json") as f: - dtdl_model_room = json.load(f) - - new_model_list = [] - new_model_list.append( - dtdl_model_building, - dtdl_model_floor, - dtdl_model_hvac, - dtdl_model_room - ) + # Create models + new_model_list = [building_model, floor_model, hvac_model, room_model, wifi_model] models = service_client.create_models(new_model_list) print('Created Models:') print(models) - # Create digital twins from the sample dtdls - building_twin_id = 'BuildingTwin-' + str(uuid.uuid4()) - with open(r"dtdl\digital_twins\buildingTwin.json") as f: - dtdl_digital_twins_building = json.load(f) - created_building_twin = service_client.upsert_digital_twin(building_twin_id, dtdl_digital_twins_building) + # Create digital twins + created_building_twin = service_client.upsert_digital_twin(building_twin_id, building_twin) print('BuildingTwin:') print(created_building_twin) - floor_twin_id = 'FloorTwin-' + str(uuid.uuid4()) - with open(r"dtdl\digital_twins\floorTwin.json") as f: - dtdl_digital_twins_floor = json.load(f) - created_floor_twin = service_client.upsert_digital_twin(floor_twin_id, dtdl_digital_twins_floor) + created_floor_twin = service_client.upsert_digital_twin(floor_twin_id, floor_twin) print('FloorTwin:') print(created_floor_twin) - hvac_twin_id = 'HVACTwin-' + str(uuid.uuid4()) - with open(r"dtdl\digital_twins\hvacTwin.json") as f: - dtdl_digital_twins_hvac = json.load(f) - created_hvac_twin = service_client.upsert_digital_twin(hvac_twin_id, dtdl_digital_twins_hvac) + created_hvac_twin = service_client.upsert_digital_twin(hvac_twin_id, hvac_twin) print('HVACTwin:') print(created_hvac_twin) - room_twin_id = 'RoomTwin-' + str(uuid.uuid4()) - with open(r"dtdl\digital_twins\hvacTwin.json") as f: - dtdl_digital_twins_room = json.load(f) - created_room_twin = service_client.upsert_digital_twin(room_twin_id, dtdl_digital_twins_room) + created_room_twin = service_client.upsert_digital_twin(room_twin_id, room_twin) print('RoomTwin:') print(created_room_twin) - # Create digital relationships from the sample dtdls - with open(r"dtdl\relationships\hospitalRelationships.json") as f: - dtdl_relationships = json.load(f) - for relationship in dtdl_relationships: + # Create digital relationships + for relationship in hospital_relationships: service_client.upsert_relationship( relationship["$sourceId"], relationship["$relationshipId"], @@ -133,11 +314,11 @@ # Create event route event_route_id = 'eventRoute-' + str(uuid.uuid4()) event_filter = "$eventType = 'DigitalTwinTelemetryMessages' or $eventType = 'DigitalTwinLifecycleNotification'" - service_client.upsert_event_route( - event_route_id, - event_hub_endpoint_name, - **{"filter": event_filter} - ) + route = DigitalTwinsEventRoute( + endpoint_name=event_hub_endpoint_name, + filter=event_filter + ) + service_client.upsert_event_route(event_route_id, route) # Get event route created_event_route = service_client.get_event_route(event_route_id) @@ -147,7 +328,7 @@ # Clean up service_client.delete_event_route(event_route_id) - for relationship in dtdl_relationships: + for relationship in hospital_relationships: service_client.delete_relationship( relationship["$sourceId"], relationship["$relationshipId"] @@ -158,15 +339,17 @@ service_client.delete_digital_twin(hvac_twin_id) service_client.delete_digital_twin(room_twin_id) - service_client.decommission_model(building_twin_id) - service_client.decommission_model(floor_twin_id) - service_client.decommission_model(hvac_twin_id) - service_client.decommission_model(room_twin_id) + service_client.decommission_model(building_model_id) + service_client.decommission_model(floor_model_id) + service_client.decommission_model(hvac_model_id) + service_client.decommission_model(room_model_id) + service_client.decommission_model(wifi_model_id) - service_client.delete_model(building_twin_id) - service_client.delete_model(floor_twin_id) - service_client.delete_model(hvac_twin_id) - service_client.delete_model(room_twin_id) + service_client.delete_model(building_model_id) + service_client.delete_model(floor_model_id) + service_client.delete_model(hvac_model_id) + service_client.delete_model(room_model_id) + service_client.delete_model(wifi_model_id) except HttpResponseError as e: print("\nThis sample has caught an error. {0}".format(e.message))