durabletask 0.4.0__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of durabletask might be problematic. Click here for more details.

@@ -1,7 +1,11 @@
1
+ import durabletask.internal.orchestrator_service_pb2 as pb
2
+
3
+
1
4
  class VersionFailureException(Exception):
2
- pass
5
+ def __init__(self, error_details: pb.TaskFailureDetails) -> None:
6
+ super().__init__()
7
+ self.error_details = error_details
3
8
 
4
9
 
5
10
  class AbandonOrchestrationError(Exception):
6
- def __init__(self, *args: object) -> None:
7
- super().__init__(*args)
11
+ pass
durabletask/worker.py CHANGED
@@ -111,7 +111,7 @@ class VersioningOptions:
111
111
 
112
112
  Args:
113
113
  version: The version of orchestrations that the worker can work on.
114
- default_version: The default version that will be used for starting new orchestrations.
114
+ default_version: The default version that will be used for starting new sub-orchestrations.
115
115
  match_strategy: The versioning strategy for the Durable Task worker.
116
116
  failure_strategy: The versioning failure strategy for the Durable Task worker.
117
117
  """
@@ -970,7 +970,6 @@ class _OrchestrationExecutor:
970
970
  )
971
971
 
972
972
  ctx = _RuntimeOrchestrationContext(instance_id, self._registry)
973
- version_failure = None
974
973
  try:
975
974
  # Rebuild local state by replaying old history into the orchestrator function
976
975
  self._logger.debug(
@@ -980,23 +979,6 @@ class _OrchestrationExecutor:
980
979
  for old_event in old_events:
981
980
  self.process_event(ctx, old_event)
982
981
 
983
- # Process versioning if applicable
984
- execution_started_events = [e.executionStarted for e in old_events if e.HasField("executionStarted")]
985
- # We only check versioning if there are executionStarted events - otherwise, on the first replay when
986
- # ctx.version will be Null, we may invalidate orchestrations early depending on the versioning strategy.
987
- if self._registry.versioning and len(execution_started_events) > 0:
988
- version_failure = self.evaluate_orchestration_versioning(
989
- self._registry.versioning,
990
- ctx.version
991
- )
992
- if version_failure:
993
- self._logger.warning(
994
- f"Orchestration version did not meet worker versioning requirements. "
995
- f"Error action = '{self._registry.versioning.failure_strategy}'. "
996
- f"Version error = '{version_failure}'"
997
- )
998
- raise pe.VersionFailureException
999
-
1000
982
  # Get new actions by executing newly received events into the orchestrator function
1001
983
  if self._logger.level <= logging.DEBUG:
1002
984
  summary = _get_new_event_summary(new_events)
@@ -1009,8 +991,8 @@ class _OrchestrationExecutor:
1009
991
 
1010
992
  except pe.VersionFailureException as ex:
1011
993
  if self._registry.versioning and self._registry.versioning.failure_strategy == VersionFailureStrategy.FAIL:
1012
- if version_failure:
1013
- ctx.set_failed(version_failure)
994
+ if ex.error_details:
995
+ ctx.set_failed(ex.error_details)
1014
996
  else:
1015
997
  ctx.set_failed(ex)
1016
998
  elif self._registry.versioning and self._registry.versioning.failure_strategy == VersionFailureStrategy.REJECT:
@@ -1068,6 +1050,19 @@ class _OrchestrationExecutor:
1068
1050
  if event.executionStarted.version:
1069
1051
  ctx._version = event.executionStarted.version.value
1070
1052
 
1053
+ if self._registry.versioning:
1054
+ version_failure = self.evaluate_orchestration_versioning(
1055
+ self._registry.versioning,
1056
+ ctx.version
1057
+ )
1058
+ if version_failure:
1059
+ self._logger.warning(
1060
+ f"Orchestration version did not meet worker versioning requirements. "
1061
+ f"Error action = '{self._registry.versioning.failure_strategy}'. "
1062
+ f"Version error = '{version_failure}'"
1063
+ )
1064
+ raise pe.VersionFailureException(version_failure)
1065
+
1071
1066
  # deserialize the input, if any
1072
1067
  input = None
1073
1068
  if (
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: durabletask
3
+ Version: 0.4.1
4
+ Summary: A Durable Task Client SDK for Python
5
+ License: MIT License
6
+
7
+ Copyright (c) Microsoft Corporation.
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE
26
+
27
+ Project-URL: repository, https://github.com/microsoft/durabletask-python
28
+ Project-URL: changelog, https://github.com/microsoft/durabletask-python/blob/main/CHANGELOG.md
29
+ Keywords: durable,task,workflow
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Requires-Python: >=3.9
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: grpcio
37
+ Requires-Dist: protobuf
38
+ Requires-Dist: asyncio
39
+ Requires-Dist: packaging
40
+ Dynamic: license-file
41
+
42
+ # Durable Task SDK for Python
43
+
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
45
+ [![Build Validation](https://github.com/microsoft/durabletask-python/actions/workflows/pr-validation.yml/badge.svg)](https://github.com/microsoft/durabletask-python/actions/workflows/pr-validation.yml)
46
+ [![PyPI version](https://badge.fury.io/py/durabletask.svg)](https://badge.fury.io/py/durabletask)
47
+
48
+ This repo contains a Python SDK for use with the [Azure Durable Task Scheduler](https://github.com/Azure/Durable-Task-Scheduler). With this SDK, you can define, schedule, and manage durable orchestrations using ordinary Python code.
49
+
50
+ > Note that this SDK is **not** currently compatible with [Azure Durable Functions](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview). If you are looking for a Python SDK for Azure Durable Functions, please see [this repo](https://github.com/Azure/azure-functions-durable-python).
51
+
52
+ # References
53
+ - [Supported Patterns](./docs/supported-patterns.md)
54
+ - [Available Features](./docs/features.md)
55
+ - [Getting Started](./docs/getting-started.md)
56
+ - [Development Guide](./docs/development.md)
57
+ - [Contributing Guide](./CONTRIBUTING.md)
58
+
59
+ ## Trademarks
60
+ This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
61
+ trademarks or logos is subject to and must follow
62
+ [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
63
+ Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
64
+ Any use of third-party trademarks or logos are subject to those third-party's policies.
@@ -1,16 +1,16 @@
1
1
  durabletask/__init__.py,sha256=1PTnFPvigLCs2apa7ITASeqqFOS09Zn-rrllyWeoDJE,263
2
2
  durabletask/client.py,sha256=VrHXntWfSk4xYAv7JkOOXgNoFzefYWcD7UPGgn2zuWM,10312
3
3
  durabletask/task.py,sha256=LCtKrDh_Yb94Zjxx8fUtXt3z5GwSaV-EieIFXKeZNOI,18404
4
- durabletask/worker.py,sha256=fmKqjrBdU3eTq9sdVhFyjbMPgg_b60ASHggdsKvcaJQ,73669
5
- durabletask/internal/exceptions.py,sha256=GVtYAhyCtoPFcbddW2rClskBIc1FkcqTOCz_EZiBd9o,176
4
+ durabletask/worker.py,sha256=B0cUfzbgQW2WlYzixvQqRZkWRtvsETdD3jDLz64RwjU,73279
5
+ durabletask/internal/exceptions.py,sha256=G1qnH5KReJk04_eewMIr2TTXvdeWDriq-SiAwSahMMs,298
6
6
  durabletask/internal/grpc_interceptor.py,sha256=KGl8GGIbNdiEnWVLwQwkOemWvIlcEO0dh-_Tg20h5XA,2834
7
7
  durabletask/internal/helpers.py,sha256=7A1Bb-KNuAovQTOh9mxeTFahCPtl45C5j5tUJ43BXuo,7538
8
8
  durabletask/internal/orchestrator_service_pb2.py,sha256=q4elBQnofrZ4eYVCVnmed4vsa-FLNFLeBRtFOZYhTv8,44631
9
9
  durabletask/internal/orchestrator_service_pb2.pyi,sha256=FFxZhxdV7SX0pKxwKPHTGaAdXKowXnib4WvjhGFw2eo,66480
10
10
  durabletask/internal/orchestrator_service_pb2_grpc.py,sha256=5xhDLJ73Ipsp1tjwWhUqVqEm30MjWHlj71zfPNBehWc,54366
11
11
  durabletask/internal/shared.py,sha256=dKRGU8z1EQM4_YA6zkKeKfiaWbiZ6-B8lP-wHy7Q_jI,4379
12
- durabletask-0.4.0.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
13
- durabletask-0.4.0.dist-info/METADATA,sha256=7ua3w7kWAdszCcUvchLxY_wWFdPd2r6Bb_Ca6N57_sk,12894
14
- durabletask-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- durabletask-0.4.0.dist-info/top_level.txt,sha256=EBVyuKWnjOwq8bJI1Uvb9U3c4fzQxACWj9p83he6fik,12
16
- durabletask-0.4.0.dist-info/RECORD,,
12
+ durabletask-0.4.1.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
13
+ durabletask-0.4.1.dist-info/METADATA,sha256=NMPUDyoxbMC33_X068dk80Ml3p3LKmXuTDV4VQlusw4,3724
14
+ durabletask-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ durabletask-0.4.1.dist-info/top_level.txt,sha256=EBVyuKWnjOwq8bJI1Uvb9U3c4fzQxACWj9p83he6fik,12
16
+ durabletask-0.4.1.dist-info/RECORD,,
@@ -1,254 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: durabletask
3
- Version: 0.4.0
4
- Summary: A Durable Task Client SDK for Python
5
- License: MIT License
6
-
7
- Copyright (c) Microsoft Corporation.
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a copy
10
- of this software and associated documentation files (the "Software"), to deal
11
- in the Software without restriction, including without limitation the rights
12
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- copies of the Software, and to permit persons to whom the Software is
14
- furnished to do so, subject to the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be included in all
17
- copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
- SOFTWARE
26
-
27
- Project-URL: repository, https://github.com/microsoft/durabletask-python
28
- Project-URL: changelog, https://github.com/microsoft/durabletask-python/blob/main/CHANGELOG.md
29
- Keywords: durable,task,workflow
30
- Classifier: Development Status :: 3 - Alpha
31
- Classifier: Programming Language :: Python :: 3
32
- Classifier: License :: OSI Approved :: MIT License
33
- Requires-Python: >=3.9
34
- Description-Content-Type: text/markdown
35
- License-File: LICENSE
36
- Requires-Dist: grpcio
37
- Requires-Dist: protobuf
38
- Requires-Dist: asyncio
39
- Requires-Dist: packaging
40
- Dynamic: license-file
41
-
42
- # Durable Task SDK for Python
43
-
44
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
45
- [![Build Validation](https://github.com/microsoft/durabletask-python/actions/workflows/pr-validation.yml/badge.svg)](https://github.com/microsoft/durabletask-python/actions/workflows/pr-validation.yml)
46
- [![PyPI version](https://badge.fury.io/py/durabletask.svg)](https://badge.fury.io/py/durabletask)
47
-
48
- This repo contains a Python SDK for use with the [Azure Durable Task Scheduler](https://techcommunity.microsoft.com/blog/appsonazureblog/announcing-limited-early-access-of-the-durable-task-scheduler-for-azure-durable-/4286526) and the [Durable Task Framework for Go](https://github.com/microsoft/durabletask-go). With this SDK, you can define, schedule, and manage durable orchestrations using ordinary Python code.
49
-
50
- ⚠️ **This SDK is currently under active development and is not yet ready for production use.** ⚠️
51
-
52
- > Note that this SDK is **not** currently compatible with [Azure Durable Functions](https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-overview). If you are looking for a Python SDK for Azure Durable Functions, please see [this repo](https://github.com/Azure/azure-functions-durable-python).
53
-
54
- ## Supported patterns
55
-
56
- The following orchestration patterns are currently supported.
57
-
58
- ### Function chaining
59
-
60
- An orchestration can chain a sequence of function calls using the following syntax:
61
-
62
- ```python
63
- # simple activity function that returns a greeting
64
- def hello(ctx: task.ActivityContext, name: str) -> str:
65
- return f'Hello {name}!'
66
-
67
- # orchestrator function that sequences the activity calls
68
- def sequence(ctx: task.OrchestrationContext, _):
69
- result1 = yield ctx.call_activity(hello, input='Tokyo')
70
- result2 = yield ctx.call_activity(hello, input='Seattle')
71
- result3 = yield ctx.call_activity(hello, input='London')
72
-
73
- return [result1, result2, result3]
74
- ```
75
-
76
- You can find the full sample [here](./examples/activity_sequence.py).
77
-
78
- ### Fan-out/fan-in
79
-
80
- An orchestration can fan-out a dynamic number of function calls in parallel and then fan-in the results using the following syntax:
81
-
82
- ```python
83
- # activity function for getting the list of work items
84
- def get_work_items(ctx: task.ActivityContext, _) -> List[str]:
85
- # ...
86
-
87
- # activity function for processing a single work item
88
- def process_work_item(ctx: task.ActivityContext, item: str) -> int:
89
- # ...
90
-
91
- # orchestrator function that fans-out the work items and then fans-in the results
92
- def orchestrator(ctx: task.OrchestrationContext, _):
93
- # the number of work-items is unknown in advance
94
- work_items = yield ctx.call_activity(get_work_items)
95
-
96
- # fan-out: schedule the work items in parallel and wait for all of them to complete
97
- tasks = [ctx.call_activity(process_work_item, input=item) for item in work_items]
98
- results = yield task.when_all(tasks)
99
-
100
- # fan-in: summarize and return the results
101
- return {'work_items': work_items, 'results': results, 'total': sum(results)}
102
- ```
103
-
104
- You can find the full sample [here](./examples/fanout_fanin.py).
105
-
106
- ### Human interaction and durable timers
107
-
108
- An orchestration can wait for a user-defined event, such as a human approval event, before proceding to the next step. In addition, the orchestration can create a timer with an arbitrary duration that triggers some alternate action if the external event hasn't been received:
109
-
110
- ```python
111
- def purchase_order_workflow(ctx: task.OrchestrationContext, order: Order):
112
- """Orchestrator function that represents a purchase order workflow"""
113
- # Orders under $1000 are auto-approved
114
- if order.Cost < 1000:
115
- return "Auto-approved"
116
-
117
- # Orders of $1000 or more require manager approval
118
- yield ctx.call_activity(send_approval_request, input=order)
119
-
120
- # Approvals must be received within 24 hours or they will be canceled.
121
- approval_event = ctx.wait_for_external_event("approval_received")
122
- timeout_event = ctx.create_timer(timedelta(hours=24))
123
- winner = yield task.when_any([approval_event, timeout_event])
124
- if winner == timeout_event:
125
- return "Canceled"
126
-
127
- # The order was approved
128
- yield ctx.call_activity(place_order, input=order)
129
- approval_details = approval_event.get_result()
130
- return f"Approved by '{approval_details.approver}'"
131
- ```
132
-
133
- As an aside, you'll also notice that the example orchestration above works with custom business objects. Support for custom business objects includes support for custom classes, custom data classes, and named tuples. Serialization and deserialization of these objects is handled automatically by the SDK.
134
-
135
- You can find the full sample [here](./examples/human_interaction.py).
136
-
137
- ## Feature overview
138
-
139
- The following features are currently supported:
140
-
141
- ### Orchestrations
142
-
143
- Orchestrations are implemented using ordinary Python functions that take an `OrchestrationContext` as their first parameter. The `OrchestrationContext` provides APIs for starting child orchestrations, scheduling activities, and waiting for external events, among other things. Orchestrations are fault-tolerant and durable, meaning that they can automatically recover from failures and rebuild their local execution state. Orchestrator functions must be deterministic, meaning that they must always produce the same output given the same input.
144
-
145
- ### Activities
146
-
147
- Activities are implemented using ordinary Python functions that take an `ActivityContext` as their first parameter. Activity functions are scheduled by orchestrations and have at-least-once execution guarantees, meaning that they will be executed at least once but may be executed multiple times in the event of a transient failure. Activity functions are where the real "work" of any orchestration is done.
148
-
149
- ### Durable timers
150
-
151
- Orchestrations can schedule durable timers using the `create_timer` API. These timers are durable, meaning that they will survive orchestrator restarts and will fire even if the orchestrator is not actively in memory. Durable timers can be of any duration, from milliseconds to months.
152
-
153
- ### Sub-orchestrations
154
-
155
- Orchestrations can start child orchestrations using the `call_sub_orchestrator` API. Child orchestrations are useful for encapsulating complex logic and for breaking up large orchestrations into smaller, more manageable pieces.
156
-
157
- ### External events
158
-
159
- Orchestrations can wait for external events using the `wait_for_external_event` API. External events are useful for implementing human interaction patterns, such as waiting for a user to approve an order before continuing.
160
-
161
- ### Continue-as-new (TODO)
162
-
163
- Orchestrations can be continued as new using the `continue_as_new` API. This API allows an orchestration to restart itself from scratch, optionally with a new input.
164
-
165
- ### Suspend, resume, and terminate
166
-
167
- Orchestrations can be suspended using the `suspend_orchestration` client API and will remain suspended until resumed using the `resume_orchestration` client API. A suspended orchestration will stop processing new events, but will continue to buffer any that happen to arrive until resumed, ensuring that no data is lost. An orchestration can also be terminated using the `terminate_orchestration` client API. Terminated orchestrations will stop processing new events and will discard any buffered events.
168
-
169
- ### Retry policies (TODO)
170
-
171
- Orchestrations can specify retry policies for activities and sub-orchestrations. These policies control how many times and how frequently an activity or sub-orchestration will be retried in the event of a transient error.
172
-
173
- ## Getting Started
174
-
175
- ### Prerequisites
176
-
177
- - Python 3.9
178
- - A Durable Task-compatible sidecar, like [Dapr Workflow](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-overview/)
179
-
180
- ### Installing the Durable Task Python client SDK
181
-
182
- Installation is currently only supported from source. Ensure pip, setuptools, and wheel are up-to-date.
183
-
184
- ```sh
185
- python3 -m pip install --upgrade pip setuptools wheel
186
- ```
187
-
188
- To install this package from source, clone this repository and run the following command from the project root:
189
-
190
- ```sh
191
- python3 -m pip install .
192
- ```
193
-
194
- ### Run the samples
195
-
196
- See the [examples](./examples) directory for a list of sample orchestrations and instructions on how to run them.
197
-
198
- ## Development
199
-
200
- The following is more information about how to develop this project. Note that development commands require that `make` is installed on your local machine. If you're using Windows, you can install `make` using [Chocolatey](https://chocolatey.org/) or use WSL.
201
-
202
- ### Generating protobufs
203
-
204
- ```sh
205
- pip3 install -r dev-requirements.txt
206
- make gen-proto
207
- ```
208
-
209
- This will download the `orchestrator_service.proto` from the `microsoft/durabletask-protobuf` repo and compile it using `grpcio-tools`. The version of the source proto file that was downloaded can be found in the file `durabletask/internal/PROTO_SOURCE_COMMIT_HASH`.
210
-
211
- ### Running unit tests
212
-
213
- Unit tests can be run using the following command from the project root. Unit tests _don't_ require a sidecar process to be running.
214
-
215
- ```sh
216
- make test-unit
217
- ```
218
-
219
- ### Running E2E tests
220
-
221
- The E2E (end-to-end) tests require a sidecar process to be running. You can use the Dapr sidecar for this or run a Durable Task test sidecar using the following command:
222
-
223
- ```sh
224
- go install github.com/microsoft/durabletask-go@main
225
- durabletask-go --port 4001
226
- ```
227
-
228
- To run the E2E tests, run the following command from the project root:
229
-
230
- ```sh
231
- make test-e2e
232
- ```
233
-
234
- ## Contributing
235
-
236
- This project welcomes contributions and suggestions. Most contributions require you to agree to a
237
- Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
238
- the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
239
-
240
- When you submit a pull request, a CLA bot will automatically determine whether you need to provide
241
- a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
242
- provided by the bot. You will only need to do this once across all repos using our CLA.
243
-
244
- This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
245
- For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
246
- contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
247
-
248
- ## Trademarks
249
-
250
- This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
251
- trademarks or logos is subject to and must follow
252
- [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
253
- Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
254
- Any use of third-party trademarks or logos are subject to those third-party's policies.