diff --git a/CODEOWNERS b/CODEOWNERS index 7f1a5c9e8..7847b460d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ -# These owners are the maintainers and approvers of this repo -* @dapr/maintainers-python-sdk @dapr/approvers-python-sdk +# These owners are the maintainers and approvers of this repo +* @dapr/maintainers-python-sdk @dapr/approvers-python-sdk diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d173bd048..36e8c80ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,129 +1,129 @@ -# Contribution Guidelines - -Thank you for your interest in Dapr! - -This project welcomes contributions and suggestions. Most contributions require you to signoff on your commits via -the Developer Certificate of Origin (DCO). When you submit a pull request, a DCO-bot will automatically determine -whether you need to provide signoff for your commit. Please follow the instructions provided by DCO-bot, as pull -requests cannot be merged until the author(s) have provided signoff to fulfill the DCO requirement. -You may find more information on the DCO requirements [below](#developer-certificate-of-origin-signing-your-work). - -This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/dapr/community/blob/master/CODE-OF-CONDUCT.md). - -Contributions come in many forms: submitting issues, writing code, participating in discussions and community calls. - -This document provides the guidelines for how to contribute to the Dapr project. - -## Issues - -This section describes the guidelines for submitting issues - -### Issue Types - -There are 4 types of issues: - -- Issue/Bug: You've found a bug with the code, and want to report it, or create an issue to track the bug. -- Issue/Discussion: You have something on your mind, which requires input form others in a discussion, before it eventually manifests as a proposal. -- Issue/Proposal: Used for items that propose a new idea or functionality. This allows feedback from others before code is written. -- Issue/Question: Use this issue type, if you need help or have a question. - -### Before You File - -Before you file an issue, make sure you've checked the following: - -1. Is it the right repository? - - The Dapr project is distributed across multiple repositories. Check the list of [repositories](https://github.com/dapr) if you aren't sure which repo is the correct one. -1. Check for existing issues - - Before you create a new issue, please do a search in [open issues](https://github.com/dapr/python-sdk/issues) to see if the issue or feature request has already been filed. - - If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reaction-to-pull-requests-issues-and-comments). Use a reaction: - - 👍 up-vote - - 👎 down-vote -1. For bugs - - Check it's not an environment issue. For example, if running on Kubernetes, make sure prerequisites are in place. (state stores, bindings, etc.) - - You have as much data as possible. This usually comes in the form of logs and/or stacktrace. If running on Kubernetes or other environment, look at the logs of the Dapr services (runtime, operator, placement service). More details on how to get logs can be found [here](https://github.com/dapr/docs/tree/master/best-practices/troubleshooting/logs.md). -1. For proposals - - Many changes to the Dapr runtime may require changes to the API. In that case, the best place to discuss the potential feature is the main [Dapr repo](https://github.com/dapr/dapr). - - Other examples could include bindings, state stores or entirely new components. - -## Contributing to Dapr - -This section describes the guidelines for contributing code / docs to Dapr. - -### Pull Requests - -All contributions come through pull requests. To submit a proposed change, we recommend following this workflow: - -1. Make sure there's an issue (bug or proposal) raised, which sets the expectations for the contribution you are about to make. -1. Fork the relevant repo and create a new branch -1. Create your change - - Code changes require tests -1. Update relevant documentation for the change -1. Commit and open a PR -1. Wait for the CI process to finish and make sure all checks are green -1. A maintainer of the project will be assigned, and you can expect a review within a few days - -#### Use work-in-progress PRs for early feedback - -A good way to communicate before investing too much time is to create a "Work-in-progress" PR and share it with your reviewers. The standard way of doing this is to add a "[WIP]" prefix in your PR's title and assign the **do-not-merge** label. This will let people looking at your PR know that it is not well baked yet. - -### Developer Certificate of Origin: Signing your work - -#### Every commit needs to be signed - -The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full text of the [DCO](https://developercertificate.org/), reformatted for readability: -``` -By making a contribution to this project, I certify that: - - (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or - - (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or - - (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. - - (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. -``` - -Contributors sign-off that they adhere to these requirements by adding a `Signed-off-by` line to commit messages. - -``` -This is my commit message - -Signed-off-by: Random J Developer -``` -Git even has a `-s` command line option to append this automatically to your commit message: -``` -$ git commit -s -m 'This is my commit message' -``` - -Each Pull Request is checked whether or not commits in a Pull Request do contain a valid Signed-off-by line. - -#### I didn't sign my commit, now what?! - -No worries - You can easily replay your changes, sign them and force push them! - -``` -git checkout -git commit --amend --no-edit --signoff -git push --force-with-lease -``` - -### Use of Third-party code - -- All third-party code must be placed in the `vendor/` folder. -- `vendor/` folder is managed by Go modules and stores the source code of third-party Go dependencies. - The `vendor/` folder should not be modified manually. -- Third-party code must include licenses. - -A non-exclusive list of code that must be places in `vendor/`: - -- Open source, free software, or commercially-licensed code. -- Tools or libraries or protocols that are open source, free software, or commercially licensed. - -**Thank You!** - Your contributions to open source, large or small, make projects like this possible. Thank you for taking the time to contribute. - -## Github Dapr Bot Commands - -Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, you can run the `/assign` (as a comment on an issue) to assign issues to a user or group of users. - -## Code of Conduct - -This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/dapr/community/blob/master/CODE-OF-CONDUCT.md) +# Contribution Guidelines + +Thank you for your interest in Dapr! + +This project welcomes contributions and suggestions. Most contributions require you to signoff on your commits via +the Developer Certificate of Origin (DCO). When you submit a pull request, a DCO-bot will automatically determine +whether you need to provide signoff for your commit. Please follow the instructions provided by DCO-bot, as pull +requests cannot be merged until the author(s) have provided signoff to fulfill the DCO requirement. +You may find more information on the DCO requirements [below](#developer-certificate-of-origin-signing-your-work). + +This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/dapr/community/blob/master/CODE-OF-CONDUCT.md). + +Contributions come in many forms: submitting issues, writing code, participating in discussions and community calls. + +This document provides the guidelines for how to contribute to the Dapr project. + +## Issues + +This section describes the guidelines for submitting issues + +### Issue Types + +There are 4 types of issues: + +- Issue/Bug: You've found a bug with the code, and want to report it, or create an issue to track the bug. +- Issue/Discussion: You have something on your mind, which requires input form others in a discussion, before it eventually manifests as a proposal. +- Issue/Proposal: Used for items that propose a new idea or functionality. This allows feedback from others before code is written. +- Issue/Question: Use this issue type, if you need help or have a question. + +### Before You File + +Before you file an issue, make sure you've checked the following: + +1. Is it the right repository? + - The Dapr project is distributed across multiple repositories. Check the list of [repositories](https://github.com/dapr) if you aren't sure which repo is the correct one. +1. Check for existing issues + - Before you create a new issue, please do a search in [open issues](https://github.com/dapr/python-sdk/issues) to see if the issue or feature request has already been filed. + - If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reaction-to-pull-requests-issues-and-comments). Use a reaction: + - 👍 up-vote + - 👎 down-vote +1. For bugs + - Check it's not an environment issue. For example, if running on Kubernetes, make sure prerequisites are in place. (state stores, bindings, etc.) + - You have as much data as possible. This usually comes in the form of logs and/or stacktrace. If running on Kubernetes or other environment, look at the logs of the Dapr services (runtime, operator, placement service). More details on how to get logs can be found [here](https://github.com/dapr/docs/tree/master/best-practices/troubleshooting/logs.md). +1. For proposals + - Many changes to the Dapr runtime may require changes to the API. In that case, the best place to discuss the potential feature is the main [Dapr repo](https://github.com/dapr/dapr). + - Other examples could include bindings, state stores or entirely new components. + +## Contributing to Dapr + +This section describes the guidelines for contributing code / docs to Dapr. + +### Pull Requests + +All contributions come through pull requests. To submit a proposed change, we recommend following this workflow: + +1. Make sure there's an issue (bug or proposal) raised, which sets the expectations for the contribution you are about to make. +1. Fork the relevant repo and create a new branch +1. Create your change + - Code changes require tests +1. Update relevant documentation for the change +1. Commit and open a PR +1. Wait for the CI process to finish and make sure all checks are green +1. A maintainer of the project will be assigned, and you can expect a review within a few days + +#### Use work-in-progress PRs for early feedback + +A good way to communicate before investing too much time is to create a "Work-in-progress" PR and share it with your reviewers. The standard way of doing this is to add a "[WIP]" prefix in your PR's title and assign the **do-not-merge** label. This will let people looking at your PR know that it is not well baked yet. + +### Developer Certificate of Origin: Signing your work + +#### Every commit needs to be signed + +The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full text of the [DCO](https://developercertificate.org/), reformatted for readability: +``` +By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or + + (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or + + (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. + + (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. +``` + +Contributors sign-off that they adhere to these requirements by adding a `Signed-off-by` line to commit messages. + +``` +This is my commit message + +Signed-off-by: Random J Developer +``` +Git even has a `-s` command line option to append this automatically to your commit message: +``` +$ git commit -s -m 'This is my commit message' +``` + +Each Pull Request is checked whether or not commits in a Pull Request do contain a valid Signed-off-by line. + +#### I didn't sign my commit, now what?! + +No worries - You can easily replay your changes, sign them and force push them! + +``` +git checkout +git commit --amend --no-edit --signoff +git push --force-with-lease +``` + +### Use of Third-party code + +- All third-party code must be placed in the `vendor/` folder. +- `vendor/` folder is managed by Go modules and stores the source code of third-party Go dependencies. - The `vendor/` folder should not be modified manually. +- Third-party code must include licenses. + +A non-exclusive list of code that must be places in `vendor/`: + +- Open source, free software, or commercially-licensed code. +- Tools or libraries or protocols that are open source, free software, or commercially licensed. + +**Thank You!** - Your contributions to open source, large or small, make projects like this possible. Thank you for taking the time to contribute. + +## Github Dapr Bot Commands + +Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, you can run the `/assign` (as a comment on an issue) to assign issues to a user or group of users. + +## Code of Conduct + +This project has adopted the [Contributor Covenant Code of Conduct](https://github.com/dapr/community/blob/master/CODE-OF-CONDUCT.md) diff --git a/LICENSE b/LICENSE index 9d0c8cef5..16d76d9a9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,203 +1,203 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 The Dapr Authors. - - and others that have contributed code to the public domain. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Dapr Authors. + + and others that have contributed code to the public domain. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 84da5bd8b..510c081d2 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,152 @@ -# Dapr SDK for Python - -[![PyPI - Version](https://img.shields.io/pypi/v/dapr?style=flat&logo=pypi&logoColor=white&label=Latest%20version)](https://pypi.org/project/dapr/) -[![PyPI - Downloads](https://img.shields.io/pypi/dm/dapr?style=flat&logo=pypi&logoColor=white&label=Downloads)](https://pypi.org/project/dapr/) -[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/dapr/python-sdk/.github%2Fworkflows%2Fbuild.yaml?branch=main&label=Build&logo=github)](https://github.com/dapr/python-sdk/actions/workflows/build.yaml) -[![codecov](https://codecov.io/gh/dapr/python-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/dapr/python-sdk) -[![GitHub License](https://img.shields.io/github/license/dapr/python-sdk?style=flat&label=License&logo=github)](https://github.com/dapr/python-sdk/blob/main/LICENSE) -[![GitHub issue custom search in repo](https://img.shields.io/github/issues-search/dapr/python-sdk?query=type%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22&label=Good%20first%20issues&style=flat&logo=github)](https://github.com/dapr/python-sdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) -[![Discord](https://img.shields.io/discord/778680217417809931?label=Discord&style=flat&logo=discord)](http://bit.ly/dapr-discord) -[![YouTube Channel Views](https://img.shields.io/youtube/channel/views/UCtpSQ9BLB_3EXdWAUQYwnRA?style=flat&label=YouTube%20views&logo=youtube)](https://youtube.com/@daprdev) -[![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/daprdev?logo=x&style=flat)](https://twitter.com/daprdev) - -[Dapr](https://docs.dapr.io/concepts/overview/) is a portable, event-driven, serverless runtime for building distributed applications across cloud and edge. - -Dapr SDK for Python allows you to implement the [Virtual Actor model](https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/), based on the actor design pattern. This SDK can run locally, in a container and in any distributed systems environment. - -This includes the following packages: - -* [dapr.actor](./dapr/actor): Actor Framework -* [dapr.clients](./dapr/clients): Dapr clients for Dapr building blocks -* [dapr.conf](./dapr/conf): Configuration -* [dapr.serializers](./dapr/serializers): serializer/deserializer -* [dapr.proto](./dapr/proto): Dapr gRPC autogenerated gRPC clients -* [dapr.ext.grpc](./ext/dapr-ext-grpc): gRPC extension for Dapr -* [dapr.ext.fastapi](./ext/dapr-ext-fastapi): FastAPI extension (actor) for Dapr -* [flask.dapr](./ext/flask_dapr): Flask extension (actor) for Dapr - -## Getting started - -### Prerequisites - -* [Install Dapr standalone mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted) -* [Install Python 3.8+](https://www.python.org/downloads/) - -### Install Dapr python sdk - -* Official package - -```sh -# Install Dapr client sdk -pip3 install dapr - -# Install Dapr gRPC AppCallback service extension -pip3 install dapr-ext-grpc - -# Install Dapr Fast Api extension for Actor -pip3 install dapr-ext-fastapi -``` - -* Development package - -```sh -# Install Dapr client sdk -pip3 install dapr-dev - -# Install Dapr gRPC AppCallback service extension -pip3 install dapr-ext-grpc-dev - -# Install Dapr Fast Api extension for Actor -pip3 install dapr-ext-fastapi-dev -``` - -> Note: Do not install both packages. - -### Try out examples - -Go to [Examples](./examples) - -## Developing - -### Build and test - -1. Clone python-sdk - -```bash -git clone https://github.com/dapr/python-sdk.git -cd python-sdk -``` - -2. Install a project in a editable mode - -```bash -pip3 install -e . -pip3 install -e ./ext/dapr-ext-grpc/ -pip3 install -e ./ext/dapr-ext-fastapi/ -pip3 install -e ./ext/dapr-ext-workflow/ -``` - -3. Install required packages - -```bash -pip3 install -r dev-requirements.txt -``` - -4. Run linter - -```bash -tox -e flake8 -``` - -5. Run autofix - -```bash -tox -e ruff -``` - -6. Run unit-test - -```bash -tox -e py311 -``` - -7. Run type check - -```bash -tox -e type -``` - -8. Run examples - -```bash -tox -e examples -``` - -## Documentation - -Documentation is generated using Sphinx. Extensions used are mainly Napoleon (To process the Google Comment Style) and Autodocs (For automatically generating documentation). The `.rst` files are generated using Sphinx-Apidocs. - -To generate documentation: - -```bash -tox -e doc -``` - -The generated files will be found in `docs/_build`. - -## Generate gRPC Protobuf client - -```sh -pip3 install -r dev-requirements.txt - -./tools/regen_grpcclient.sh -``` - -## Help & Feedback - -Need help or have feedback on the SDK? Please open a GitHub issue or come chat with us in the `#python-sdk` channel of our Discord server ([click here to join](https://discord.gg/MySdVxrH)). - -## Code of Conduct - -This project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). - +# Dapr SDK for Python + +[![PyPI - Version](https://img.shields.io/pypi/v/dapr?style=flat&logo=pypi&logoColor=white&label=Latest%20version)](https://pypi.org/project/dapr/) +[![PyPI - Downloads](https://img.shields.io/pypi/dm/dapr?style=flat&logo=pypi&logoColor=white&label=Downloads)](https://pypi.org/project/dapr/) +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/dapr/python-sdk/.github%2Fworkflows%2Fbuild.yaml?branch=main&label=Build&logo=github)](https://github.com/dapr/python-sdk/actions/workflows/build.yaml) +[![codecov](https://codecov.io/gh/dapr/python-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/dapr/python-sdk) +[![GitHub License](https://img.shields.io/github/license/dapr/python-sdk?style=flat&label=License&logo=github)](https://github.com/dapr/python-sdk/blob/main/LICENSE) +[![GitHub issue custom search in repo](https://img.shields.io/github/issues-search/dapr/python-sdk?query=type%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22&label=Good%20first%20issues&style=flat&logo=github)](https://github.com/dapr/python-sdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) +[![Discord](https://img.shields.io/discord/778680217417809931?label=Discord&style=flat&logo=discord)](http://bit.ly/dapr-discord) +[![YouTube Channel Views](https://img.shields.io/youtube/channel/views/UCtpSQ9BLB_3EXdWAUQYwnRA?style=flat&label=YouTube%20views&logo=youtube)](https://youtube.com/@daprdev) +[![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/daprdev?logo=x&style=flat)](https://twitter.com/daprdev) + +[Dapr](https://docs.dapr.io/concepts/overview/) is a portable, event-driven, serverless runtime for building distributed applications across cloud and edge. + +Dapr SDK for Python allows you to implement the [Virtual Actor model](https://docs.dapr.io/developing-applications/building-blocks/actors/actors-overview/), based on the actor design pattern. This SDK can run locally, in a container and in any distributed systems environment. + +This includes the following packages: + +* [dapr.actor](./dapr/actor): Actor Framework +* [dapr.clients](./dapr/clients): Dapr clients for Dapr building blocks +* [dapr.conf](./dapr/conf): Configuration +* [dapr.serializers](./dapr/serializers): serializer/deserializer +* [dapr.proto](./dapr/proto): Dapr gRPC autogenerated gRPC clients +* [dapr.ext.grpc](./ext/dapr-ext-grpc): gRPC extension for Dapr +* [dapr.ext.fastapi](./ext/dapr-ext-fastapi): FastAPI extension (actor) for Dapr +* [flask.dapr](./ext/flask_dapr): Flask extension (actor) for Dapr + +## Getting started + +### Prerequisites + +* [Install Dapr standalone mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted) +* [Install Python 3.8+](https://www.python.org/downloads/) + +### Install Dapr python sdk + +* Official package + +```sh +# Install Dapr client sdk +pip3 install dapr + +# Install Dapr gRPC AppCallback service extension +pip3 install dapr-ext-grpc + +# Install Dapr Fast Api extension for Actor +pip3 install dapr-ext-fastapi +``` + +* Development package + +```sh +# Install Dapr client sdk +pip3 install dapr-dev + +# Install Dapr gRPC AppCallback service extension +pip3 install dapr-ext-grpc-dev + +# Install Dapr Fast Api extension for Actor +pip3 install dapr-ext-fastapi-dev +``` + +> Note: Do not install both packages. + +### Try out examples + +Go to [Examples](./examples) + +## Developing + +### Build and test + +1. Clone python-sdk + +```bash +git clone https://github.com/dapr/python-sdk.git +cd python-sdk +``` + +2. Install a project in a editable mode + +```bash +pip3 install -e . +pip3 install -e ./ext/dapr-ext-grpc/ +pip3 install -e ./ext/dapr-ext-fastapi/ +pip3 install -e ./ext/dapr-ext-workflow/ +``` + +3. Install required packages + +```bash +pip3 install -r dev-requirements.txt +``` + +4. Run linter + +```bash +tox -e flake8 +``` + +5. Run autofix + +```bash +tox -e ruff +``` + +6. Run unit-test + +```bash +tox -e py311 +``` + +7. Run type check + +```bash +tox -e type +``` + +8. Run examples + +```bash +tox -e examples +``` + +## Documentation + +Documentation is generated using Sphinx. Extensions used are mainly Napoleon (To process the Google Comment Style) and Autodocs (For automatically generating documentation). The `.rst` files are generated using Sphinx-Apidocs. + +To generate documentation: + +```bash +tox -e doc +``` + +The generated files will be found in `docs/_build`. + +## Generate gRPC Protobuf client + +```sh +pip3 install -r dev-requirements.txt + +./tools/regen_grpcclient.sh +``` + +## Help & Feedback + +Need help or have feedback on the SDK? Please open a GitHub issue or come chat with us in the `#python-sdk` channel of our Discord server ([click here to join](https://discord.gg/MySdVxrH)). + +## Code of Conduct + +This project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). + diff --git a/dapr/actor/__init__.py b/dapr/actor/__init__.py index 4323caae2..fc0b35b20 100644 --- a/dapr/actor/__init__.py +++ b/dapr/actor/__init__.py @@ -1,33 +1,33 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.actor.actor_interface import ActorInterface, actormethod -from dapr.actor.client.proxy import ActorProxy, ActorProxyFactory -from dapr.actor.id import ActorId -from dapr.actor.runtime.actor import Actor -from dapr.actor.runtime.remindable import Remindable -from dapr.actor.runtime.runtime import ActorRuntime - - -__all__ = [ - 'ActorInterface', - 'ActorProxy', - 'ActorProxyFactory', - 'ActorId', - 'Actor', - 'ActorRuntime', - 'Remindable', - 'actormethod', -] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.actor.actor_interface import ActorInterface, actormethod +from dapr.actor.client.proxy import ActorProxy, ActorProxyFactory +from dapr.actor.id import ActorId +from dapr.actor.runtime.actor import Actor +from dapr.actor.runtime.remindable import Remindable +from dapr.actor.runtime.runtime import ActorRuntime + + +__all__ = [ + 'ActorInterface', + 'ActorProxy', + 'ActorProxyFactory', + 'ActorId', + 'Actor', + 'ActorRuntime', + 'Remindable', + 'actormethod', +] diff --git a/dapr/actor/actor_interface.py b/dapr/actor/actor_interface.py index 2de1c04ad..bc0a76a6b 100644 --- a/dapr/actor/actor_interface.py +++ b/dapr/actor/actor_interface.py @@ -1,61 +1,61 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from abc import ABC -from typing import Optional - - -class ActorInterface(ABC): - """A base class that Dapr Actor inherits. - - Actor requires to inherit ActorInterface as a base class. - - Example:: - - class DaprActorInterface(ActorInterface): - @actormethod('DoActorMethod1') - async def do_actor_method1(self, param): - ... - - @actormethod('DoActorMethod2') - async def do_actor_method2(self, param): - ... - """ - - ... - - -def actormethod(name: Optional[str] = None): - """Decorate actor method to define the method invoked by the remote actor. - - This allows users to call the decorated name via the proxy client. - - Example:: - - class DaprActorInterface(ActorInterface): - @actormethod(name='DoActorCall') - async def do_actor_call(self, param): - ... - - Args: - name (str, optional): the name of actor method. - """ - - def wrapper(funcobj): - funcobj.__actormethod__ = name - funcobj.__isabstractmethod__ = True - return funcobj - - return wrapper +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from abc import ABC +from typing import Optional + + +class ActorInterface(ABC): + """A base class that Dapr Actor inherits. + + Actor requires to inherit ActorInterface as a base class. + + Example:: + + class DaprActorInterface(ActorInterface): + @actormethod('DoActorMethod1') + async def do_actor_method1(self, param): + ... + + @actormethod('DoActorMethod2') + async def do_actor_method2(self, param): + ... + """ + + ... + + +def actormethod(name: Optional[str] = None): + """Decorate actor method to define the method invoked by the remote actor. + + This allows users to call the decorated name via the proxy client. + + Example:: + + class DaprActorInterface(ActorInterface): + @actormethod(name='DoActorCall') + async def do_actor_call(self, param): + ... + + Args: + name (str, optional): the name of actor method. + """ + + def wrapper(funcobj): + funcobj.__actormethod__ = name + funcobj.__isabstractmethod__ = True + return funcobj + + return wrapper diff --git a/dapr/actor/client/__init__.py b/dapr/actor/client/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/actor/client/__init__.py +++ b/dapr/actor/client/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/actor/client/proxy.py b/dapr/actor/client/proxy.py index a7648bf97..fb9620d55 100644 --- a/dapr/actor/client/proxy.py +++ b/dapr/actor/client/proxy.py @@ -1,220 +1,220 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from abc import ABC, abstractmethod -from typing import Any, Callable, Dict, Optional, Type - -from dapr.actor.actor_interface import ActorInterface -from dapr.actor.id import ActorId -from dapr.actor.runtime._type_utils import get_dispatchable_attrs_from_interface -from dapr.clients import DaprActorClientBase, DaprActorHttpClient -from dapr.clients.retry import RetryPolicy -from dapr.serializers import Serializer, DefaultJSONSerializer -from dapr.conf import settings - -# Actor factory Callable type hint. -ACTOR_FACTORY_CALLBACK = Callable[[ActorInterface, str, str], 'ActorProxy'] - - -class ActorFactoryBase(ABC): - @abstractmethod - def create( - self, - actor_type: str, - actor_id: ActorId, - actor_interface: Optional[Type[ActorInterface]] = None, - ) -> 'ActorProxy': - ... - - -class ActorProxyFactory(ActorFactoryBase): - """A factory class that creates :class:`ActorProxy` object to the remote - actor objects. - - DefaultActorProxyFactory creates :class:`ActorProxy` with - :class:`DaprActorHttpClient` connecting to Dapr runtime. - """ - - def __init__( - self, - message_serializer=DefaultJSONSerializer(), - http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS, - retry_policy: Optional[RetryPolicy] = None, - ): - # TODO: support serializer for state store later - self._dapr_client = DaprActorHttpClient( - message_serializer, timeout=http_timeout_seconds, retry_policy=retry_policy - ) - self._message_serializer = message_serializer - - def create( - self, - actor_type: str, - actor_id: ActorId, - actor_interface: Optional[Type[ActorInterface]] = None, - ) -> 'ActorProxy': - return ActorProxy( - self._dapr_client, actor_type, actor_id, actor_interface, self._message_serializer - ) - - -class CallableProxy: - def __init__( - self, proxy: 'ActorProxy', attr_call_type: Dict[str, Any], message_serializer: Serializer - ): - self._proxy = proxy - self._attr_call_type = attr_call_type - self._message_serializer = message_serializer - - async def __call__(self, *args, **kwargs) -> Any: - if len(args) > 1: - raise ValueError('does not support multiple arguments') - - bytes_data = None - if len(args) > 0: - if isinstance(args[0], bytes): - bytes_data = args[0] - else: - bytes_data = self._message_serializer.serialize(args[0]) - - rtnval = await self._proxy.invoke_method(self._attr_call_type['actor_method'], bytes_data) - - return self._message_serializer.deserialize(rtnval, self._attr_call_type['return_types']) - - -class ActorProxy: - """A remote proxy client that is proxy to the remote :class:`Actor` - objects. - - :class:`ActorProxy` object is used for client-to-actor and actor-to-actor - communication. - """ - - _default_proxy_factory = None - - def __init__( - self, - client: DaprActorClientBase, - actor_type: str, - actor_id: ActorId, - actor_interface: Optional[Type[ActorInterface]], - message_serializer: Serializer, - ): - self._dapr_client = client - self._actor_id = actor_id - self._actor_type = actor_type - self._actor_interface = actor_interface - self._message_serializer = message_serializer - - self._dispatchable_attr: Dict[str, Dict[str, Any]] = {} - self._callable_proxies: Dict[str, CallableProxy] = {} - - @property - def actor_id(self) -> ActorId: - """Returns ActorId.""" - return self._actor_id - - @property - def actor_type(self) -> str: - """Returns actor type.""" - return self._actor_type - - @classmethod - def _get_default_factory_instance(cls): - """Lazily initializes and returns the default ActorProxyFactory instance.""" - if cls._default_proxy_factory is None: - cls._default_proxy_factory = ActorProxyFactory() - return cls._default_proxy_factory - - @classmethod - def create( - cls, - actor_type: str, - actor_id: ActorId, - actor_interface: Optional[Type[ActorInterface]] = None, - actor_proxy_factory: Optional[ActorFactoryBase] = None, - ) -> 'ActorProxy': - """Creates ActorProxy client to call actor. - - Args: - actor_type (str): the name of actor type. - actor_id (str): the id of actor. - actor_interface (:class:`ActorInterface`, optional): the actor interface derived from - :class:`ActorInterface`. - actor_proxy_factor (Callable, optional): the actor factory callable. - - Returns: - :class:`ActorProxy': new Actor Proxy client. - @param actor_proxy_factory: - """ - factory = ( - actor_proxy_factory if actor_proxy_factory else cls._get_default_factory_instance() - ) - return factory.create(actor_type, actor_id, actor_interface) - - async def invoke_method(self, method: str, raw_body: Optional[bytes] = None) -> bytes: - """Invokes actor method. - - This is the non-rpc style actor method invocation. It needs to serialize - the request object and deserialize the response body. - - If the client has Actor interface, it is recommended to use RPC style actor - invocation. - - Args: - method (str): the method defined in actor. - raw_body (bytes): the request body which will be sent to actor. - - Returns: - bytes: the response from actor method. - """ - - if raw_body is not None and not isinstance(raw_body, bytes): - raise ValueError(f'raw_body {type(raw_body)} is not bytes type') - - return await self._dapr_client.invoke_method( - self._actor_type, str(self._actor_id), method, raw_body - ) - - def __getattr__(self, name: str) -> CallableProxy: - """Enables RPC style actor method invocation. - - Args: - name (str): the name of method - - Returns: - :class:`CallableProxy`: the callable object to invoke actor method - like a RPC method call. - - Raises: - ValueError: actor_interface is not given. - AttributeError: method is not defined in Actor interface. - """ - if not self._actor_interface: - raise ValueError('actor_interface is not set. use invoke method.') - - if name not in self._dispatchable_attr: - get_dispatchable_attrs_from_interface(self._actor_interface, self._dispatchable_attr) - - attr_call_type = self._dispatchable_attr.get(name) - if attr_call_type is None: - raise AttributeError(f'{self._actor_interface.__class__} has no attribute {name}') - - if name not in self._callable_proxies: - self._callable_proxies[name] = CallableProxy( - self, attr_call_type, self._message_serializer - ) - - return self._callable_proxies[name] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from abc import ABC, abstractmethod +from typing import Any, Callable, Dict, Optional, Type + +from dapr.actor.actor_interface import ActorInterface +from dapr.actor.id import ActorId +from dapr.actor.runtime._type_utils import get_dispatchable_attrs_from_interface +from dapr.clients import DaprActorClientBase, DaprActorHttpClient +from dapr.clients.retry import RetryPolicy +from dapr.serializers import Serializer, DefaultJSONSerializer +from dapr.conf import settings + +# Actor factory Callable type hint. +ACTOR_FACTORY_CALLBACK = Callable[[ActorInterface, str, str], 'ActorProxy'] + + +class ActorFactoryBase(ABC): + @abstractmethod + def create( + self, + actor_type: str, + actor_id: ActorId, + actor_interface: Optional[Type[ActorInterface]] = None, + ) -> 'ActorProxy': + ... + + +class ActorProxyFactory(ActorFactoryBase): + """A factory class that creates :class:`ActorProxy` object to the remote + actor objects. + + DefaultActorProxyFactory creates :class:`ActorProxy` with + :class:`DaprActorHttpClient` connecting to Dapr runtime. + """ + + def __init__( + self, + message_serializer=DefaultJSONSerializer(), + http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS, + retry_policy: Optional[RetryPolicy] = None, + ): + # TODO: support serializer for state store later + self._dapr_client = DaprActorHttpClient( + message_serializer, timeout=http_timeout_seconds, retry_policy=retry_policy + ) + self._message_serializer = message_serializer + + def create( + self, + actor_type: str, + actor_id: ActorId, + actor_interface: Optional[Type[ActorInterface]] = None, + ) -> 'ActorProxy': + return ActorProxy( + self._dapr_client, actor_type, actor_id, actor_interface, self._message_serializer + ) + + +class CallableProxy: + def __init__( + self, proxy: 'ActorProxy', attr_call_type: Dict[str, Any], message_serializer: Serializer + ): + self._proxy = proxy + self._attr_call_type = attr_call_type + self._message_serializer = message_serializer + + async def __call__(self, *args, **kwargs) -> Any: + if len(args) > 1: + raise ValueError('does not support multiple arguments') + + bytes_data = None + if len(args) > 0: + if isinstance(args[0], bytes): + bytes_data = args[0] + else: + bytes_data = self._message_serializer.serialize(args[0]) + + rtnval = await self._proxy.invoke_method(self._attr_call_type['actor_method'], bytes_data) + + return self._message_serializer.deserialize(rtnval, self._attr_call_type['return_types']) + + +class ActorProxy: + """A remote proxy client that is proxy to the remote :class:`Actor` + objects. + + :class:`ActorProxy` object is used for client-to-actor and actor-to-actor + communication. + """ + + _default_proxy_factory = None + + def __init__( + self, + client: DaprActorClientBase, + actor_type: str, + actor_id: ActorId, + actor_interface: Optional[Type[ActorInterface]], + message_serializer: Serializer, + ): + self._dapr_client = client + self._actor_id = actor_id + self._actor_type = actor_type + self._actor_interface = actor_interface + self._message_serializer = message_serializer + + self._dispatchable_attr: Dict[str, Dict[str, Any]] = {} + self._callable_proxies: Dict[str, CallableProxy] = {} + + @property + def actor_id(self) -> ActorId: + """Returns ActorId.""" + return self._actor_id + + @property + def actor_type(self) -> str: + """Returns actor type.""" + return self._actor_type + + @classmethod + def _get_default_factory_instance(cls): + """Lazily initializes and returns the default ActorProxyFactory instance.""" + if cls._default_proxy_factory is None: + cls._default_proxy_factory = ActorProxyFactory() + return cls._default_proxy_factory + + @classmethod + def create( + cls, + actor_type: str, + actor_id: ActorId, + actor_interface: Optional[Type[ActorInterface]] = None, + actor_proxy_factory: Optional[ActorFactoryBase] = None, + ) -> 'ActorProxy': + """Creates ActorProxy client to call actor. + + Args: + actor_type (str): the name of actor type. + actor_id (str): the id of actor. + actor_interface (:class:`ActorInterface`, optional): the actor interface derived from + :class:`ActorInterface`. + actor_proxy_factor (Callable, optional): the actor factory callable. + + Returns: + :class:`ActorProxy': new Actor Proxy client. + @param actor_proxy_factory: + """ + factory = ( + actor_proxy_factory if actor_proxy_factory else cls._get_default_factory_instance() + ) + return factory.create(actor_type, actor_id, actor_interface) + + async def invoke_method(self, method: str, raw_body: Optional[bytes] = None) -> bytes: + """Invokes actor method. + + This is the non-rpc style actor method invocation. It needs to serialize + the request object and deserialize the response body. + + If the client has Actor interface, it is recommended to use RPC style actor + invocation. + + Args: + method (str): the method defined in actor. + raw_body (bytes): the request body which will be sent to actor. + + Returns: + bytes: the response from actor method. + """ + + if raw_body is not None and not isinstance(raw_body, bytes): + raise ValueError(f'raw_body {type(raw_body)} is not bytes type') + + return await self._dapr_client.invoke_method( + self._actor_type, str(self._actor_id), method, raw_body + ) + + def __getattr__(self, name: str) -> CallableProxy: + """Enables RPC style actor method invocation. + + Args: + name (str): the name of method + + Returns: + :class:`CallableProxy`: the callable object to invoke actor method + like a RPC method call. + + Raises: + ValueError: actor_interface is not given. + AttributeError: method is not defined in Actor interface. + """ + if not self._actor_interface: + raise ValueError('actor_interface is not set. use invoke method.') + + if name not in self._dispatchable_attr: + get_dispatchable_attrs_from_interface(self._actor_interface, self._dispatchable_attr) + + attr_call_type = self._dispatchable_attr.get(name) + if attr_call_type is None: + raise AttributeError(f'{self._actor_interface.__class__} has no attribute {name}') + + if name not in self._callable_proxies: + self._callable_proxies[name] = CallableProxy( + self, attr_call_type, self._message_serializer + ) + + return self._callable_proxies[name] diff --git a/dapr/actor/id.py b/dapr/actor/id.py index 677a1f36e..7853423ff 100644 --- a/dapr/actor/id.py +++ b/dapr/actor/id.py @@ -1,63 +1,63 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import uuid - - -class ActorId: - """ActorId that represents the identity of an actor. - - Example:: - - # create actorid with id 1 - actor_id = ActorId('1') - - # create random hex ActorId - actor_random_id = ActorId.create_random_id() - - """ - - def __init__(self, actor_id: str): - if not isinstance(actor_id, str): - raise TypeError(f'Argument actor_id must be of type str, not {type(actor_id)}') - self._id = actor_id - - @classmethod - def create_random_id(cls): - """Creates new object of :class:`ActorId` with the random id value.""" - random_id = uuid.uuid1().hex - return ActorId(random_id) - - @property - def id(self) -> str: - """Gets Actor ID string.""" - return self._id - - def __hash__(self): - return hash(self._id) - - def __str__(self): - return self._id - - def __eq__(self, other): - if not other: - return False - return self._id == other.id - - def __ne__(self, other): - if not other: - return False - - return self._id != other.id +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import uuid + + +class ActorId: + """ActorId that represents the identity of an actor. + + Example:: + + # create actorid with id 1 + actor_id = ActorId('1') + + # create random hex ActorId + actor_random_id = ActorId.create_random_id() + + """ + + def __init__(self, actor_id: str): + if not isinstance(actor_id, str): + raise TypeError(f'Argument actor_id must be of type str, not {type(actor_id)}') + self._id = actor_id + + @classmethod + def create_random_id(cls): + """Creates new object of :class:`ActorId` with the random id value.""" + random_id = uuid.uuid1().hex + return ActorId(random_id) + + @property + def id(self) -> str: + """Gets Actor ID string.""" + return self._id + + def __hash__(self): + return hash(self._id) + + def __str__(self): + return self._id + + def __eq__(self, other): + if not other: + return False + return self._id == other.id + + def __ne__(self, other): + if not other: + return False + + return self._id != other.id diff --git a/dapr/actor/runtime/__init__.py b/dapr/actor/runtime/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/actor/runtime/__init__.py +++ b/dapr/actor/runtime/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/actor/runtime/_call_type.py b/dapr/actor/runtime/_call_type.py index a61b3ab8f..4371ffe68 100644 --- a/dapr/actor/runtime/_call_type.py +++ b/dapr/actor/runtime/_call_type.py @@ -1,30 +1,30 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from enum import Enum - - -class ActorCallType(Enum): - """A enumeration that represents the call-type associated with the actor method. - :class:`ActorMethodContext` includes :class:`ActorCallType` passing to - :meth:`Actor._on_pre_actor_method` and :meth:`Actor._on_post_actor_method` - """ - - # Specifies that the method invoked is an actor interface method for a given client request. - actor_interface_method = 0 - # Specifies that the method invoked is a timer callback method. - timer_method = 1 - # Specifies that the method is when a reminder fires. - reminder_method = 2 +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from enum import Enum + + +class ActorCallType(Enum): + """A enumeration that represents the call-type associated with the actor method. + :class:`ActorMethodContext` includes :class:`ActorCallType` passing to + :meth:`Actor._on_pre_actor_method` and :meth:`Actor._on_post_actor_method` + """ + + # Specifies that the method invoked is an actor interface method for a given client request. + actor_interface_method = 0 + # Specifies that the method invoked is a timer callback method. + timer_method = 1 + # Specifies that the method is when a reminder fires. + reminder_method = 2 diff --git a/dapr/actor/runtime/_method_context.py b/dapr/actor/runtime/_method_context.py index 0f1504493..8e9687cb6 100644 --- a/dapr/actor/runtime/_method_context.py +++ b/dapr/actor/runtime/_method_context.py @@ -1,51 +1,51 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.actor.runtime._call_type import ActorCallType - - -class ActorMethodContext: - """A Actor method context that contains method information invoked - by :class:`ActorRuntime`. - """ - - def __init__(self, method_name: str, call_type: ActorCallType): - self._method_name = method_name - self._calltype = call_type - - @property - def method_name(self) -> str: - """Gets the method name.""" - return self._method_name - - @property - def call_type(self) -> ActorCallType: - """Gets :class:`ActorCallType` for this method.""" - return self._calltype - - @classmethod - def create_for_actor(cls, method_name: str): - """Creates :class:`ActorMethodContext` object for actor method.""" - return ActorMethodContext(method_name, ActorCallType.actor_interface_method) - - @classmethod - def create_for_timer(cls, method_name: str): - """Creates :class:`ActorMethodContext` object for timer_method.""" - return ActorMethodContext(method_name, ActorCallType.timer_method) - - @classmethod - def create_for_reminder(cls, method_name: str): - """Creates :class:`ActorMethodContext` object for reminder_method.""" - return ActorMethodContext(method_name, ActorCallType.reminder_method) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.actor.runtime._call_type import ActorCallType + + +class ActorMethodContext: + """A Actor method context that contains method information invoked + by :class:`ActorRuntime`. + """ + + def __init__(self, method_name: str, call_type: ActorCallType): + self._method_name = method_name + self._calltype = call_type + + @property + def method_name(self) -> str: + """Gets the method name.""" + return self._method_name + + @property + def call_type(self) -> ActorCallType: + """Gets :class:`ActorCallType` for this method.""" + return self._calltype + + @classmethod + def create_for_actor(cls, method_name: str): + """Creates :class:`ActorMethodContext` object for actor method.""" + return ActorMethodContext(method_name, ActorCallType.actor_interface_method) + + @classmethod + def create_for_timer(cls, method_name: str): + """Creates :class:`ActorMethodContext` object for timer_method.""" + return ActorMethodContext(method_name, ActorCallType.timer_method) + + @classmethod + def create_for_reminder(cls, method_name: str): + """Creates :class:`ActorMethodContext` object for reminder_method.""" + return ActorMethodContext(method_name, ActorCallType.reminder_method) diff --git a/dapr/actor/runtime/_reminder_data.py b/dapr/actor/runtime/_reminder_data.py index dec698ac4..2f890499f 100644 --- a/dapr/actor/runtime/_reminder_data.py +++ b/dapr/actor/runtime/_reminder_data.py @@ -1,118 +1,118 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import base64 - -from datetime import timedelta -from typing import Any, Dict, Optional - - -class ActorReminderData: - """The class that holds actor reminder data. - - Attrtibutes: - reminder_name: the name of Actor reminder. - state: the state data data passed to receive_reminder callback. - due_time: the amount of time to delay before invoking the reminder - for the first time. - period: the time interval between reminder invocations after - the first invocation. - """ - - def __init__( - self, - reminder_name: str, - state: Optional[bytes], - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta] = None, - ): - """Creates new :class:`ActorReminderData` instance. - - Args: - reminder_name (str): the name of Actor reminder. - state (bytes, str): the state data passed to - receive_reminder callback. - due_time (datetime.timedelta): the amount of time to delay before - invoking the reminder for the first time. - period (datetime.timedelta): the time interval between reminder - invocations after the first invocation. - ttl (Optional[datetime.timedelta]): the time interval before the reminder stops firing. - """ - self._reminder_name = reminder_name - self._due_time = due_time - self._period = period - self._ttl = ttl - - if not isinstance(state, bytes): - raise ValueError(f'only bytes are allowed for state: {type(state)}') - - self._state = state - - @property - def reminder_name(self) -> str: - """Gets the name of Actor Reminder.""" - return self._reminder_name - - @property - def state(self) -> bytes: - """Gets the state data of Actor Reminder.""" - return self._state - - @property - def due_time(self) -> timedelta: - """Gets due_time of Actor Reminder.""" - return self._due_time - - @property - def period(self) -> timedelta: - """Gets period of Actor Reminder.""" - return self._period - - @property - def ttl(self) -> Optional[timedelta]: - """Gets ttl of Actor Reminder.""" - return self._ttl - - def as_dict(self) -> Dict[str, Any]: - """Gets :class:`ActorReminderData` as a dict object.""" - encoded_state = None - if self._state is not None: - encoded_state = base64.b64encode(self._state) - reminderDict: Dict[str, Any] = { - 'reminderName': self._reminder_name, - 'dueTime': self._due_time, - 'period': self._period, - 'data': encoded_state.decode('utf-8'), - } - - if self._ttl is not None: - reminderDict.update({'ttl': self._ttl}) - - return reminderDict - - @classmethod - def from_dict(cls, reminder_name: str, obj: Dict[str, Any]) -> 'ActorReminderData': - """Creates :class:`ActorReminderData` object from dict object.""" - b64encoded_state = obj.get('data') - state_bytes = None - if b64encoded_state is not None and len(b64encoded_state) > 0: - state_bytes = base64.b64decode(b64encoded_state) - if 'ttl' in obj: - return ActorReminderData( - reminder_name, state_bytes, obj['dueTime'], obj['period'], obj['ttl'] - ) - else: - return ActorReminderData(reminder_name, state_bytes, obj['dueTime'], obj['period']) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import base64 + +from datetime import timedelta +from typing import Any, Dict, Optional + + +class ActorReminderData: + """The class that holds actor reminder data. + + Attrtibutes: + reminder_name: the name of Actor reminder. + state: the state data data passed to receive_reminder callback. + due_time: the amount of time to delay before invoking the reminder + for the first time. + period: the time interval between reminder invocations after + the first invocation. + """ + + def __init__( + self, + reminder_name: str, + state: Optional[bytes], + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ): + """Creates new :class:`ActorReminderData` instance. + + Args: + reminder_name (str): the name of Actor reminder. + state (bytes, str): the state data passed to + receive_reminder callback. + due_time (datetime.timedelta): the amount of time to delay before + invoking the reminder for the first time. + period (datetime.timedelta): the time interval between reminder + invocations after the first invocation. + ttl (Optional[datetime.timedelta]): the time interval before the reminder stops firing. + """ + self._reminder_name = reminder_name + self._due_time = due_time + self._period = period + self._ttl = ttl + + if not isinstance(state, bytes): + raise ValueError(f'only bytes are allowed for state: {type(state)}') + + self._state = state + + @property + def reminder_name(self) -> str: + """Gets the name of Actor Reminder.""" + return self._reminder_name + + @property + def state(self) -> bytes: + """Gets the state data of Actor Reminder.""" + return self._state + + @property + def due_time(self) -> timedelta: + """Gets due_time of Actor Reminder.""" + return self._due_time + + @property + def period(self) -> timedelta: + """Gets period of Actor Reminder.""" + return self._period + + @property + def ttl(self) -> Optional[timedelta]: + """Gets ttl of Actor Reminder.""" + return self._ttl + + def as_dict(self) -> Dict[str, Any]: + """Gets :class:`ActorReminderData` as a dict object.""" + encoded_state = None + if self._state is not None: + encoded_state = base64.b64encode(self._state) + reminderDict: Dict[str, Any] = { + 'reminderName': self._reminder_name, + 'dueTime': self._due_time, + 'period': self._period, + 'data': encoded_state.decode('utf-8'), + } + + if self._ttl is not None: + reminderDict.update({'ttl': self._ttl}) + + return reminderDict + + @classmethod + def from_dict(cls, reminder_name: str, obj: Dict[str, Any]) -> 'ActorReminderData': + """Creates :class:`ActorReminderData` object from dict object.""" + b64encoded_state = obj.get('data') + state_bytes = None + if b64encoded_state is not None and len(b64encoded_state) > 0: + state_bytes = base64.b64decode(b64encoded_state) + if 'ttl' in obj: + return ActorReminderData( + reminder_name, state_bytes, obj['dueTime'], obj['period'], obj['ttl'] + ) + else: + return ActorReminderData(reminder_name, state_bytes, obj['dueTime'], obj['period']) diff --git a/dapr/actor/runtime/_state_provider.py b/dapr/actor/runtime/_state_provider.py index 54f6b5837..6985d3539 100644 --- a/dapr/actor/runtime/_state_provider.py +++ b/dapr/actor/runtime/_state_provider.py @@ -1,110 +1,110 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import io - -from typing import Any, List, Type, Tuple -from dapr.actor.runtime.state_change import StateChangeKind, ActorStateChange -from dapr.clients.base import DaprActorClientBase -from dapr.serializers import Serializer, DefaultJSONSerializer - - -# Mapping StateChangeKind to Dapr State Operation -_MAP_CHANGE_KIND_TO_OPERATION = { - StateChangeKind.remove: b'delete', - StateChangeKind.add: b'upsert', - StateChangeKind.update: b'upsert', -} - - -class StateProvider: - """The adapter class for StateManager to use Dapr Actor Client. - - This provides the decorator methods to load and save states and check the existence of states. - """ - - def __init__( - self, - actor_client: DaprActorClientBase, - state_serializer: Serializer = DefaultJSONSerializer(), - ): - self._state_client = actor_client - self._state_serializer = state_serializer - - async def try_load_state( - self, actor_type: str, actor_id: str, state_name: str, state_type: Type[Any] = object - ) -> Tuple[bool, Any]: - raw_state_value = await self._state_client.get_state(actor_type, actor_id, state_name) - if (not raw_state_value) or len(raw_state_value) == 0: - return (False, None) - result = self._state_serializer.deserialize(raw_state_value, state_type) - return (True, result) - - async def contains_state(self, actor_type: str, actor_id: str, state_name: str) -> bool: - raw_state_value = await self._state_client.get_state(actor_type, actor_id, state_name) - return (raw_state_value is not None) and len(raw_state_value) > 0 - - async def save_state( - self, actor_type: str, actor_id: str, state_changes: List[ActorStateChange] - ) -> None: - """ - Transactional state update request body: - [ - { - "operation": "upsert", - "request": { - "key": "key1", - "value": "myData", - "metadata": { - "ttlInSeconds": "3600" - } - } - }, - { - "operation": "delete", - "request": { - "key": "key2" - } - } - ] - """ - - json_output = io.BytesIO() - json_output.write(b'[') - first_state = True - for state in state_changes: - if not first_state: - json_output.write(b',') - operation = _MAP_CHANGE_KIND_TO_OPERATION.get(state.change_kind) or b'' - json_output.write(b'{"operation":"') - json_output.write(operation) - json_output.write(b'","request":{"key":"') - json_output.write(state.state_name.encode('utf-8')) - json_output.write(b'"') - if state.value is not None: - serialized = self._state_serializer.serialize(state.value) - json_output.write(b',"value":') - json_output.write(serialized) - if state.ttl_in_seconds is not None and state.ttl_in_seconds >= 0: - serialized = self._state_serializer.serialize(state.ttl_in_seconds) - json_output.write(b',"metadata":{"ttlInSeconds":"') - json_output.write(serialized) - json_output.write(b'"}') - json_output.write(b'}}') - first_state = False - json_output.write(b']') - data = json_output.getvalue() - json_output.close() - await self._state_client.save_state_transactionally(actor_type, actor_id, data) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import io + +from typing import Any, List, Type, Tuple +from dapr.actor.runtime.state_change import StateChangeKind, ActorStateChange +from dapr.clients.base import DaprActorClientBase +from dapr.serializers import Serializer, DefaultJSONSerializer + + +# Mapping StateChangeKind to Dapr State Operation +_MAP_CHANGE_KIND_TO_OPERATION = { + StateChangeKind.remove: b'delete', + StateChangeKind.add: b'upsert', + StateChangeKind.update: b'upsert', +} + + +class StateProvider: + """The adapter class for StateManager to use Dapr Actor Client. + + This provides the decorator methods to load and save states and check the existence of states. + """ + + def __init__( + self, + actor_client: DaprActorClientBase, + state_serializer: Serializer = DefaultJSONSerializer(), + ): + self._state_client = actor_client + self._state_serializer = state_serializer + + async def try_load_state( + self, actor_type: str, actor_id: str, state_name: str, state_type: Type[Any] = object + ) -> Tuple[bool, Any]: + raw_state_value = await self._state_client.get_state(actor_type, actor_id, state_name) + if (not raw_state_value) or len(raw_state_value) == 0: + return (False, None) + result = self._state_serializer.deserialize(raw_state_value, state_type) + return (True, result) + + async def contains_state(self, actor_type: str, actor_id: str, state_name: str) -> bool: + raw_state_value = await self._state_client.get_state(actor_type, actor_id, state_name) + return (raw_state_value is not None) and len(raw_state_value) > 0 + + async def save_state( + self, actor_type: str, actor_id: str, state_changes: List[ActorStateChange] + ) -> None: + """ + Transactional state update request body: + [ + { + "operation": "upsert", + "request": { + "key": "key1", + "value": "myData", + "metadata": { + "ttlInSeconds": "3600" + } + } + }, + { + "operation": "delete", + "request": { + "key": "key2" + } + } + ] + """ + + json_output = io.BytesIO() + json_output.write(b'[') + first_state = True + for state in state_changes: + if not first_state: + json_output.write(b',') + operation = _MAP_CHANGE_KIND_TO_OPERATION.get(state.change_kind) or b'' + json_output.write(b'{"operation":"') + json_output.write(operation) + json_output.write(b'","request":{"key":"') + json_output.write(state.state_name.encode('utf-8')) + json_output.write(b'"') + if state.value is not None: + serialized = self._state_serializer.serialize(state.value) + json_output.write(b',"value":') + json_output.write(serialized) + if state.ttl_in_seconds is not None and state.ttl_in_seconds >= 0: + serialized = self._state_serializer.serialize(state.ttl_in_seconds) + json_output.write(b',"metadata":{"ttlInSeconds":"') + json_output.write(serialized) + json_output.write(b'"}') + json_output.write(b'}}') + first_state = False + json_output.write(b']') + data = json_output.getvalue() + json_output.close() + await self._state_client.save_state_transactionally(actor_type, actor_id, data) diff --git a/dapr/actor/runtime/_timer_data.py b/dapr/actor/runtime/_timer_data.py index 99a9c04ec..a968df7da 100644 --- a/dapr/actor/runtime/_timer_data.py +++ b/dapr/actor/runtime/_timer_data.py @@ -1,109 +1,109 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from datetime import timedelta -from typing import Any, Awaitable, Callable, Dict, Optional - -TIMER_CALLBACK = Callable[[Any], Awaitable[None]] - - -class ActorTimerData: - """The class that holds actor timer data. - - Attributes: - timer_name: the name of Actor timer. - state: the state object passed to timer callback. - due_time: the amount of time to delay before the first timer trigger. - period: the time interval between reminder invocations after - the first timer trigger. - callback: timer callback when timer is triggered. - ttl: the time interval before the timer stops firing. - """ - - def __init__( - self, - timer_name: str, - callback: TIMER_CALLBACK, - state: Any, - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta] = None, - ): - """Create new :class:`ActorTimerData` instance. - - Args: - timer_name (str): the name of Actor timer. - callback (TIMER_CALLBACK): timer callback when timer is triggered. - state (Any): the state object passed to timer callback. - due_time (datetime.timedelta): the amount of time to delay - before the first timer trigger. - period (datetime.timedelta): the time interval between reminder - invocations after the first timer trigger. - ttl (Optional[datetime.timedelta]): the time interval before the timer stops firing. - """ - self._timer_name = timer_name - self._callback = callback.__name__ - self._state = state - self._due_time = due_time - self._period = period - self._ttl = ttl - - @property - def timer_name(self) -> str: - """Gets the name of the actor timer.""" - return self._timer_name - - @property - def state(self) -> Any: - """Gets the state object of the actor timer.""" - return self._state - - @property - def due_time(self) -> timedelta: - """Gets due_timer of the actor timer.""" - return self._due_time - - @property - def period(self) -> timedelta: - """Gets period of the actor timer.""" - return self._period - - @property - def ttl(self) -> Optional[timedelta]: - """Gets ttl of the actor timer.""" - return self._ttl - - @property - def callback(self) -> str: - """Gets the callback of the actor timer.""" - return self._callback - - def as_dict(self) -> Dict[str, Any]: - """Returns :class:`ActorTimerData` object as a dict. - - This is used to serialize Actor Timer Data for Dapr runtime API. - """ - - timerDict: Dict[str, Any] = { - 'callback': self._callback, - 'data': self._state, - 'dueTime': self._due_time, - 'period': self._period, - } - - if self._ttl: - timerDict.update({'ttl': self._ttl}) - - return timerDict +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from datetime import timedelta +from typing import Any, Awaitable, Callable, Dict, Optional + +TIMER_CALLBACK = Callable[[Any], Awaitable[None]] + + +class ActorTimerData: + """The class that holds actor timer data. + + Attributes: + timer_name: the name of Actor timer. + state: the state object passed to timer callback. + due_time: the amount of time to delay before the first timer trigger. + period: the time interval between reminder invocations after + the first timer trigger. + callback: timer callback when timer is triggered. + ttl: the time interval before the timer stops firing. + """ + + def __init__( + self, + timer_name: str, + callback: TIMER_CALLBACK, + state: Any, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ): + """Create new :class:`ActorTimerData` instance. + + Args: + timer_name (str): the name of Actor timer. + callback (TIMER_CALLBACK): timer callback when timer is triggered. + state (Any): the state object passed to timer callback. + due_time (datetime.timedelta): the amount of time to delay + before the first timer trigger. + period (datetime.timedelta): the time interval between reminder + invocations after the first timer trigger. + ttl (Optional[datetime.timedelta]): the time interval before the timer stops firing. + """ + self._timer_name = timer_name + self._callback = callback.__name__ + self._state = state + self._due_time = due_time + self._period = period + self._ttl = ttl + + @property + def timer_name(self) -> str: + """Gets the name of the actor timer.""" + return self._timer_name + + @property + def state(self) -> Any: + """Gets the state object of the actor timer.""" + return self._state + + @property + def due_time(self) -> timedelta: + """Gets due_timer of the actor timer.""" + return self._due_time + + @property + def period(self) -> timedelta: + """Gets period of the actor timer.""" + return self._period + + @property + def ttl(self) -> Optional[timedelta]: + """Gets ttl of the actor timer.""" + return self._ttl + + @property + def callback(self) -> str: + """Gets the callback of the actor timer.""" + return self._callback + + def as_dict(self) -> Dict[str, Any]: + """Returns :class:`ActorTimerData` object as a dict. + + This is used to serialize Actor Timer Data for Dapr runtime API. + """ + + timerDict: Dict[str, Any] = { + 'callback': self._callback, + 'data': self._state, + 'dueTime': self._due_time, + 'period': self._period, + } + + if self._ttl: + timerDict.update({'ttl': self._ttl}) + + return timerDict diff --git a/dapr/actor/runtime/_type_information.py b/dapr/actor/runtime/_type_information.py index 72566eb17..bccf5be7f 100644 --- a/dapr/actor/runtime/_type_information.py +++ b/dapr/actor/runtime/_type_information.py @@ -1,78 +1,78 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.actor.runtime.remindable import Remindable -from dapr.actor.runtime._type_utils import is_dapr_actor, get_actor_interfaces - -from typing import List, Type, TYPE_CHECKING - -if TYPE_CHECKING: - from dapr.actor.actor_interface import ActorInterface # noqa: F401 - from dapr.actor.runtime.actor import Actor # noqa: F401 - - -class ActorTypeInformation: - """The object that contains information about the object - implementing an actor. - """ - - def __init__( - self, - name: str, - implementation_class: Type['Actor'], - actor_bases: List[Type['ActorInterface']], - ): - self._name = name - self._impl_type = implementation_class - self._actor_bases = actor_bases - - @property - def type_name(self) -> str: - """Returns Actor type name.""" - return self._name - - @property - def implementation_type(self) -> Type['Actor']: - """Returns Actor implementation type.""" - return self._impl_type - - @property - def actor_interfaces(self) -> List[Type['ActorInterface']]: - """Returns the list of :class:`ActorInterface` of this type.""" - return self._actor_bases - - def is_remindable(self) -> bool: - """Returns True if this actor implements :class:`Remindable`.""" - return Remindable in self._impl_type.__bases__ - - @classmethod - def create(cls, actor_class: Type['Actor']) -> 'ActorTypeInformation': - """Creates :class:`ActorTypeInformation` for actor_class. - - Args: - actor_class (:class:`Actor`): The actor implementation inherited from Actor. - - Returns: - :class:`ActorTypeInformation`: includes type name, actor_class type, - and actor base class deriving :class:`ActorInterface` - """ - if not is_dapr_actor(actor_class): - raise ValueError(f'{actor_class.__name__} is not actor') - - actors = get_actor_interfaces(actor_class) - if len(actors) == 0: - raise ValueError(f'{actor_class.__name__} does not implement ActorInterface') - - return ActorTypeInformation(actor_class.__name__, actor_class, actors) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.actor.runtime.remindable import Remindable +from dapr.actor.runtime._type_utils import is_dapr_actor, get_actor_interfaces + +from typing import List, Type, TYPE_CHECKING + +if TYPE_CHECKING: + from dapr.actor.actor_interface import ActorInterface # noqa: F401 + from dapr.actor.runtime.actor import Actor # noqa: F401 + + +class ActorTypeInformation: + """The object that contains information about the object + implementing an actor. + """ + + def __init__( + self, + name: str, + implementation_class: Type['Actor'], + actor_bases: List[Type['ActorInterface']], + ): + self._name = name + self._impl_type = implementation_class + self._actor_bases = actor_bases + + @property + def type_name(self) -> str: + """Returns Actor type name.""" + return self._name + + @property + def implementation_type(self) -> Type['Actor']: + """Returns Actor implementation type.""" + return self._impl_type + + @property + def actor_interfaces(self) -> List[Type['ActorInterface']]: + """Returns the list of :class:`ActorInterface` of this type.""" + return self._actor_bases + + def is_remindable(self) -> bool: + """Returns True if this actor implements :class:`Remindable`.""" + return Remindable in self._impl_type.__bases__ + + @classmethod + def create(cls, actor_class: Type['Actor']) -> 'ActorTypeInformation': + """Creates :class:`ActorTypeInformation` for actor_class. + + Args: + actor_class (:class:`Actor`): The actor implementation inherited from Actor. + + Returns: + :class:`ActorTypeInformation`: includes type name, actor_class type, + and actor base class deriving :class:`ActorInterface` + """ + if not is_dapr_actor(actor_class): + raise ValueError(f'{actor_class.__name__} is not actor') + + actors = get_actor_interfaces(actor_class) + if len(actors) == 0: + raise ValueError(f'{actor_class.__name__} does not implement ActorInterface') + + return ActorTypeInformation(actor_class.__name__, actor_class, actors) diff --git a/dapr/actor/runtime/_type_utils.py b/dapr/actor/runtime/_type_utils.py index 37f9c1734..57374737c 100644 --- a/dapr/actor/runtime/_type_utils.py +++ b/dapr/actor/runtime/_type_utils.py @@ -1,117 +1,117 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Any, Dict, List, Type - -from dapr.actor.actor_interface import ActorInterface -from dapr.actor.runtime.actor import Actor - - -def get_class_method_args(func: Any) -> List[str]: - args = func.__code__.co_varnames[: func.__code__.co_argcount] - - # Exclude self, cls arguments - if args[0] == 'self' or args[0] == 'cls': - args = args[1:] - return list(args) - - -def get_method_arg_types(func: Any) -> List[Type]: - annotations = getattr(func, '__annotations__') - args = get_class_method_args(func) - arg_types = [] - for arg_name in args: - arg_type = object if arg_name not in annotations else annotations[arg_name] - arg_types.append(arg_type) - return arg_types - - -def get_method_return_types(func: Any) -> Type: - annotations = getattr(func, '__annotations__') - if len(annotations) == 0 or not annotations['return']: - return object - return annotations['return'] - - -def get_dispatchable_attrs_from_interface( - actor_interface: Type[ActorInterface], dispatch_map: Dict[str, Any] -) -> None: - for attr, v in actor_interface.__dict__.items(): - if attr.startswith('_') or not callable(v): - continue - actor_method_name = getattr(v, '__actormethod__') if hasattr(v, '__actormethod__') else attr - - dispatch_map[actor_method_name] = { - 'actor_method': actor_method_name, - 'method_name': attr, - 'arg_names': get_class_method_args(v), - 'arg_types': get_method_arg_types(v), - 'return_types': get_method_return_types(v), - } - - -def get_dispatchable_attrs(actor_class: Type[Actor]) -> Dict[str, Any]: - """Gets the list of dispatchable attributes from actor. - - Args: - actor_class (type): The actor object which inherits :class:`ActorInterface` - - Returns: - Dict[str, Any]: The map from attribute to actor method. - - Raises: - ValueError: `actor_class` doesn't inherit :class:`ActorInterface`. - """ - # Find all user actor interfaces derived from ActorInterface - actor_interfaces = get_actor_interfaces(actor_class) - if len(actor_interfaces) == 0: - raise ValueError(f'{actor_class.__name__} has not inherited from ActorInterface') - - # Find all dispatchable attributes - dispatch_map: Dict[str, Any] = {} - for user_actor_cls in actor_interfaces: - get_dispatchable_attrs_from_interface(user_actor_cls, dispatch_map) - - return dispatch_map - - -def is_dapr_actor(cls: Type[Actor]) -> bool: - """Checks if class inherits :class:`Actor`. - - Args: - cls (type): The Actor implementation. - - Returns: - bool: True if cls inherits :class:`Actor`. Otherwise, False - """ - return issubclass(cls, Actor) - - -def get_actor_interfaces(cls: Type[Actor]) -> List[Type[ActorInterface]]: - """Gets the list of the base classes that inherits :class:`ActorInterface`. - - Args: - cls (:class:`Actor`): The Actor object that inherit :class:`Actor` and - :class:`ActorInterfaces`. - - Returns: - List: the list of classes that inherit :class:`ActorInterface`. - """ - actor_bases = [] - for cl in cls.mro(): - if ActorInterface in cl.__bases__: - actor_bases.append(cl) - - return actor_bases +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Any, Dict, List, Type + +from dapr.actor.actor_interface import ActorInterface +from dapr.actor.runtime.actor import Actor + + +def get_class_method_args(func: Any) -> List[str]: + args = func.__code__.co_varnames[: func.__code__.co_argcount] + + # Exclude self, cls arguments + if args[0] == 'self' or args[0] == 'cls': + args = args[1:] + return list(args) + + +def get_method_arg_types(func: Any) -> List[Type]: + annotations = getattr(func, '__annotations__') + args = get_class_method_args(func) + arg_types = [] + for arg_name in args: + arg_type = object if arg_name not in annotations else annotations[arg_name] + arg_types.append(arg_type) + return arg_types + + +def get_method_return_types(func: Any) -> Type: + annotations = getattr(func, '__annotations__') + if len(annotations) == 0 or not annotations['return']: + return object + return annotations['return'] + + +def get_dispatchable_attrs_from_interface( + actor_interface: Type[ActorInterface], dispatch_map: Dict[str, Any] +) -> None: + for attr, v in actor_interface.__dict__.items(): + if attr.startswith('_') or not callable(v): + continue + actor_method_name = getattr(v, '__actormethod__') if hasattr(v, '__actormethod__') else attr + + dispatch_map[actor_method_name] = { + 'actor_method': actor_method_name, + 'method_name': attr, + 'arg_names': get_class_method_args(v), + 'arg_types': get_method_arg_types(v), + 'return_types': get_method_return_types(v), + } + + +def get_dispatchable_attrs(actor_class: Type[Actor]) -> Dict[str, Any]: + """Gets the list of dispatchable attributes from actor. + + Args: + actor_class (type): The actor object which inherits :class:`ActorInterface` + + Returns: + Dict[str, Any]: The map from attribute to actor method. + + Raises: + ValueError: `actor_class` doesn't inherit :class:`ActorInterface`. + """ + # Find all user actor interfaces derived from ActorInterface + actor_interfaces = get_actor_interfaces(actor_class) + if len(actor_interfaces) == 0: + raise ValueError(f'{actor_class.__name__} has not inherited from ActorInterface') + + # Find all dispatchable attributes + dispatch_map: Dict[str, Any] = {} + for user_actor_cls in actor_interfaces: + get_dispatchable_attrs_from_interface(user_actor_cls, dispatch_map) + + return dispatch_map + + +def is_dapr_actor(cls: Type[Actor]) -> bool: + """Checks if class inherits :class:`Actor`. + + Args: + cls (type): The Actor implementation. + + Returns: + bool: True if cls inherits :class:`Actor`. Otherwise, False + """ + return issubclass(cls, Actor) + + +def get_actor_interfaces(cls: Type[Actor]) -> List[Type[ActorInterface]]: + """Gets the list of the base classes that inherits :class:`ActorInterface`. + + Args: + cls (:class:`Actor`): The Actor object that inherit :class:`Actor` and + :class:`ActorInterfaces`. + + Returns: + List: the list of classes that inherit :class:`ActorInterface`. + """ + actor_bases = [] + for cl in cls.mro(): + if ActorInterface in cl.__bases__: + actor_bases.append(cl) + + return actor_bases diff --git a/dapr/actor/runtime/actor.py b/dapr/actor/runtime/actor.py index 9f8d6ff8d..979ce9590 100644 --- a/dapr/actor/runtime/actor.py +++ b/dapr/actor/runtime/actor.py @@ -1,262 +1,262 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import uuid - -from datetime import timedelta -from typing import Any, Optional - -from dapr.actor.id import ActorId -from dapr.actor.runtime._method_context import ActorMethodContext -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.actor.runtime.state_manager import ActorStateManager -from dapr.actor.runtime._reminder_data import ActorReminderData -from dapr.actor.runtime._timer_data import TIMER_CALLBACK, ActorTimerData - - -class Actor: - """A base class of Actors that provides the common functionality of actors. - - Examples: - - class DaprActorInterface(ActorInterface): - @actor_method(name="method") - async def method_invoke(self, arg: str) -> str: - ... - - class DaprActor(Actor, DaprActorInterface): - def __init__(self, ctx, actor_id): - super(DaprActor, self).__init__(ctx, actor_id) - - async def method_invoke(self, arg: str) -> str: - return arg - - async def _on_activate(self): - pass - - async def _on_deactivate(self): - pass - - Attributes: - runtime_ctx: the :class:`ActorRuntimeContext` object served for - the actor implementation. - """ - - def __init__(self, ctx: ActorRuntimeContext, actor_id: ActorId): - self.id = actor_id - self._runtime_ctx = ctx - self._state_manager: ActorStateManager = ActorStateManager(self) - - @property - def runtime_ctx(self) -> ActorRuntimeContext: - return self._runtime_ctx - - def __get_new_timer_name(self): - return f'{self.id}_Timer_{uuid.uuid4()}' - - async def register_timer( - self, - name: Optional[str], - callback: TIMER_CALLBACK, - state: Any, - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta] = None, - ) -> None: - """Registers actor timer. - - All timers are stopped when the actor is deactivated as part of garbage collection. - - Args: - name (str): the name of the timer to register. - callback (Callable): An awaitable callable which will be called when the timer fires. - state (Any): An object which will pass to the callback method, or None. - due_time (datetime.timedelta): the amount of time to delay before the awaitable - callback is first invoked. - period (datetime.timedelta): the time interval between invocations - of the awaitable callback. - ttl (Optional[datetime.timedelta]): the time interval before the timer stops firing - """ - name = name or self.__get_new_timer_name() - timer = ActorTimerData(name, callback, state, due_time, period, ttl) - - req_body = self._runtime_ctx.message_serializer.serialize(timer.as_dict()) - await self._runtime_ctx.dapr_client.register_timer( - self._runtime_ctx.actor_type_info.type_name, self.id.id, name, req_body - ) - - async def unregister_timer(self, name: str) -> None: - """Unregisters actor timer. - - Args: - name (str): the name of the timer to unregister. - """ - await self._runtime_ctx.dapr_client.unregister_timer( - self._runtime_ctx.actor_type_info.type_name, self.id.id, name - ) - - async def register_reminder( - self, - name: str, - state: bytes, - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta] = None, - ) -> None: - """Registers actor reminder. - - Reminders are a mechanism to trigger persistent callbacks on an actor at specified times. - Their functionality is similar to timers. But unlike timers, reminders are triggered under - all circumstances until the actor explicitly unregisters them or the actor is explicitly - deleted. Specifically, reminders are triggered across actor deactivations and failovers - because the Actors runtime persists information about the actor's reminders using actor - state provider. Also existing reminders can be updated by calling this registration method - again using the same reminderName. - - Args: - name (str): the name of the reminder to register. the name must be unique per actor. - state (bytes): the user state passed to the reminder invocation. - due_time (datetime.timedelta): the amount of time to delay before invoking the reminder - for the first time. - period (datetime.timedelta): the time interval between reminder invocations after - the first invocation. - ttl (datetime.timedelta): the time interval before the reminder stops firing - """ - reminder = ActorReminderData(name, state, due_time, period, ttl) - req_body = self._runtime_ctx.message_serializer.serialize(reminder.as_dict()) - await self._runtime_ctx.dapr_client.register_reminder( - self._runtime_ctx.actor_type_info.type_name, self.id.id, name, req_body - ) - - async def unregister_reminder(self, name: str) -> None: - """Unregisters actor reminder. - - Args: - name (str): the name of the reminder to unregister. - """ - await self._runtime_ctx.dapr_client.unregister_reminder( - self._runtime_ctx.actor_type_info.type_name, self.id.id, name - ) - - async def _on_activate_internal(self) -> None: - """Clears all state cache, calls the overridden :meth:`_on_activate`, - and then save the states. - - This internal callback is called when actor is activated. - """ - await self._reset_state_internal() - await self._on_activate() - await self._save_state_internal() - - async def _on_deactivate_internal(self) -> None: - """Clears all state cache, calls the overridden :meth:`_on_deactivate`. - - This internal callback is called when actor is deactivated. - """ - await self._reset_state_internal() - await self._on_deactivate() - - async def _on_pre_actor_method_internal(self, method_context: ActorMethodContext) -> None: - """Calls the overridden :meth:`_on_pre_actor_method`. - - This internal callback is called before actor method is invoked. - """ - await self._on_pre_actor_method(method_context) - - async def _on_post_actor_method_internal(self, method_context: ActorMethodContext) -> None: - """Calls the overridden :meth:`_on_post_actor_method` and - saves the states. - - This internal callback is called after actor method is invoked. - """ - await self._on_post_actor_method(method_context) - await self._save_state_internal() - - async def _on_invoke_failed_internal(self, exception=None): - """Clears states in the cache when actor method invocation is failed.""" - await self._reset_state_internal() - - async def _reset_state_internal(self) -> None: - """Clears actor state cache. - - This will be called when actor method invocation is failed and actor is activated. - """ - await self._state_manager.clear_cache() - - async def _save_state_internal(self): - """Saves all the state changes (add/update/remove) that were made since last call - to the actor state provider associated with the actor. - """ - await self._state_manager.save_state() - - async def _fire_timer_internal(self, timer_callback: str, timer_data: Any) -> None: - """Calls timer callback. - - Args: - timer_callback (str): the timer's callback method. - timer_data (Any): the timer's callback method data. - """ - return await getattr(self, timer_callback)(timer_data) - - async def _on_activate(self) -> None: - """Override this method to initialize the members. - - This method is called right after the actor is activated and before - any method call or reminders are dispatched on it. - """ - ... - - async def _on_deactivate(self) -> None: - """Override this method to release any resources. - - This method is called when actor is deactivated (garbage collected - by Actor Runtime). Actor operations like state changes should not - be called from this method. - """ - ... - - async def _on_pre_actor_method(self, method_context: ActorMethodContext) -> None: - """Override this method for performing any action prior to - an actor method is invoked. - - This method is invoked by actor runtime just before invoking - an actor method. - - This method is invoked by actor runtime prior to: - - Invoking an actor interface method when a client request comes. - - Invoking a method when a reminder fires. - - Invoking a timer callback when timer fires. - - Args: - method_context (:class:`ActorMethodContext`): The method information. - """ - ... - - async def _on_post_actor_method(self, method_context: ActorMethodContext) -> None: - """Override this method for performing any action after - an actor method has finished execution. - - This method is invoked by actor runtime an actor method has finished - execution. - - This method is invoked by actor runtime after: - - Invoking an actor interface method when a client request comes. - - Invoking a method when a reminder fires. - - Invoking a timer callback when timer fires. - - Args: - method_context (:class:`ActorMethodContext`): The method information. - """ - ... +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import uuid + +from datetime import timedelta +from typing import Any, Optional + +from dapr.actor.id import ActorId +from dapr.actor.runtime._method_context import ActorMethodContext +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.actor.runtime.state_manager import ActorStateManager +from dapr.actor.runtime._reminder_data import ActorReminderData +from dapr.actor.runtime._timer_data import TIMER_CALLBACK, ActorTimerData + + +class Actor: + """A base class of Actors that provides the common functionality of actors. + + Examples: + + class DaprActorInterface(ActorInterface): + @actor_method(name="method") + async def method_invoke(self, arg: str) -> str: + ... + + class DaprActor(Actor, DaprActorInterface): + def __init__(self, ctx, actor_id): + super(DaprActor, self).__init__(ctx, actor_id) + + async def method_invoke(self, arg: str) -> str: + return arg + + async def _on_activate(self): + pass + + async def _on_deactivate(self): + pass + + Attributes: + runtime_ctx: the :class:`ActorRuntimeContext` object served for + the actor implementation. + """ + + def __init__(self, ctx: ActorRuntimeContext, actor_id: ActorId): + self.id = actor_id + self._runtime_ctx = ctx + self._state_manager: ActorStateManager = ActorStateManager(self) + + @property + def runtime_ctx(self) -> ActorRuntimeContext: + return self._runtime_ctx + + def __get_new_timer_name(self): + return f'{self.id}_Timer_{uuid.uuid4()}' + + async def register_timer( + self, + name: Optional[str], + callback: TIMER_CALLBACK, + state: Any, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ) -> None: + """Registers actor timer. + + All timers are stopped when the actor is deactivated as part of garbage collection. + + Args: + name (str): the name of the timer to register. + callback (Callable): An awaitable callable which will be called when the timer fires. + state (Any): An object which will pass to the callback method, or None. + due_time (datetime.timedelta): the amount of time to delay before the awaitable + callback is first invoked. + period (datetime.timedelta): the time interval between invocations + of the awaitable callback. + ttl (Optional[datetime.timedelta]): the time interval before the timer stops firing + """ + name = name or self.__get_new_timer_name() + timer = ActorTimerData(name, callback, state, due_time, period, ttl) + + req_body = self._runtime_ctx.message_serializer.serialize(timer.as_dict()) + await self._runtime_ctx.dapr_client.register_timer( + self._runtime_ctx.actor_type_info.type_name, self.id.id, name, req_body + ) + + async def unregister_timer(self, name: str) -> None: + """Unregisters actor timer. + + Args: + name (str): the name of the timer to unregister. + """ + await self._runtime_ctx.dapr_client.unregister_timer( + self._runtime_ctx.actor_type_info.type_name, self.id.id, name + ) + + async def register_reminder( + self, + name: str, + state: bytes, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ) -> None: + """Registers actor reminder. + + Reminders are a mechanism to trigger persistent callbacks on an actor at specified times. + Their functionality is similar to timers. But unlike timers, reminders are triggered under + all circumstances until the actor explicitly unregisters them or the actor is explicitly + deleted. Specifically, reminders are triggered across actor deactivations and failovers + because the Actors runtime persists information about the actor's reminders using actor + state provider. Also existing reminders can be updated by calling this registration method + again using the same reminderName. + + Args: + name (str): the name of the reminder to register. the name must be unique per actor. + state (bytes): the user state passed to the reminder invocation. + due_time (datetime.timedelta): the amount of time to delay before invoking the reminder + for the first time. + period (datetime.timedelta): the time interval between reminder invocations after + the first invocation. + ttl (datetime.timedelta): the time interval before the reminder stops firing + """ + reminder = ActorReminderData(name, state, due_time, period, ttl) + req_body = self._runtime_ctx.message_serializer.serialize(reminder.as_dict()) + await self._runtime_ctx.dapr_client.register_reminder( + self._runtime_ctx.actor_type_info.type_name, self.id.id, name, req_body + ) + + async def unregister_reminder(self, name: str) -> None: + """Unregisters actor reminder. + + Args: + name (str): the name of the reminder to unregister. + """ + await self._runtime_ctx.dapr_client.unregister_reminder( + self._runtime_ctx.actor_type_info.type_name, self.id.id, name + ) + + async def _on_activate_internal(self) -> None: + """Clears all state cache, calls the overridden :meth:`_on_activate`, + and then save the states. + + This internal callback is called when actor is activated. + """ + await self._reset_state_internal() + await self._on_activate() + await self._save_state_internal() + + async def _on_deactivate_internal(self) -> None: + """Clears all state cache, calls the overridden :meth:`_on_deactivate`. + + This internal callback is called when actor is deactivated. + """ + await self._reset_state_internal() + await self._on_deactivate() + + async def _on_pre_actor_method_internal(self, method_context: ActorMethodContext) -> None: + """Calls the overridden :meth:`_on_pre_actor_method`. + + This internal callback is called before actor method is invoked. + """ + await self._on_pre_actor_method(method_context) + + async def _on_post_actor_method_internal(self, method_context: ActorMethodContext) -> None: + """Calls the overridden :meth:`_on_post_actor_method` and + saves the states. + + This internal callback is called after actor method is invoked. + """ + await self._on_post_actor_method(method_context) + await self._save_state_internal() + + async def _on_invoke_failed_internal(self, exception=None): + """Clears states in the cache when actor method invocation is failed.""" + await self._reset_state_internal() + + async def _reset_state_internal(self) -> None: + """Clears actor state cache. + + This will be called when actor method invocation is failed and actor is activated. + """ + await self._state_manager.clear_cache() + + async def _save_state_internal(self): + """Saves all the state changes (add/update/remove) that were made since last call + to the actor state provider associated with the actor. + """ + await self._state_manager.save_state() + + async def _fire_timer_internal(self, timer_callback: str, timer_data: Any) -> None: + """Calls timer callback. + + Args: + timer_callback (str): the timer's callback method. + timer_data (Any): the timer's callback method data. + """ + return await getattr(self, timer_callback)(timer_data) + + async def _on_activate(self) -> None: + """Override this method to initialize the members. + + This method is called right after the actor is activated and before + any method call or reminders are dispatched on it. + """ + ... + + async def _on_deactivate(self) -> None: + """Override this method to release any resources. + + This method is called when actor is deactivated (garbage collected + by Actor Runtime). Actor operations like state changes should not + be called from this method. + """ + ... + + async def _on_pre_actor_method(self, method_context: ActorMethodContext) -> None: + """Override this method for performing any action prior to + an actor method is invoked. + + This method is invoked by actor runtime just before invoking + an actor method. + + This method is invoked by actor runtime prior to: + - Invoking an actor interface method when a client request comes. + - Invoking a method when a reminder fires. + - Invoking a timer callback when timer fires. + + Args: + method_context (:class:`ActorMethodContext`): The method information. + """ + ... + + async def _on_post_actor_method(self, method_context: ActorMethodContext) -> None: + """Override this method for performing any action after + an actor method has finished execution. + + This method is invoked by actor runtime an actor method has finished + execution. + + This method is invoked by actor runtime after: + - Invoking an actor interface method when a client request comes. + - Invoking a method when a reminder fires. + - Invoking a timer callback when timer fires. + + Args: + method_context (:class:`ActorMethodContext`): The method information. + """ + ... diff --git a/dapr/actor/runtime/config.py b/dapr/actor/runtime/config.py index 8d42ebda4..eebcef25c 100644 --- a/dapr/actor/runtime/config.py +++ b/dapr/actor/runtime/config.py @@ -1,195 +1,195 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from datetime import timedelta -from typing import Any, Dict, List, Optional, Set - - -class ActorReentrancyConfig: - def __init__(self, enabled: bool = False, maxStackDepth: int = 32): - """Inits :class:`ActorReentrancyConfig` to optionally configure actor - reentrancy. - - Args: - enabled (bool): Set to enable or disable reentrancy. - maxStackDepth (int): Limit for the number of concurrent reentrant requests - to an actor, further requests are denied. - """ - - self._enabled = enabled - self._maxStackDepth = maxStackDepth - - def as_dict(self) -> Dict[str, Any]: - """Returns ActorReentrancyConfig as a dict.""" - return { - 'enabled': self._enabled, - 'maxStackDepth': self._maxStackDepth, - } - - -class ActorTypeConfig: - """Per Actor Type Configuration that configures Actor behavior for a specific Actor Type in - Dapr Runtime. - """ - - def __init__( - self, - actor_type: str, - actor_idle_timeout: Optional[timedelta] = None, - actor_scan_interval: Optional[timedelta] = None, - drain_ongoing_call_timeout: Optional[timedelta] = None, - drain_rebalanced_actors: Optional[bool] = None, - reentrancy: Optional[ActorReentrancyConfig] = None, - reminders_storage_partitions: Optional[int] = None, - ): - """Inits :class:`ActorTypeConfig` to configure the behavior of a specific actor type - when dapr runtime starts. - - Args: - actor_type (str): Actor type. - actor_idle_timeout (datetime.timedelta): The timeout before deactivating an idle actor. - actor_scan_interval (datetime.timedelta): The duration which specifies how often to scan - for actors to deactivate idle actors. Actors that have been idle longer than - actor_idle_timeout will be deactivated. - drain_ongoing_call_timeout (datetime.timedelta): The duration which specifies the - timeout for the current active actor method to finish before actor deactivation. - If there is no current actor method call, this is ignored. - drain_rebalanced_actors (bool): If true, Dapr will wait for drain_ongoing_call_timeout - to allow a current actor call to complete before trying to deactivate an actor. - reentrancy (ActorReentrancyConfig): Configure the reentrancy behavior for an actor. - If not provided, reentrancy is diabled. - reminders_storage_partitions (int): The number of partitions to use for reminders - storage. - """ - self._actor_type = actor_type - self._actor_idle_timeout = actor_idle_timeout - self._actor_scan_interval = actor_scan_interval - self._drain_ongoing_call_timeout = drain_ongoing_call_timeout - self._drain_rebalanced_actors = drain_rebalanced_actors - self._reentrancy = reentrancy - self._reminders_storage_partitions = reminders_storage_partitions - - def as_dict(self) -> Dict[str, Any]: - """Returns ActorTypeConfig as a dict.""" - - configDict: Dict[str, Any] = dict() - configDict['entities'] = [self._actor_type] - - if self._actor_idle_timeout is not None: - configDict.update({'actorIdleTimeout': self._actor_idle_timeout}) - - if self._actor_scan_interval is not None: - configDict.update({'actorScanInterval': self._actor_scan_interval}) - - if self._drain_ongoing_call_timeout is not None: - configDict.update({'drainOngoingCallTimeout': self._drain_ongoing_call_timeout}) - - if self._drain_rebalanced_actors is not None: - configDict.update({'drainRebalancedActors': self._drain_rebalanced_actors}) - - if self._reentrancy: - configDict.update({'reentrancy': self._reentrancy.as_dict()}) - - if self._reminders_storage_partitions: - configDict.update({'remindersStoragePartitions': self._reminders_storage_partitions}) - - return configDict - - -class ActorRuntimeConfig: - """Actor runtime configuration that configures Actor behavior in - Dapr Runtime. - """ - - def __init__( - self, - actor_idle_timeout: Optional[timedelta] = timedelta(hours=1), - actor_scan_interval: Optional[timedelta] = timedelta(seconds=30), - drain_ongoing_call_timeout: Optional[timedelta] = timedelta(minutes=1), - drain_rebalanced_actors: Optional[bool] = True, - reentrancy: Optional[ActorReentrancyConfig] = None, - reminders_storage_partitions: Optional[int] = None, - actor_type_configs: List[ActorTypeConfig] = [], - ): - """Inits :class:`ActorRuntimeConfig` to configure actors when dapr runtime starts. - - Args: - actor_idle_timeout (datetime.timedelta): The timeout before deactivating an idle actor. - actor_scan_interval (datetime.timedelta): The duration which specifies how often to scan - for actors to deactivate idle actors. Actors that have been idle longer than - actor_idle_timeout will be deactivated. - drain_ongoing_call_timeout (datetime.timedelta): The duration which specifies the - timeout for the current active actor method to finish before actor deactivation. - If there is no current actor method call, this is ignored. - drain_rebalanced_actors (bool): If true, Dapr will wait for drain_ongoing_call_timeout - to allow a current actor call to complete before trying to deactivate an actor. - reentrancy (ActorReentrancyConfig): Configure the reentrancy behavior for an actor. - If not provided, reentrancy is diabled. - reminders_storage_partitions (int): The number of partitions to use for reminders - storage. - actor_type_configs (List[ActorTypeConfig]): Configure the behavior of specific - actor types. - """ - self._entities: Set[str] = set() - self._actor_idle_timeout = actor_idle_timeout - self._actor_scan_interval = actor_scan_interval - self._drain_ongoing_call_timeout = drain_ongoing_call_timeout - self._drain_rebalanced_actors = drain_rebalanced_actors - self._reentrancy = reentrancy - self._reminders_storage_partitions = reminders_storage_partitions - self._entitiesConfig: List[ActorTypeConfig] = actor_type_configs - - def update_entities(self, entities: List[str]) -> None: - """Updates actor types in entities property. - - Args: - entities (List[str]): the list of actor type names - """ - self._entities.update(entities) - - def update_actor_type_configs(self, actor_type_configs: List[ActorTypeConfig]) -> None: - """Updates actor type configs. - - Args: - actor_type_configs (List[ActorTypeConfig]): the list of actor type configs - """ - self._entitiesConfig = actor_type_configs or [] - - def as_dict(self) -> Dict[str, Any]: - """Returns ActorRuntimeConfig as a dict.""" - - entities: Set[str] = self._entities - - configDict: Dict[str, Any] = { - 'actorIdleTimeout': self._actor_idle_timeout, - 'actorScanInterval': self._actor_scan_interval, - 'drainOngoingCallTimeout': self._drain_ongoing_call_timeout, - 'drainRebalancedActors': self._drain_rebalanced_actors, - } - - if self._reentrancy: - configDict.update({'reentrancy': self._reentrancy.as_dict()}) - - if self._reminders_storage_partitions: - configDict.update({'remindersStoragePartitions': self._reminders_storage_partitions}) - - configDict['entitiesConfig'] = [] - for entityConfig in self._entitiesConfig: - configDict['entitiesConfig'].append(entityConfig.as_dict()) - entities.add(entityConfig._actor_type) - - configDict['entities'] = list(entities) - - return configDict +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from datetime import timedelta +from typing import Any, Dict, List, Optional, Set + + +class ActorReentrancyConfig: + def __init__(self, enabled: bool = False, maxStackDepth: int = 32): + """Inits :class:`ActorReentrancyConfig` to optionally configure actor + reentrancy. + + Args: + enabled (bool): Set to enable or disable reentrancy. + maxStackDepth (int): Limit for the number of concurrent reentrant requests + to an actor, further requests are denied. + """ + + self._enabled = enabled + self._maxStackDepth = maxStackDepth + + def as_dict(self) -> Dict[str, Any]: + """Returns ActorReentrancyConfig as a dict.""" + return { + 'enabled': self._enabled, + 'maxStackDepth': self._maxStackDepth, + } + + +class ActorTypeConfig: + """Per Actor Type Configuration that configures Actor behavior for a specific Actor Type in + Dapr Runtime. + """ + + def __init__( + self, + actor_type: str, + actor_idle_timeout: Optional[timedelta] = None, + actor_scan_interval: Optional[timedelta] = None, + drain_ongoing_call_timeout: Optional[timedelta] = None, + drain_rebalanced_actors: Optional[bool] = None, + reentrancy: Optional[ActorReentrancyConfig] = None, + reminders_storage_partitions: Optional[int] = None, + ): + """Inits :class:`ActorTypeConfig` to configure the behavior of a specific actor type + when dapr runtime starts. + + Args: + actor_type (str): Actor type. + actor_idle_timeout (datetime.timedelta): The timeout before deactivating an idle actor. + actor_scan_interval (datetime.timedelta): The duration which specifies how often to scan + for actors to deactivate idle actors. Actors that have been idle longer than + actor_idle_timeout will be deactivated. + drain_ongoing_call_timeout (datetime.timedelta): The duration which specifies the + timeout for the current active actor method to finish before actor deactivation. + If there is no current actor method call, this is ignored. + drain_rebalanced_actors (bool): If true, Dapr will wait for drain_ongoing_call_timeout + to allow a current actor call to complete before trying to deactivate an actor. + reentrancy (ActorReentrancyConfig): Configure the reentrancy behavior for an actor. + If not provided, reentrancy is diabled. + reminders_storage_partitions (int): The number of partitions to use for reminders + storage. + """ + self._actor_type = actor_type + self._actor_idle_timeout = actor_idle_timeout + self._actor_scan_interval = actor_scan_interval + self._drain_ongoing_call_timeout = drain_ongoing_call_timeout + self._drain_rebalanced_actors = drain_rebalanced_actors + self._reentrancy = reentrancy + self._reminders_storage_partitions = reminders_storage_partitions + + def as_dict(self) -> Dict[str, Any]: + """Returns ActorTypeConfig as a dict.""" + + configDict: Dict[str, Any] = dict() + configDict['entities'] = [self._actor_type] + + if self._actor_idle_timeout is not None: + configDict.update({'actorIdleTimeout': self._actor_idle_timeout}) + + if self._actor_scan_interval is not None: + configDict.update({'actorScanInterval': self._actor_scan_interval}) + + if self._drain_ongoing_call_timeout is not None: + configDict.update({'drainOngoingCallTimeout': self._drain_ongoing_call_timeout}) + + if self._drain_rebalanced_actors is not None: + configDict.update({'drainRebalancedActors': self._drain_rebalanced_actors}) + + if self._reentrancy: + configDict.update({'reentrancy': self._reentrancy.as_dict()}) + + if self._reminders_storage_partitions: + configDict.update({'remindersStoragePartitions': self._reminders_storage_partitions}) + + return configDict + + +class ActorRuntimeConfig: + """Actor runtime configuration that configures Actor behavior in + Dapr Runtime. + """ + + def __init__( + self, + actor_idle_timeout: Optional[timedelta] = timedelta(hours=1), + actor_scan_interval: Optional[timedelta] = timedelta(seconds=30), + drain_ongoing_call_timeout: Optional[timedelta] = timedelta(minutes=1), + drain_rebalanced_actors: Optional[bool] = True, + reentrancy: Optional[ActorReentrancyConfig] = None, + reminders_storage_partitions: Optional[int] = None, + actor_type_configs: List[ActorTypeConfig] = [], + ): + """Inits :class:`ActorRuntimeConfig` to configure actors when dapr runtime starts. + + Args: + actor_idle_timeout (datetime.timedelta): The timeout before deactivating an idle actor. + actor_scan_interval (datetime.timedelta): The duration which specifies how often to scan + for actors to deactivate idle actors. Actors that have been idle longer than + actor_idle_timeout will be deactivated. + drain_ongoing_call_timeout (datetime.timedelta): The duration which specifies the + timeout for the current active actor method to finish before actor deactivation. + If there is no current actor method call, this is ignored. + drain_rebalanced_actors (bool): If true, Dapr will wait for drain_ongoing_call_timeout + to allow a current actor call to complete before trying to deactivate an actor. + reentrancy (ActorReentrancyConfig): Configure the reentrancy behavior for an actor. + If not provided, reentrancy is diabled. + reminders_storage_partitions (int): The number of partitions to use for reminders + storage. + actor_type_configs (List[ActorTypeConfig]): Configure the behavior of specific + actor types. + """ + self._entities: Set[str] = set() + self._actor_idle_timeout = actor_idle_timeout + self._actor_scan_interval = actor_scan_interval + self._drain_ongoing_call_timeout = drain_ongoing_call_timeout + self._drain_rebalanced_actors = drain_rebalanced_actors + self._reentrancy = reentrancy + self._reminders_storage_partitions = reminders_storage_partitions + self._entitiesConfig: List[ActorTypeConfig] = actor_type_configs + + def update_entities(self, entities: List[str]) -> None: + """Updates actor types in entities property. + + Args: + entities (List[str]): the list of actor type names + """ + self._entities.update(entities) + + def update_actor_type_configs(self, actor_type_configs: List[ActorTypeConfig]) -> None: + """Updates actor type configs. + + Args: + actor_type_configs (List[ActorTypeConfig]): the list of actor type configs + """ + self._entitiesConfig = actor_type_configs or [] + + def as_dict(self) -> Dict[str, Any]: + """Returns ActorRuntimeConfig as a dict.""" + + entities: Set[str] = self._entities + + configDict: Dict[str, Any] = { + 'actorIdleTimeout': self._actor_idle_timeout, + 'actorScanInterval': self._actor_scan_interval, + 'drainOngoingCallTimeout': self._drain_ongoing_call_timeout, + 'drainRebalancedActors': self._drain_rebalanced_actors, + } + + if self._reentrancy: + configDict.update({'reentrancy': self._reentrancy.as_dict()}) + + if self._reminders_storage_partitions: + configDict.update({'remindersStoragePartitions': self._reminders_storage_partitions}) + + configDict['entitiesConfig'] = [] + for entityConfig in self._entitiesConfig: + configDict['entitiesConfig'].append(entityConfig.as_dict()) + entities.add(entityConfig._actor_type) + + configDict['entities'] = list(entities) + + return configDict diff --git a/dapr/actor/runtime/context.py b/dapr/actor/runtime/context.py index ec66ba366..883ca7dd2 100644 --- a/dapr/actor/runtime/context.py +++ b/dapr/actor/runtime/context.py @@ -1,120 +1,120 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.actor.id import ActorId -from dapr.actor.runtime._state_provider import StateProvider -from dapr.clients.base import DaprActorClientBase -from dapr.serializers import Serializer - -from typing import Callable, Optional, TYPE_CHECKING - -if TYPE_CHECKING: - from dapr.actor.runtime.actor import Actor - from dapr.actor.runtime._type_information import ActorTypeInformation - - -class ActorRuntimeContext: - """A context of ActorRuntime. - - This defines the context of actor runtime, which carries the type information of Actor, - the serializers for invocation and state, and the actor clients for Dapr runtime. - - Attributes: - actor_type_info(:class:`ActorTypeInformation`): the type information to initiate - Actor object. - message_serializer(:class:`Serializer`): the serializer for actor invocation request - and response body. - state_serializer(:class:`Serializer`): the seralizer for state value. - state_provider(:class:`StateProvider`): the provider which is the adapter used - for state manager. - dapr_client(:class:`DaprActorClientBase`): the actor client used for dapr runtime. - """ - - def __init__( - self, - actor_type_info: 'ActorTypeInformation', - message_serializer: Serializer, - state_serializer: Serializer, - actor_client: DaprActorClientBase, - actor_factory: Optional[Callable[['ActorRuntimeContext', ActorId], 'Actor']] = None, - ): - """Creates :class:`ActorRuntimeContext` object. - - Args: - actor_type_info(:class:`ActorTypeInformation`): the type information to initiate - Actor object. - message_serializer(:class:`Serializer`): the serializer for actor invocation - request and response body. - state_serializer(:class:`Serializer`): the seralizer for state value. - actor_client(:class:`DaprActorClientBase`): the actor client used for dapr runtime. - actor_factory(Callable, optional): the factory to create Actor object by - actor_type_info. - """ - self._actor_type_info = actor_type_info - self._actor_factory = actor_factory or self._default_actor_factory - self._message_serializer = message_serializer - self._state_serializer = state_serializer - - # Create State management provider for actor. - self._dapr_client = actor_client - self._provider: StateProvider = StateProvider(self._dapr_client, state_serializer) - - @property - def actor_type_info(self) -> 'ActorTypeInformation': - """Return :class:`ActorTypeInformation` in this context.""" - return self._actor_type_info - - @property - def message_serializer(self) -> Serializer: - """Return message serializer which is used for Actor invocation.""" - return self._message_serializer - - @property - def state_serializer(self) -> Serializer: - """Return state serializer which is used for State value.""" - return self._state_serializer - - @property - def state_provider(self) -> StateProvider: - """Return state provider to manage actor states.""" - return self._provider - - @property - def dapr_client(self) -> DaprActorClientBase: - """Return dapr client.""" - return self._dapr_client - - def create_actor(self, actor_id: ActorId) -> 'Actor': - """Create the object of :class:`Actor` for :class:`ActorId`. - - Args: - actor_id (:class:`ActorId`): ActorId object representing :class:`ActorId` - - Returns: - :class:`Actor`: new actor. - """ - return self._actor_factory(self, actor_id) - - def _default_actor_factory(self, ctx: 'ActorRuntimeContext', actor_id: ActorId) -> 'Actor': - """Creates new Actor with actor_id. - - Args: - ctx (:class:`ActorRuntimeContext`): the actor runtime context for new actor. - actor_id (:class:`ActorId`): the actor id object. - - Returns: - :class:`Actor`: new actor. - """ - return self.actor_type_info.implementation_type(ctx, actor_id) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.actor.id import ActorId +from dapr.actor.runtime._state_provider import StateProvider +from dapr.clients.base import DaprActorClientBase +from dapr.serializers import Serializer + +from typing import Callable, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from dapr.actor.runtime.actor import Actor + from dapr.actor.runtime._type_information import ActorTypeInformation + + +class ActorRuntimeContext: + """A context of ActorRuntime. + + This defines the context of actor runtime, which carries the type information of Actor, + the serializers for invocation and state, and the actor clients for Dapr runtime. + + Attributes: + actor_type_info(:class:`ActorTypeInformation`): the type information to initiate + Actor object. + message_serializer(:class:`Serializer`): the serializer for actor invocation request + and response body. + state_serializer(:class:`Serializer`): the seralizer for state value. + state_provider(:class:`StateProvider`): the provider which is the adapter used + for state manager. + dapr_client(:class:`DaprActorClientBase`): the actor client used for dapr runtime. + """ + + def __init__( + self, + actor_type_info: 'ActorTypeInformation', + message_serializer: Serializer, + state_serializer: Serializer, + actor_client: DaprActorClientBase, + actor_factory: Optional[Callable[['ActorRuntimeContext', ActorId], 'Actor']] = None, + ): + """Creates :class:`ActorRuntimeContext` object. + + Args: + actor_type_info(:class:`ActorTypeInformation`): the type information to initiate + Actor object. + message_serializer(:class:`Serializer`): the serializer for actor invocation + request and response body. + state_serializer(:class:`Serializer`): the seralizer for state value. + actor_client(:class:`DaprActorClientBase`): the actor client used for dapr runtime. + actor_factory(Callable, optional): the factory to create Actor object by + actor_type_info. + """ + self._actor_type_info = actor_type_info + self._actor_factory = actor_factory or self._default_actor_factory + self._message_serializer = message_serializer + self._state_serializer = state_serializer + + # Create State management provider for actor. + self._dapr_client = actor_client + self._provider: StateProvider = StateProvider(self._dapr_client, state_serializer) + + @property + def actor_type_info(self) -> 'ActorTypeInformation': + """Return :class:`ActorTypeInformation` in this context.""" + return self._actor_type_info + + @property + def message_serializer(self) -> Serializer: + """Return message serializer which is used for Actor invocation.""" + return self._message_serializer + + @property + def state_serializer(self) -> Serializer: + """Return state serializer which is used for State value.""" + return self._state_serializer + + @property + def state_provider(self) -> StateProvider: + """Return state provider to manage actor states.""" + return self._provider + + @property + def dapr_client(self) -> DaprActorClientBase: + """Return dapr client.""" + return self._dapr_client + + def create_actor(self, actor_id: ActorId) -> 'Actor': + """Create the object of :class:`Actor` for :class:`ActorId`. + + Args: + actor_id (:class:`ActorId`): ActorId object representing :class:`ActorId` + + Returns: + :class:`Actor`: new actor. + """ + return self._actor_factory(self, actor_id) + + def _default_actor_factory(self, ctx: 'ActorRuntimeContext', actor_id: ActorId) -> 'Actor': + """Creates new Actor with actor_id. + + Args: + ctx (:class:`ActorRuntimeContext`): the actor runtime context for new actor. + actor_id (:class:`ActorId`): the actor id object. + + Returns: + :class:`Actor`: new actor. + """ + return self.actor_type_info.implementation_type(ctx, actor_id) diff --git a/dapr/actor/runtime/manager.py b/dapr/actor/runtime/manager.py index a6d1a792a..d0cee9971 100644 --- a/dapr/actor/runtime/manager.py +++ b/dapr/actor/runtime/manager.py @@ -1,147 +1,147 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import asyncio -import uuid - -from typing import Any, Callable, Coroutine, Dict, Optional - -from dapr.actor.id import ActorId -from dapr.clients.exceptions import DaprInternalError -from dapr.actor.runtime.actor import Actor -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.actor.runtime._method_context import ActorMethodContext -from dapr.actor.runtime.method_dispatcher import ActorMethodDispatcher -from dapr.actor.runtime._reminder_data import ActorReminderData -from dapr.actor.runtime.reentrancy_context import reentrancy_ctx - -TIMER_METHOD_NAME = 'fire_timer' -REMINDER_METHOD_NAME = 'receive_reminder' - - -class ActorManager: - """A Actor Manager manages actors of a specific actor type.""" - - def __init__(self, ctx: ActorRuntimeContext): - self._runtime_ctx = ctx - self._message_serializer = self._runtime_ctx.message_serializer - - self._active_actors: Dict[str, Actor] = {} - self._active_actors_lock = asyncio.Lock() - - self._dispatcher = ActorMethodDispatcher(ctx.actor_type_info) - self._timer_method_context = ActorMethodContext.create_for_timer(TIMER_METHOD_NAME) - self._reminder_method_context = ActorMethodContext.create_for_reminder(REMINDER_METHOD_NAME) - - async def activate_actor(self, actor_id: ActorId): - """Activates actor.""" - actor = self._runtime_ctx.create_actor(actor_id) - await actor._on_activate_internal() - - async with self._active_actors_lock: - self._active_actors[actor_id.id] = actor - - async def deactivate_actor(self, actor_id: ActorId): - async with self._active_actors_lock: - deactivated_actor = self._active_actors.pop(actor_id.id, None) - if not deactivated_actor: - raise ValueError(f'{actor_id} is not activated') - await deactivated_actor._on_deactivate_internal() - - async def fire_reminder( - self, actor_id: ActorId, reminder_name: str, request_body: bytes - ) -> None: - if not self._runtime_ctx.actor_type_info.is_remindable(): - raise ValueError( - f'{self._runtime_ctx.actor_type_info.type_name} does not implment Remindable.' - ) - request_obj = self._message_serializer.deserialize(request_body, object) - if isinstance(request_obj, dict): - reminder_data = ActorReminderData.from_dict(reminder_name, request_obj) - # ignore if request_obj is not dict - - async def invoke_reminder(actor: Actor) -> Optional[bytes]: - reminder = getattr(actor, REMINDER_METHOD_NAME) - if reminder is not None: - await reminder( - reminder_data.reminder_name, - reminder_data.state, - reminder_data.due_time, - reminder_data.period, - reminder_data.ttl, - ) - return None - - await self._dispatch_internal(actor_id, self._reminder_method_context, invoke_reminder) - - async def fire_timer(self, actor_id: ActorId, timer_name: str, request_body: bytes) -> None: - timer = self._message_serializer.deserialize(request_body, object) - - async def invoke_timer(actor: Actor) -> Optional[bytes]: - await actor._fire_timer_internal(timer['callback'], timer['data']) - return None - - await self._dispatch_internal(actor_id, self._timer_method_context, invoke_timer) - - async def dispatch( - self, actor_id: ActorId, actor_method_name: str, request_body: bytes - ) -> bytes: - method_context = ActorMethodContext.create_for_actor(actor_method_name) - arg_types = self._dispatcher.get_arg_types(actor_method_name) - - async def invoke_method(actor): - if len(arg_types) > 0: - # Limitation: - # * Support only one argument - # * If you use the default DaprJSONSerializer, it support only object type - # as a argument - input_obj = self._message_serializer.deserialize(request_body, arg_types[0]) - rtnval = await self._dispatcher.dispatch(actor, actor_method_name, input_obj) - else: - rtnval = await self._dispatcher.dispatch(actor, actor_method_name) - return rtnval - - rtn_obj = await self._dispatch_internal(actor_id, method_context, invoke_method) - return self._message_serializer.serialize(rtn_obj) - - async def _dispatch_internal( - self, - actor_id: ActorId, - method_context: ActorMethodContext, - dispatch_action: Callable[[Actor], Coroutine[Any, Any, Optional[bytes]]], - ) -> object: - # Activate actor when actor is invoked. - if actor_id.id not in self._active_actors: - await self.activate_actor(actor_id) - actor = None - async with self._active_actors_lock: - actor = self._active_actors.get(actor_id.id, None) - if not actor: - raise ValueError(f'{actor_id} is not activated') - - try: - if reentrancy_ctx.get(None) is not None: - actor._state_manager.set_state_context(str(uuid.uuid4())) - await actor._on_pre_actor_method_internal(method_context) - retval = await dispatch_action(actor) - await actor._on_post_actor_method_internal(method_context) - except DaprInternalError as ex: - await actor._on_invoke_failed_internal(ex) - raise ex - finally: - if reentrancy_ctx is not None: - actor._state_manager.set_state_context(None) - - return retval +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import asyncio +import uuid + +from typing import Any, Callable, Coroutine, Dict, Optional + +from dapr.actor.id import ActorId +from dapr.clients.exceptions import DaprInternalError +from dapr.actor.runtime.actor import Actor +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.actor.runtime._method_context import ActorMethodContext +from dapr.actor.runtime.method_dispatcher import ActorMethodDispatcher +from dapr.actor.runtime._reminder_data import ActorReminderData +from dapr.actor.runtime.reentrancy_context import reentrancy_ctx + +TIMER_METHOD_NAME = 'fire_timer' +REMINDER_METHOD_NAME = 'receive_reminder' + + +class ActorManager: + """A Actor Manager manages actors of a specific actor type.""" + + def __init__(self, ctx: ActorRuntimeContext): + self._runtime_ctx = ctx + self._message_serializer = self._runtime_ctx.message_serializer + + self._active_actors: Dict[str, Actor] = {} + self._active_actors_lock = asyncio.Lock() + + self._dispatcher = ActorMethodDispatcher(ctx.actor_type_info) + self._timer_method_context = ActorMethodContext.create_for_timer(TIMER_METHOD_NAME) + self._reminder_method_context = ActorMethodContext.create_for_reminder(REMINDER_METHOD_NAME) + + async def activate_actor(self, actor_id: ActorId): + """Activates actor.""" + actor = self._runtime_ctx.create_actor(actor_id) + await actor._on_activate_internal() + + async with self._active_actors_lock: + self._active_actors[actor_id.id] = actor + + async def deactivate_actor(self, actor_id: ActorId): + async with self._active_actors_lock: + deactivated_actor = self._active_actors.pop(actor_id.id, None) + if not deactivated_actor: + raise ValueError(f'{actor_id} is not activated') + await deactivated_actor._on_deactivate_internal() + + async def fire_reminder( + self, actor_id: ActorId, reminder_name: str, request_body: bytes + ) -> None: + if not self._runtime_ctx.actor_type_info.is_remindable(): + raise ValueError( + f'{self._runtime_ctx.actor_type_info.type_name} does not implment Remindable.' + ) + request_obj = self._message_serializer.deserialize(request_body, object) + if isinstance(request_obj, dict): + reminder_data = ActorReminderData.from_dict(reminder_name, request_obj) + # ignore if request_obj is not dict + + async def invoke_reminder(actor: Actor) -> Optional[bytes]: + reminder = getattr(actor, REMINDER_METHOD_NAME) + if reminder is not None: + await reminder( + reminder_data.reminder_name, + reminder_data.state, + reminder_data.due_time, + reminder_data.period, + reminder_data.ttl, + ) + return None + + await self._dispatch_internal(actor_id, self._reminder_method_context, invoke_reminder) + + async def fire_timer(self, actor_id: ActorId, timer_name: str, request_body: bytes) -> None: + timer = self._message_serializer.deserialize(request_body, object) + + async def invoke_timer(actor: Actor) -> Optional[bytes]: + await actor._fire_timer_internal(timer['callback'], timer['data']) + return None + + await self._dispatch_internal(actor_id, self._timer_method_context, invoke_timer) + + async def dispatch( + self, actor_id: ActorId, actor_method_name: str, request_body: bytes + ) -> bytes: + method_context = ActorMethodContext.create_for_actor(actor_method_name) + arg_types = self._dispatcher.get_arg_types(actor_method_name) + + async def invoke_method(actor): + if len(arg_types) > 0: + # Limitation: + # * Support only one argument + # * If you use the default DaprJSONSerializer, it support only object type + # as a argument + input_obj = self._message_serializer.deserialize(request_body, arg_types[0]) + rtnval = await self._dispatcher.dispatch(actor, actor_method_name, input_obj) + else: + rtnval = await self._dispatcher.dispatch(actor, actor_method_name) + return rtnval + + rtn_obj = await self._dispatch_internal(actor_id, method_context, invoke_method) + return self._message_serializer.serialize(rtn_obj) + + async def _dispatch_internal( + self, + actor_id: ActorId, + method_context: ActorMethodContext, + dispatch_action: Callable[[Actor], Coroutine[Any, Any, Optional[bytes]]], + ) -> object: + # Activate actor when actor is invoked. + if actor_id.id not in self._active_actors: + await self.activate_actor(actor_id) + actor = None + async with self._active_actors_lock: + actor = self._active_actors.get(actor_id.id, None) + if not actor: + raise ValueError(f'{actor_id} is not activated') + + try: + if reentrancy_ctx.get(None) is not None: + actor._state_manager.set_state_context(str(uuid.uuid4())) + await actor._on_pre_actor_method_internal(method_context) + retval = await dispatch_action(actor) + await actor._on_post_actor_method_internal(method_context) + except DaprInternalError as ex: + await actor._on_invoke_failed_internal(ex) + raise ex + finally: + if reentrancy_ctx is not None: + actor._state_manager.set_state_context(None) + + return retval diff --git a/dapr/actor/runtime/method_dispatcher.py b/dapr/actor/runtime/method_dispatcher.py index 8d9b65114..c01a860d7 100644 --- a/dapr/actor/runtime/method_dispatcher.py +++ b/dapr/actor/runtime/method_dispatcher.py @@ -1,44 +1,44 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Any, Dict, List -from dapr.actor.runtime.actor import Actor -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.actor.runtime._type_utils import get_dispatchable_attrs - - -class ActorMethodDispatcher: - def __init__(self, type_info: ActorTypeInformation): - self._dispatch_mapping = get_dispatchable_attrs(type_info.implementation_type) - - async def dispatch(self, actor: Actor, name: str, *args, **kwargs) -> Any: - self._check_name_exist(name) - return await getattr(actor, self._dispatch_mapping[name]['method_name'])(*args, **kwargs) - - def get_arg_names(self, name: str) -> List[str]: - self._check_name_exist(name) - return self._dispatch_mapping[name]['arg_names'] - - def get_arg_types(self, name: str) -> List[Any]: - self._check_name_exist(name) - return self._dispatch_mapping[name]['arg_types'] - - def get_return_type(self, name: str) -> Dict[str, Any]: - self._check_name_exist(name) - return self._dispatch_mapping[name]['return_types'] - - def _check_name_exist(self, name: str): - if name not in self._dispatch_mapping: - raise AttributeError(f'type object {self.__class__.__name__} has no method {name}') +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Any, Dict, List +from dapr.actor.runtime.actor import Actor +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.actor.runtime._type_utils import get_dispatchable_attrs + + +class ActorMethodDispatcher: + def __init__(self, type_info: ActorTypeInformation): + self._dispatch_mapping = get_dispatchable_attrs(type_info.implementation_type) + + async def dispatch(self, actor: Actor, name: str, *args, **kwargs) -> Any: + self._check_name_exist(name) + return await getattr(actor, self._dispatch_mapping[name]['method_name'])(*args, **kwargs) + + def get_arg_names(self, name: str) -> List[str]: + self._check_name_exist(name) + return self._dispatch_mapping[name]['arg_names'] + + def get_arg_types(self, name: str) -> List[Any]: + self._check_name_exist(name) + return self._dispatch_mapping[name]['arg_types'] + + def get_return_type(self, name: str) -> Dict[str, Any]: + self._check_name_exist(name) + return self._dispatch_mapping[name]['return_types'] + + def _check_name_exist(self, name: str): + if name not in self._dispatch_mapping: + raise AttributeError(f'type object {self.__class__.__name__} has no method {name}') diff --git a/dapr/actor/runtime/mock_actor.py b/dapr/actor/runtime/mock_actor.py new file mode 100644 index 000000000..a932d8dde --- /dev/null +++ b/dapr/actor/runtime/mock_actor.py @@ -0,0 +1,121 @@ +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from datetime import timedelta +from typing import Any, Optional, TypeVar + +from dapr.actor.id import ActorId +from dapr.actor.runtime._reminder_data import ActorReminderData +from dapr.actor.runtime._timer_data import TIMER_CALLBACK, ActorTimerData +from dapr.actor.runtime.actor import Actor +from dapr.actor.runtime.mock_state_manager import MockStateManager +from dapr.actor.runtime.state_manager import ActorStateManager + + +class MockActor(Actor): + """A mock actor class to be used to override certain Actor methods for unit testing. + To be used only via the create_mock_actor function, which takes in a class and returns a + mock actor object for that class. + + Examples: + class SomeActorInterface(ActorInterface): + @actor_method(name="method") + async def set_state(self, data: dict) -> None: + + class SomeActor(Actor, SomeActorInterface): + async def set_state(self, data: dict) -> None: + await self._state_manager.set_state('state', data) + await self._state_manager.save_state() + + mock_actor = create_mock_actor(SomeActor) + assert mock_actor._state_manager._mock_state == {} + await mock_actor.set_state({"test":10}) + assert mock_actor._state_manager._mock_state == {"test":10} + """ + + def __init__(self, actor_id: str, initstate: Optional[dict]): + self.id = ActorId(actor_id) + self._runtime_ctx = None + self._state_manager: ActorStateManager = MockStateManager(self, initstate) + + async def register_timer( + self, + name: Optional[str], + callback: TIMER_CALLBACK, + state: Any, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ) -> None: + """Adds actor timer to self._state_manager._mock_timers. + Args: + name (str): the name of the timer to register. + callback (Callable): An awaitable callable which will be called when the timer fires. + state (Any): An object which will pass to the callback method, or None. + due_time (datetime.timedelta): the amount of time to delay before the awaitable + callback is first invoked. + period (datetime.timedelta): the time interval between invocations + of the awaitable callback. + ttl (Optional[datetime.timedelta]): the time interval before the timer stops firing + """ + name = name or self.__get_new_timer_name() + timer = ActorTimerData(name, callback, state, due_time, period, ttl) + self._state_manager._mock_timers[name] = timer + + async def unregister_timer(self, name: str) -> None: + """Unregisters actor timer from self._state_manager._mock_timers. + + Args: + name (str): the name of the timer to unregister. + """ + self._state_manager._mock_timers.pop(name, None) + + async def register_reminder( + self, + name: str, + state: bytes, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ) -> None: + """Adds actor reminder to self._state_manager._mock_reminders. + + Args: + name (str): the name of the reminder to register. the name must be unique per actor. + state (bytes): the user state passed to the reminder invocation. + due_time (datetime.timedelta): the amount of time to delay before invoking the reminder + for the first time. + period (datetime.timedelta): the time interval between reminder invocations after + the first invocation. + ttl (datetime.timedelta): the time interval before the reminder stops firing + """ + reminder = ActorReminderData(name, state, due_time, period, ttl) + self._state_manager._mock_reminders[name] = reminder + + async def unregister_reminder(self, name: str) -> None: + """Unregisters actor reminder from self._state_manager._mock_reminders.. + + Args: + name (str): the name of the reminder to unregister. + """ + self._state_manager._mock_reminders.pop(name, None) + + +T = TypeVar('T', bound=Actor) + + +def create_mock_actor(cls1: type[T], actor_id: str, initstate: Optional[dict] = None) -> T: + class MockSuperClass(MockActor, cls1): + pass + + return MockSuperClass(actor_id, initstate) # type: ignore diff --git a/dapr/actor/runtime/mock_state_manager.py b/dapr/actor/runtime/mock_state_manager.py new file mode 100644 index 000000000..71568f1db --- /dev/null +++ b/dapr/actor/runtime/mock_state_manager.py @@ -0,0 +1,235 @@ +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import asyncio +from contextvars import ContextVar +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, TypeVar + +from dapr.actor.runtime._reminder_data import ActorReminderData +from dapr.actor.runtime._timer_data import ActorTimerData +from dapr.actor.runtime.state_change import ActorStateChange, StateChangeKind +from dapr.actor.runtime.state_manager import ActorStateManager, StateMetadata + +if TYPE_CHECKING: + from dapr.actor.runtime.mock_actor import MockActor + +T = TypeVar('T') +CONTEXT: ContextVar[Optional[Dict[str, Any]]] = ContextVar('state_tracker_context') + + +class MockStateManager(ActorStateManager): + def __init__(self, actor: 'MockActor', initstate: Optional[dict]): + self._actor = actor + self._default_state_change_tracker: Dict[str, StateMetadata] = {} + self._mock_state: dict[str, Any] = {} + self._mock_timers: dict[str, ActorTimerData] = {} + self._mock_reminders: dict[str, ActorReminderData] = {} + if initstate: + self._mock_state = initstate + + async def add_state(self, state_name: str, value: T) -> None: + if not await self.try_add_state(state_name, value): + raise ValueError(f'The actor state name {state_name} already exist.') + + async def try_add_state(self, state_name: str, value: T) -> bool: + if state_name in self._default_state_change_tracker: + state_metadata = self._default_state_change_tracker[state_name] + if state_metadata.change_kind == StateChangeKind.remove: + self._default_state_change_tracker[state_name] = StateMetadata( + value, StateChangeKind.update + ) + return True + return False + existed = state_name in self._mock_state + if not existed: + return False + self._default_state_change_tracker[state_name] = StateMetadata(value, StateChangeKind.add) + self._mock_state[state_name] = value + return True + + async def get_state(self, state_name: str) -> Optional[T]: + has_value, val = await self.try_get_state(state_name) + if has_value: + return val + else: + raise KeyError(f'Actor State with name {state_name} was not found.') + + async def try_get_state(self, state_name: str) -> Tuple[bool, Optional[T]]: + if state_name in self._default_state_change_tracker: + state_metadata = self._default_state_change_tracker[state_name] + if state_metadata.change_kind == StateChangeKind.remove: + return False, None + return True, state_metadata.value + has_value = state_name in self._mock_state + val = self._mock_state.get(state_name) + if has_value: + self._default_state_change_tracker[state_name] = StateMetadata( + val, StateChangeKind.none + ) + return has_value, val + + async def set_state(self, state_name: str, value: T) -> None: + await self.set_state_ttl(state_name, value, None) + + async def set_state_ttl(self, state_name: str, value: T, ttl_in_seconds: Optional[int]) -> None: + if ttl_in_seconds is not None and ttl_in_seconds < 0: + return + + if state_name in self._default_state_change_tracker: + state_metadata = self._default_state_change_tracker[state_name] + state_metadata.value = value + state_metadata.ttl_in_seconds = ttl_in_seconds + + if ( + state_metadata.change_kind == StateChangeKind.none + or state_metadata.change_kind == StateChangeKind.remove + ): + state_metadata.change_kind = StateChangeKind.update + self._default_state_change_tracker[state_name] = state_metadata + self._mock_state[state_name] = value + return + + existed = state_name in self._mock_state + if existed: + self._default_state_change_tracker[state_name] = StateMetadata( + value, StateChangeKind.update, ttl_in_seconds + ) + else: + self._default_state_change_tracker[state_name] = StateMetadata( + value, StateChangeKind.add, ttl_in_seconds + ) + self._mock_state[state_name] = value + + async def remove_state(self, state_name: str) -> None: + if not await self.try_remove_state(state_name): + raise KeyError(f'Actor State with name {state_name} was not found.') + + async def try_remove_state(self, state_name: str) -> bool: + if state_name in self._default_state_change_tracker: + state_metadata = self._default_state_change_tracker[state_name] + if state_metadata.change_kind == StateChangeKind.remove: + return False + elif state_metadata.change_kind == StateChangeKind.add: + self._default_state_change_tracker.pop(state_name, None) + self._mock_state.pop(state_name, None) + return True + self._mock_state.pop(state_name, None) + state_metadata.change_kind = StateChangeKind.remove + return True + + existed = state_name in self._mock_state + if existed: + self._default_state_change_tracker[state_name] = StateMetadata( + None, StateChangeKind.remove + ) + self._mock_state.pop(state_name, None) + return True + return False + + async def contains_state(self, state_name: str) -> bool: + if state_name in self._default_state_change_tracker: + state_metadata = self._default_state_change_tracker[state_name] + return state_metadata.change_kind != StateChangeKind.remove + return state_name in self._mock_state + + async def get_or_add_state(self, state_name: str, value: T) -> Optional[T]: + has_value, val = await self.try_get_state(state_name) + if has_value: + return val + change_kind = ( + StateChangeKind.update + if self.is_state_marked_for_remove(state_name) + else StateChangeKind.add + ) + self._default_state_change_tracker[state_name] = StateMetadata(value, change_kind) + return value + + async def add_or_update_state( + self, state_name: str, value: T, update_value_factory: Callable[[str, T], T] + ) -> T: + if not callable(update_value_factory): + raise AttributeError('update_value_factory is not callable') + + if state_name in self._default_state_change_tracker: + state_metadata = self._default_state_change_tracker[state_name] + if state_metadata.change_kind == StateChangeKind.remove: + self._default_state_change_tracker[state_name] = StateMetadata( + value, StateChangeKind.update + ) + self._mock_state[state_name] = value + return value + new_value = update_value_factory(state_name, state_metadata.value) + state_metadata.value = new_value + if state_metadata.change_kind == StateChangeKind.none: + state_metadata.change_kind = StateChangeKind.update + self._default_state_change_tracker[state_name] = state_metadata + self._mock_state[state_name] = value + return new_value + + has_value = state_name in self._mock_state + val: Any = self._mock_state.get(state_name) + if has_value: + new_value = update_value_factory(state_name, val) + self._default_state_change_tracker[state_name] = StateMetadata( + new_value, StateChangeKind.update + ) + return new_value + self._default_state_change_tracker[state_name] = StateMetadata(value, StateChangeKind.add) + return value + + async def get_state_names(self) -> List[str]: + # TODO: Get all state names from Dapr once implemented. + def append_names_sync(): + state_names = [] + for key, value in self._default_state_change_tracker.items(): + if value.change_kind == StateChangeKind.add: + state_names.append(key) + elif value.change_kind == StateChangeKind.remove: + state_names.append(key) + return state_names + + default_loop = asyncio.get_running_loop() + return await default_loop.run_in_executor(None, append_names_sync) + + async def clear_cache(self) -> None: + self._default_state_change_tracker.clear() + + async def save_state(self) -> None: + if len(self._default_state_change_tracker) == 0: + return + + state_changes = [] + states_to_remove = [] + for state_name, state_metadata in self._default_state_change_tracker.items(): + if state_metadata.change_kind == StateChangeKind.none: + continue + state_changes.append( + ActorStateChange( + state_name, + state_metadata.value, + state_metadata.change_kind, + state_metadata.ttl_in_seconds, + ) + ) + if state_metadata.change_kind == StateChangeKind.remove: + states_to_remove.append(state_name) + # Mark the states as unmodified so that tracking for next invocation is done correctly. + state_metadata.change_kind = StateChangeKind.none + for state_name in states_to_remove: + self._default_state_change_tracker.pop(state_name, None) + + def is_state_marked_for_remove(self, state_name: str) -> bool: + return ( + state_name in self._default_state_change_tracker + and self._default_state_change_tracker[state_name].change_kind == StateChangeKind.remove + ) diff --git a/dapr/actor/runtime/reentrancy_context.py b/dapr/actor/runtime/reentrancy_context.py index 0fc9927df..3ad93cceb 100644 --- a/dapr/actor/runtime/reentrancy_context.py +++ b/dapr/actor/runtime/reentrancy_context.py @@ -1,19 +1,19 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Optional -from contextvars import ContextVar - -reentrancy_ctx: ContextVar[Optional[str]] = ContextVar('reentrancy_ctx', default=None) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Optional +from contextvars import ContextVar + +reentrancy_ctx: ContextVar[Optional[str]] = ContextVar('reentrancy_ctx', default=None) diff --git a/dapr/actor/runtime/remindable.py b/dapr/actor/runtime/remindable.py index 6d6a2a3f0..1233d54c9 100644 --- a/dapr/actor/runtime/remindable.py +++ b/dapr/actor/runtime/remindable.py @@ -1,46 +1,46 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from abc import ABC, abstractmethod -from datetime import timedelta -from typing import Optional - - -class Remindable(ABC): - """An interface that actors must implement to consume reminders registered - using :meth:`Remindable.register_reminder`. - """ - - @abstractmethod - async def receive_reminder( - self, - name: str, - state: bytes, - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta] = None, - ) -> None: - """A callback which will be called when reminder is triggered. - - Args: - name (str): the name of the reminder to register. the name must be unique per actor. - state (bytes): the user state passed to the reminder invocation. - due_time (datetime.timedelta): the amount of time to delay before invoking the reminder - for the first time. - period (datetime.timedelta): the time interval between reminder invocations - after the first invocation. - ttl (datetime.timedelta): the time interval before the reminder stops firing - """ - ... +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from abc import ABC, abstractmethod +from datetime import timedelta +from typing import Optional + + +class Remindable(ABC): + """An interface that actors must implement to consume reminders registered + using :meth:`Remindable.register_reminder`. + """ + + @abstractmethod + async def receive_reminder( + self, + name: str, + state: bytes, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta] = None, + ) -> None: + """A callback which will be called when reminder is triggered. + + Args: + name (str): the name of the reminder to register. the name must be unique per actor. + state (bytes): the user state passed to the reminder invocation. + due_time (datetime.timedelta): the amount of time to delay before invoking the reminder + for the first time. + period (datetime.timedelta): the time interval between reminder invocations + after the first invocation. + ttl (datetime.timedelta): the time interval before the reminder stops firing + """ + ... diff --git a/dapr/actor/runtime/runtime.py b/dapr/actor/runtime/runtime.py index 3659f1479..ef6950acf 100644 --- a/dapr/actor/runtime/runtime.py +++ b/dapr/actor/runtime/runtime.py @@ -1,190 +1,190 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import asyncio - -from typing import Dict, List, Optional, Type, Callable - -from dapr.actor.id import ActorId -from dapr.actor.runtime.actor import Actor -from dapr.actor.runtime.config import ActorRuntimeConfig -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.actor.runtime.manager import ActorManager -from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient -from dapr.serializers import Serializer, DefaultJSONSerializer -from dapr.conf import settings - -from dapr.actor.runtime.reentrancy_context import reentrancy_ctx - - -class ActorRuntime: - """The class that creates instances of :class:`Actor` and - activates and deactivates :class:`Actor`. - """ - - _actor_config = ActorRuntimeConfig() - - _actor_managers: Dict[str, ActorManager] = {} - _actor_managers_lock = asyncio.Lock() - - @classmethod - async def register_actor( - cls, - actor: Type[Actor], - message_serializer: Serializer = DefaultJSONSerializer(), - state_serializer: Serializer = DefaultJSONSerializer(), - http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS, - actor_factory: Optional[Callable[['ActorRuntimeContext', ActorId], 'Actor']] = None, - ) -> None: - """Registers an :class:`Actor` object with the runtime. - - Args: - actor (:class:`Actor`): Actor implementation. - message_serializer (:class:`Serializer`): A serializer that serializes message - between actors. - state_serializer (:class:`Serializer`): Serializer that serializes state values. - http_timeout_seconds (:int:): a configurable timeout value - """ - type_info = ActorTypeInformation.create(actor) - # TODO: We will allow to use gRPC client later. - actor_client = DaprActorHttpClient(message_serializer, timeout=http_timeout_seconds) - ctx = ActorRuntimeContext( - type_info, message_serializer, state_serializer, actor_client, actor_factory - ) - - # Create an ActorManager, override existing entry if registered again. - async with cls._actor_managers_lock: - cls._actor_managers[type_info.type_name] = ActorManager(ctx) - cls._actor_config.update_entities(ActorRuntime.get_registered_actor_types()) - - @classmethod - def get_registered_actor_types(cls) -> List[str]: - """Gets registered actor types.""" - return [actor_type for actor_type in cls._actor_managers.keys()] - - @classmethod - async def deactivate(cls, actor_type_name: str, actor_id: str) -> None: - """Deactivates an actor for an actor type with given actor id. - - Args: - actor_type_name (str): the name of actor type. - actor_id (str): the actor id. - - Raises: - ValueError: `actor_type_name` actor type is not registered. - """ - manager = await cls._get_actor_manager(actor_type_name) - if not manager: - raise ValueError(f'{actor_type_name} is not registered.') - await manager.deactivate_actor(ActorId(actor_id)) - - @classmethod - async def dispatch( - cls, - actor_type_name: str, - actor_id: str, - actor_method_name: str, - request_body: bytes, - reentrancy_id: Optional[str] = None, - ) -> bytes: - """Dispatches actor method defined in actor_type. - - Args: - actor_type_name (str): the name of actor type. - actor_id (str): Actor ID. - actor_method_name (str): the method name that is dispatched. - request_body (bytes): the body of request that is passed to actor method arguments. - reentrancy_id (str): reentrancy ID obtained from the dapr_reentrancy_id header - if present. - - Returns: - bytes: serialized response data. - - Raises: - ValueError: `actor_type_name` actor type is not registered. - """ - reentrancy_ctx.set(reentrancy_id) - manager = await cls._get_actor_manager(actor_type_name) - if not manager: - raise ValueError(f'{actor_type_name} is not registered.') - return await manager.dispatch(ActorId(actor_id), actor_method_name, request_body) - - @classmethod - async def fire_reminder( - cls, actor_type_name: str, actor_id: str, name: str, state: bytes - ) -> None: - """Fires a reminder for the Actor. - - Args: - actor_type_name (str): the name of actor type. - actor_id (str): Actor ID. - name (str): the name of reminder. - state (bytes): the body of request that is passed to reminder callback. - - Raises: - ValueError: `actor_type_name` actor type is not registered. - """ - - manager = await cls._get_actor_manager(actor_type_name) - if not manager: - raise ValueError(f'{actor_type_name} is not registered.') - await manager.fire_reminder(ActorId(actor_id), name, state) - - @classmethod - async def fire_timer(cls, actor_type_name: str, actor_id: str, name: str, state: bytes) -> None: - """Fires a timer for the Actor. - - Args: - actor_type_name (str): the name of actor type. - actor_id (str): Actor ID. - name (str): the timer's name. - state (bytes): the timer's trigger body. - - Raises: - ValueError: `actor_type_name` actor type is not registered. - """ - manager = await cls._get_actor_manager(actor_type_name) - if not manager: - raise ValueError(f'{actor_type_name} is not registered.') - await manager.fire_timer(ActorId(actor_id), name, state) - - @classmethod - def set_actor_config(cls, config: ActorRuntimeConfig) -> None: - """Sets actor runtime config - - Args: - config (:class:`ActorRuntimeConfig`): The config to set up actor runtime - """ - cls._actor_config = config - cls._actor_config.update_entities(ActorRuntime.get_registered_actor_types()) - - @classmethod - def get_actor_config(cls) -> ActorRuntimeConfig: - """Gets :class:`ActorRuntimeConfig`.""" - return cls._actor_config - - @classmethod - async def _get_actor_manager(cls, actor_type_name: str) -> Optional[ActorManager]: - """Gets :class:`ActorManager` object for actor_type_name. - - Args: - actor_type_name (str): the type name of actor. - - Returns: - :class:`ActorManager`: an actor manager object for actor_type_name actor. - """ - async with cls._actor_managers_lock: - return cls._actor_managers.get(actor_type_name) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import asyncio + +from typing import Dict, List, Optional, Type, Callable + +from dapr.actor.id import ActorId +from dapr.actor.runtime.actor import Actor +from dapr.actor.runtime.config import ActorRuntimeConfig +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.actor.runtime.manager import ActorManager +from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient +from dapr.serializers import Serializer, DefaultJSONSerializer +from dapr.conf import settings + +from dapr.actor.runtime.reentrancy_context import reentrancy_ctx + + +class ActorRuntime: + """The class that creates instances of :class:`Actor` and + activates and deactivates :class:`Actor`. + """ + + _actor_config = ActorRuntimeConfig() + + _actor_managers: Dict[str, ActorManager] = {} + _actor_managers_lock = asyncio.Lock() + + @classmethod + async def register_actor( + cls, + actor: Type[Actor], + message_serializer: Serializer = DefaultJSONSerializer(), + state_serializer: Serializer = DefaultJSONSerializer(), + http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS, + actor_factory: Optional[Callable[['ActorRuntimeContext', ActorId], 'Actor']] = None, + ) -> None: + """Registers an :class:`Actor` object with the runtime. + + Args: + actor (:class:`Actor`): Actor implementation. + message_serializer (:class:`Serializer`): A serializer that serializes message + between actors. + state_serializer (:class:`Serializer`): Serializer that serializes state values. + http_timeout_seconds (:int:): a configurable timeout value + """ + type_info = ActorTypeInformation.create(actor) + # TODO: We will allow to use gRPC client later. + actor_client = DaprActorHttpClient(message_serializer, timeout=http_timeout_seconds) + ctx = ActorRuntimeContext( + type_info, message_serializer, state_serializer, actor_client, actor_factory + ) + + # Create an ActorManager, override existing entry if registered again. + async with cls._actor_managers_lock: + cls._actor_managers[type_info.type_name] = ActorManager(ctx) + cls._actor_config.update_entities(ActorRuntime.get_registered_actor_types()) + + @classmethod + def get_registered_actor_types(cls) -> List[str]: + """Gets registered actor types.""" + return [actor_type for actor_type in cls._actor_managers.keys()] + + @classmethod + async def deactivate(cls, actor_type_name: str, actor_id: str) -> None: + """Deactivates an actor for an actor type with given actor id. + + Args: + actor_type_name (str): the name of actor type. + actor_id (str): the actor id. + + Raises: + ValueError: `actor_type_name` actor type is not registered. + """ + manager = await cls._get_actor_manager(actor_type_name) + if not manager: + raise ValueError(f'{actor_type_name} is not registered.') + await manager.deactivate_actor(ActorId(actor_id)) + + @classmethod + async def dispatch( + cls, + actor_type_name: str, + actor_id: str, + actor_method_name: str, + request_body: bytes, + reentrancy_id: Optional[str] = None, + ) -> bytes: + """Dispatches actor method defined in actor_type. + + Args: + actor_type_name (str): the name of actor type. + actor_id (str): Actor ID. + actor_method_name (str): the method name that is dispatched. + request_body (bytes): the body of request that is passed to actor method arguments. + reentrancy_id (str): reentrancy ID obtained from the dapr_reentrancy_id header + if present. + + Returns: + bytes: serialized response data. + + Raises: + ValueError: `actor_type_name` actor type is not registered. + """ + reentrancy_ctx.set(reentrancy_id) + manager = await cls._get_actor_manager(actor_type_name) + if not manager: + raise ValueError(f'{actor_type_name} is not registered.') + return await manager.dispatch(ActorId(actor_id), actor_method_name, request_body) + + @classmethod + async def fire_reminder( + cls, actor_type_name: str, actor_id: str, name: str, state: bytes + ) -> None: + """Fires a reminder for the Actor. + + Args: + actor_type_name (str): the name of actor type. + actor_id (str): Actor ID. + name (str): the name of reminder. + state (bytes): the body of request that is passed to reminder callback. + + Raises: + ValueError: `actor_type_name` actor type is not registered. + """ + + manager = await cls._get_actor_manager(actor_type_name) + if not manager: + raise ValueError(f'{actor_type_name} is not registered.') + await manager.fire_reminder(ActorId(actor_id), name, state) + + @classmethod + async def fire_timer(cls, actor_type_name: str, actor_id: str, name: str, state: bytes) -> None: + """Fires a timer for the Actor. + + Args: + actor_type_name (str): the name of actor type. + actor_id (str): Actor ID. + name (str): the timer's name. + state (bytes): the timer's trigger body. + + Raises: + ValueError: `actor_type_name` actor type is not registered. + """ + manager = await cls._get_actor_manager(actor_type_name) + if not manager: + raise ValueError(f'{actor_type_name} is not registered.') + await manager.fire_timer(ActorId(actor_id), name, state) + + @classmethod + def set_actor_config(cls, config: ActorRuntimeConfig) -> None: + """Sets actor runtime config + + Args: + config (:class:`ActorRuntimeConfig`): The config to set up actor runtime + """ + cls._actor_config = config + cls._actor_config.update_entities(ActorRuntime.get_registered_actor_types()) + + @classmethod + def get_actor_config(cls) -> ActorRuntimeConfig: + """Gets :class:`ActorRuntimeConfig`.""" + return cls._actor_config + + @classmethod + async def _get_actor_manager(cls, actor_type_name: str) -> Optional[ActorManager]: + """Gets :class:`ActorManager` object for actor_type_name. + + Args: + actor_type_name (str): the type name of actor. + + Returns: + :class:`ActorManager`: an actor manager object for actor_type_name actor. + """ + async with cls._actor_managers_lock: + return cls._actor_managers.get(actor_type_name) diff --git a/dapr/actor/runtime/state_change.py b/dapr/actor/runtime/state_change.py index dba21e2c1..76b93fb0f 100644 --- a/dapr/actor/runtime/state_change.py +++ b/dapr/actor/runtime/state_change.py @@ -1,64 +1,64 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from enum import Enum -from typing import TypeVar, Generic, Optional - -T = TypeVar('T') - - -class StateChangeKind(Enum): - """A enumeration that represents the kind of state change for an actor state - when saves change is called to a set of actor states. - """ - - # No change in state - none = 0 - # The state needs to be added - add = 1 - # The state needs to be updated - update = 2 - # The state needs to be removed - remove = 3 - - -class ActorStateChange(Generic[T]): - def __init__( - self, - state_name: str, - value: T, - change_kind: StateChangeKind, - ttl_in_seconds: Optional[int] = None, - ): - self._state_name = state_name - self._value = value - self._change_kind = change_kind - self._ttl_in_seconds = ttl_in_seconds - - @property - def state_name(self) -> str: - return self._state_name - - @property - def value(self) -> T: - return self._value - - @property - def change_kind(self) -> StateChangeKind: - return self._change_kind - - @property - def ttl_in_seconds(self) -> Optional[int]: - return self._ttl_in_seconds +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from enum import Enum +from typing import TypeVar, Generic, Optional + +T = TypeVar('T') + + +class StateChangeKind(Enum): + """A enumeration that represents the kind of state change for an actor state + when saves change is called to a set of actor states. + """ + + # No change in state + none = 0 + # The state needs to be added + add = 1 + # The state needs to be updated + update = 2 + # The state needs to be removed + remove = 3 + + +class ActorStateChange(Generic[T]): + def __init__( + self, + state_name: str, + value: T, + change_kind: StateChangeKind, + ttl_in_seconds: Optional[int] = None, + ): + self._state_name = state_name + self._value = value + self._change_kind = change_kind + self._ttl_in_seconds = ttl_in_seconds + + @property + def state_name(self) -> str: + return self._state_name + + @property + def value(self) -> T: + return self._value + + @property + def change_kind(self) -> StateChangeKind: + return self._change_kind + + @property + def ttl_in_seconds(self) -> Optional[int]: + return self._ttl_in_seconds diff --git a/dapr/actor/runtime/state_manager.py b/dapr/actor/runtime/state_manager.py index 7132175b3..f9c996f0a 100644 --- a/dapr/actor/runtime/state_manager.py +++ b/dapr/actor/runtime/state_manager.py @@ -15,13 +15,14 @@ import asyncio from contextvars import ContextVar +from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, List, Optional, Tuple, TypeVar -from dapr.actor.runtime.state_change import StateChangeKind, ActorStateChange from dapr.actor.runtime.reentrancy_context import reentrancy_ctx - -from typing import Any, Callable, Dict, Generic, List, Tuple, TypeVar, Optional, TYPE_CHECKING +from dapr.actor.runtime.state_change import ActorStateChange, StateChangeKind if TYPE_CHECKING: + from dapr.actor.runtime._reminder_data import ActorReminderData + from dapr.actor.runtime._timer_data import ActorTimerData from dapr.actor.runtime.actor import Actor T = TypeVar('T') @@ -69,6 +70,9 @@ def __init__(self, actor: 'Actor'): self._type_name = actor.runtime_ctx.actor_type_info.type_name self._default_state_change_tracker: Dict[str, StateMetadata] = {} + self._mock_state: dict[str, Any] + self._mock_timers: dict[str, ActorTimerData] + self._mock_reminders: dict[str, ActorReminderData] async def add_state(self, state_name: str, value: T) -> None: if not await self.try_add_state(state_name, value): diff --git a/dapr/aio/clients/__init__.py b/dapr/aio/clients/__init__.py index ae77258e8..df36b8cf8 100644 --- a/dapr/aio/clients/__init__.py +++ b/dapr/aio/clients/__init__.py @@ -1,143 +1,143 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Callable, Dict, List, Optional, Union - -from dapr.clients.base import DaprActorClientBase -from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN -from dapr.aio.clients.grpc.client import DaprGrpcClientAsync, MetadataTuple, InvokeMethodResponse -from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient -from dapr.clients.http.dapr_invocation_http_client import DaprInvocationHttpClient -from dapr.conf import settings -from google.protobuf.message import Message as GrpcMessage - -__all__ = [ - 'DaprClient', - 'DaprActorClientBase', - 'DaprActorHttpClient', - 'DaprInternalError', - 'ERROR_CODE_UNKNOWN', -] - -from grpc.aio import ( # type: ignore - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, -) - - -class DaprClient(DaprGrpcClientAsync): - """The Dapr python-sdk uses gRPC for most operations. The exception being - service invocation which needs to support HTTP to HTTP invocations. The sdk defaults - to HTTP but can be overridden with the DAPR_API_METHOD_INVOCATION_PROTOCOL environment - variable. See: https://github.com/dapr/python-sdk/issues/176 for more details""" - - def __init__( - self, - address: Optional[str] = None, - headers_callback: Optional[Callable[[], Dict[str, str]]] = None, - interceptors: Optional[ - List[ - Union[ - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, - ] - ] - ] = None, - http_timeout_seconds: Optional[int] = None, - max_grpc_message_length: Optional[int] = None, - ): - """Connects to Dapr Runtime and via gRPC and HTTP. - - Args: - address (str, optional): Dapr Runtime gRPC endpoint address. - headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. - interceptors (list of UnaryUnaryClientInterceptor or - UnaryStreamClientInterceptor or - StreamUnaryClientInterceptor or - StreamStreamClientInterceptor, optional): gRPC interceptors. - http_timeout_seconds (int): specify a timeout for http connections - max_grpc_messsage_length (int, optional): The maximum grpc send and receive - message length in bytes. - """ - super().__init__(address, interceptors, max_grpc_message_length) - self.invocation_client = None - - invocation_protocol = settings.DAPR_API_METHOD_INVOCATION_PROTOCOL.upper() - - if invocation_protocol == 'HTTP': - if http_timeout_seconds is None: - http_timeout_seconds = settings.DAPR_HTTP_TIMEOUT_SECONDS - self.invocation_client = DaprInvocationHttpClient( - headers_callback=headers_callback, timeout=http_timeout_seconds - ) - elif invocation_protocol == 'GRPC': - pass - else: - raise DaprInternalError( - f'Unknown value for DAPR_API_METHOD_INVOCATION_PROTOCOL: {invocation_protocol}' - ) - - async def invoke_method( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage], - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invoke a service method over gRPC or HTTP. - - Args: - app_id (str): Application Id. - method_name (str): Method to be invoked. - data (bytes or str or GrpcMessage, optional): Data for requet's body. - content_type (str, optional): Content type of the data. - metadata (MetadataTuple, optional): Additional metadata or headers. - http_verb (str, optional): HTTP verb for the request. - http_querystring (MetadataTuple, optional): Query parameters. - timeout (int, optional): Request timeout in seconds. - - Returns: - InvokeMethodResponse: the method invocation response. - """ - if self.invocation_client: - return await self.invocation_client.invoke_method_async( - app_id, - method_name, - data, - content_type=content_type, - metadata=metadata, - http_verb=http_verb, - http_querystring=http_querystring, - timeout=timeout, - ) - else: - return await super().invoke_method( - app_id, - method_name, - data, - content_type=content_type, - metadata=metadata, - http_verb=http_verb, - http_querystring=http_querystring, - timeout=timeout, - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Callable, Dict, List, Optional, Union + +from dapr.clients.base import DaprActorClientBase +from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN +from dapr.aio.clients.grpc.client import DaprGrpcClientAsync, MetadataTuple, InvokeMethodResponse +from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient +from dapr.clients.http.dapr_invocation_http_client import DaprInvocationHttpClient +from dapr.conf import settings +from google.protobuf.message import Message as GrpcMessage + +__all__ = [ + 'DaprClient', + 'DaprActorClientBase', + 'DaprActorHttpClient', + 'DaprInternalError', + 'ERROR_CODE_UNKNOWN', +] + +from grpc.aio import ( # type: ignore + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, +) + + +class DaprClient(DaprGrpcClientAsync): + """The Dapr python-sdk uses gRPC for most operations. The exception being + service invocation which needs to support HTTP to HTTP invocations. The sdk defaults + to HTTP but can be overridden with the DAPR_API_METHOD_INVOCATION_PROTOCOL environment + variable. See: https://github.com/dapr/python-sdk/issues/176 for more details""" + + def __init__( + self, + address: Optional[str] = None, + headers_callback: Optional[Callable[[], Dict[str, str]]] = None, + interceptors: Optional[ + List[ + Union[ + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, + ] + ] + ] = None, + http_timeout_seconds: Optional[int] = None, + max_grpc_message_length: Optional[int] = None, + ): + """Connects to Dapr Runtime and via gRPC and HTTP. + + Args: + address (str, optional): Dapr Runtime gRPC endpoint address. + headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. + interceptors (list of UnaryUnaryClientInterceptor or + UnaryStreamClientInterceptor or + StreamUnaryClientInterceptor or + StreamStreamClientInterceptor, optional): gRPC interceptors. + http_timeout_seconds (int): specify a timeout for http connections + max_grpc_messsage_length (int, optional): The maximum grpc send and receive + message length in bytes. + """ + super().__init__(address, interceptors, max_grpc_message_length) + self.invocation_client = None + + invocation_protocol = settings.DAPR_API_METHOD_INVOCATION_PROTOCOL.upper() + + if invocation_protocol == 'HTTP': + if http_timeout_seconds is None: + http_timeout_seconds = settings.DAPR_HTTP_TIMEOUT_SECONDS + self.invocation_client = DaprInvocationHttpClient( + headers_callback=headers_callback, timeout=http_timeout_seconds + ) + elif invocation_protocol == 'GRPC': + pass + else: + raise DaprInternalError( + f'Unknown value for DAPR_API_METHOD_INVOCATION_PROTOCOL: {invocation_protocol}' + ) + + async def invoke_method( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage], + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invoke a service method over gRPC or HTTP. + + Args: + app_id (str): Application Id. + method_name (str): Method to be invoked. + data (bytes or str or GrpcMessage, optional): Data for requet's body. + content_type (str, optional): Content type of the data. + metadata (MetadataTuple, optional): Additional metadata or headers. + http_verb (str, optional): HTTP verb for the request. + http_querystring (MetadataTuple, optional): Query parameters. + timeout (int, optional): Request timeout in seconds. + + Returns: + InvokeMethodResponse: the method invocation response. + """ + if self.invocation_client: + return await self.invocation_client.invoke_method_async( + app_id, + method_name, + data, + content_type=content_type, + metadata=metadata, + http_verb=http_verb, + http_querystring=http_querystring, + timeout=timeout, + ) + else: + return await super().invoke_method( + app_id, + method_name, + data, + content_type=content_type, + metadata=metadata, + http_verb=http_verb, + http_querystring=http_querystring, + timeout=timeout, + ) diff --git a/dapr/aio/clients/grpc/__init__.py b/dapr/aio/clients/grpc/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/aio/clients/grpc/__init__.py +++ b/dapr/aio/clients/grpc/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/aio/clients/grpc/_request.py b/dapr/aio/clients/grpc/_request.py index b3c3ce2d4..39ed0175d 100644 --- a/dapr/aio/clients/grpc/_request.py +++ b/dapr/aio/clients/grpc/_request.py @@ -1,116 +1,116 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import io -from typing import Union - -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.clients.grpc._helpers import to_bytes -from dapr.clients.grpc._request import DaprRequest -from dapr.proto import api_v1, common_v1 - - -class EncryptRequestIterator(DaprRequest): - """An asynchronous iterator for cryptography encrypt API requests. - - This reads data from a given stream by chunks and converts it to an asynchronous iterator - of cryptography encrypt API requests. - This iterator will be used for encrypt gRPC bidirectional streaming requests. - """ - - def __init__( - self, - data: Union[str, bytes], - options: EncryptOptions, - ): - """Initialize EncryptRequestIterator with data and encryption options. - - Args: - data (Union[str, bytes]): data to be encrypted - options (EncryptOptions): encryption options - """ - self.data = io.BytesIO(to_bytes(data)) - self.options = options.get_proto() - self.buffer_size = 2 << 10 # 2KiB - self.seq = 0 - - def __aiter__(self): - """Returns the iterator object itself.""" - return self - - async def __anext__(self): - """Read the next chunk of data from the input stream and create a gRPC stream request.""" - # Read data from the input stream, in chunks of up to 2KiB - # Send the data until we reach the end of the input stream - chunk = self.data.read(self.buffer_size) - if not chunk: - raise StopAsyncIteration - - payload = common_v1.StreamPayload(data=chunk, seq=self.seq) - if self.seq == 0: - # If this is the first chunk, add the options - request_proto = api_v1.EncryptRequest(payload=payload, options=self.options) - else: - request_proto = api_v1.EncryptRequest(payload=payload) - - self.seq += 1 - return request_proto - - -class DecryptRequestIterator(DaprRequest): - """An asynchronous iterator for cryptography decrypt API requests. - - This reads data from a given stream by chunks and converts it to an asynchronous iterator - of cryptography decrypt API requests. - This iterator will be used for encrypt gRPC bidirectional streaming requests. - """ - - def __init__( - self, - data: Union[str, bytes], - options: DecryptOptions, - ): - """Initialize DecryptRequestIterator with data and decryption options. - - Args: - data (Union[str, bytes]): data to be decrypted - options (DecryptOptions): decryption options - """ - self.data = io.BytesIO(to_bytes(data)) - self.options = options.get_proto() - self.buffer_size = 2 << 10 # 2KiB - self.seq = 0 - - def __aiter__(self): - """Returns the iterator object itself.""" - return self - - async def __anext__(self): - """Read the next chunk of data from the input stream and create a gRPC stream request.""" - # Read data from the input stream, in chunks of up to 2KiB - # Send the data until we reach the end of the input stream - chunk = self.data.read(self.buffer_size) - if not chunk: - raise StopAsyncIteration - - payload = common_v1.StreamPayload(data=chunk, seq=self.seq) - if self.seq == 0: - # If this is the first chunk, add the options - request_proto = api_v1.DecryptRequest(payload=payload, options=self.options) - else: - request_proto = api_v1.DecryptRequest(payload=payload) - - self.seq += 1 - return request_proto +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import io +from typing import Union + +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.clients.grpc._helpers import to_bytes +from dapr.clients.grpc._request import DaprRequest +from dapr.proto import api_v1, common_v1 + + +class EncryptRequestIterator(DaprRequest): + """An asynchronous iterator for cryptography encrypt API requests. + + This reads data from a given stream by chunks and converts it to an asynchronous iterator + of cryptography encrypt API requests. + This iterator will be used for encrypt gRPC bidirectional streaming requests. + """ + + def __init__( + self, + data: Union[str, bytes], + options: EncryptOptions, + ): + """Initialize EncryptRequestIterator with data and encryption options. + + Args: + data (Union[str, bytes]): data to be encrypted + options (EncryptOptions): encryption options + """ + self.data = io.BytesIO(to_bytes(data)) + self.options = options.get_proto() + self.buffer_size = 2 << 10 # 2KiB + self.seq = 0 + + def __aiter__(self): + """Returns the iterator object itself.""" + return self + + async def __anext__(self): + """Read the next chunk of data from the input stream and create a gRPC stream request.""" + # Read data from the input stream, in chunks of up to 2KiB + # Send the data until we reach the end of the input stream + chunk = self.data.read(self.buffer_size) + if not chunk: + raise StopAsyncIteration + + payload = common_v1.StreamPayload(data=chunk, seq=self.seq) + if self.seq == 0: + # If this is the first chunk, add the options + request_proto = api_v1.EncryptRequest(payload=payload, options=self.options) + else: + request_proto = api_v1.EncryptRequest(payload=payload) + + self.seq += 1 + return request_proto + + +class DecryptRequestIterator(DaprRequest): + """An asynchronous iterator for cryptography decrypt API requests. + + This reads data from a given stream by chunks and converts it to an asynchronous iterator + of cryptography decrypt API requests. + This iterator will be used for encrypt gRPC bidirectional streaming requests. + """ + + def __init__( + self, + data: Union[str, bytes], + options: DecryptOptions, + ): + """Initialize DecryptRequestIterator with data and decryption options. + + Args: + data (Union[str, bytes]): data to be decrypted + options (DecryptOptions): decryption options + """ + self.data = io.BytesIO(to_bytes(data)) + self.options = options.get_proto() + self.buffer_size = 2 << 10 # 2KiB + self.seq = 0 + + def __aiter__(self): + """Returns the iterator object itself.""" + return self + + async def __anext__(self): + """Read the next chunk of data from the input stream and create a gRPC stream request.""" + # Read data from the input stream, in chunks of up to 2KiB + # Send the data until we reach the end of the input stream + chunk = self.data.read(self.buffer_size) + if not chunk: + raise StopAsyncIteration + + payload = common_v1.StreamPayload(data=chunk, seq=self.seq) + if self.seq == 0: + # If this is the first chunk, add the options + request_proto = api_v1.DecryptRequest(payload=payload, options=self.options) + else: + request_proto = api_v1.DecryptRequest(payload=payload) + + self.seq += 1 + return request_proto diff --git a/dapr/aio/clients/grpc/_response.py b/dapr/aio/clients/grpc/_response.py index 480eb7769..4592eeabc 100644 --- a/dapr/aio/clients/grpc/_response.py +++ b/dapr/aio/clients/grpc/_response.py @@ -1,91 +1,91 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import AsyncGenerator, Generic - -from dapr.proto import api_v1 -from dapr.clients.grpc._response import DaprResponse, TCryptoResponse - - -class CryptoResponse(DaprResponse, Generic[TCryptoResponse]): - """An asynchronous iterable of cryptography API responses.""" - - def __init__(self, stream: AsyncGenerator[TCryptoResponse, None]): - """Initialize a CryptoResponse. - - Args: - stream (AsyncGenerator[TCryptoResponse, None, None]): A stream of cryptography API responses. - """ - self._stream = stream - self._buffer = bytearray() - self._expected_seq = 0 - - async def __aiter__(self) -> AsyncGenerator[bytes, None]: - """Read the next chunk of data from the stream. - - Yields: - bytes: The payload data of the next chunk from the stream. - - Raises: - ValueError: If the sequence number of the next chunk is incorrect. - """ - async for chunk in self._stream: - if chunk.payload.seq != self._expected_seq: - raise ValueError('invalid sequence number in chunk') - self._expected_seq += 1 - yield chunk.payload.data - - async def read(self, size: int = -1) -> bytes: - """Read bytes from the stream. - - If size is -1, the entire stream is read and returned as bytes. - Otherwise, up to `size` bytes are read from the stream and returned. - If the stream ends before `size` bytes are available, the remaining - bytes are returned. - - Args: - size (int): The maximum number of bytes to read. If -1 (the default), - read until the end of the stream. - - Returns: - bytes: The bytes read from the stream. - """ - if size == -1: - # Read the entire stream - return b''.join([chunk async for chunk in self]) - - # Read the requested number of bytes - data = bytes(self._buffer) - self._buffer.clear() - - async for chunk in self: - data += chunk - if len(data) >= size: - break - - # Update the buffer - remaining = data[size:] - self._buffer.extend(remaining) - - # Return the requested number of bytes - return data[:size] - - -class EncryptResponse(CryptoResponse[api_v1.EncryptResponse]): - ... - - -class DecryptResponse(CryptoResponse[api_v1.DecryptResponse]): - ... +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import AsyncGenerator, Generic + +from dapr.proto import api_v1 +from dapr.clients.grpc._response import DaprResponse, TCryptoResponse + + +class CryptoResponse(DaprResponse, Generic[TCryptoResponse]): + """An asynchronous iterable of cryptography API responses.""" + + def __init__(self, stream: AsyncGenerator[TCryptoResponse, None]): + """Initialize a CryptoResponse. + + Args: + stream (AsyncGenerator[TCryptoResponse, None, None]): A stream of cryptography API responses. + """ + self._stream = stream + self._buffer = bytearray() + self._expected_seq = 0 + + async def __aiter__(self) -> AsyncGenerator[bytes, None]: + """Read the next chunk of data from the stream. + + Yields: + bytes: The payload data of the next chunk from the stream. + + Raises: + ValueError: If the sequence number of the next chunk is incorrect. + """ + async for chunk in self._stream: + if chunk.payload.seq != self._expected_seq: + raise ValueError('invalid sequence number in chunk') + self._expected_seq += 1 + yield chunk.payload.data + + async def read(self, size: int = -1) -> bytes: + """Read bytes from the stream. + + If size is -1, the entire stream is read and returned as bytes. + Otherwise, up to `size` bytes are read from the stream and returned. + If the stream ends before `size` bytes are available, the remaining + bytes are returned. + + Args: + size (int): The maximum number of bytes to read. If -1 (the default), + read until the end of the stream. + + Returns: + bytes: The bytes read from the stream. + """ + if size == -1: + # Read the entire stream + return b''.join([chunk async for chunk in self]) + + # Read the requested number of bytes + data = bytes(self._buffer) + self._buffer.clear() + + async for chunk in self: + data += chunk + if len(data) >= size: + break + + # Update the buffer + remaining = data[size:] + self._buffer.extend(remaining) + + # Return the requested number of bytes + return data[:size] + + +class EncryptResponse(CryptoResponse[api_v1.EncryptResponse]): + ... + + +class DecryptResponse(CryptoResponse[api_v1.DecryptResponse]): + ... diff --git a/dapr/aio/clients/grpc/client.py b/dapr/aio/clients/grpc/client.py index 2b40101cf..1a8086842 100644 --- a/dapr/aio/clients/grpc/client.py +++ b/dapr/aio/clients/grpc/client.py @@ -1,1827 +1,1827 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import asyncio -import time -import socket -import json -import uuid - -from datetime import datetime -from urllib.parse import urlencode - -from warnings import warn - -from typing import Callable, Dict, Optional, Text, Union, Sequence, List, Any, Awaitable -from typing_extensions import Self - -from google.protobuf.message import Message as GrpcMessage -from google.protobuf.empty_pb2 import Empty as GrpcEmpty - -import grpc.aio # type: ignore -from grpc.aio import ( # type: ignore - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, - AioRpcError, -) - -from dapr.aio.clients.grpc.subscription import Subscription -from dapr.clients.exceptions import DaprInternalError, DaprGrpcError -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.clients.grpc._state import StateOptions, StateItem -from dapr.clients.grpc._helpers import getWorkflowRuntimeStatus -from dapr.clients.health import DaprHealth -from dapr.clients.retry import RetryPolicy -from dapr.common.pubsub.subscription import StreamInactiveError -from dapr.conf.helpers import GrpcEndpoint -from dapr.conf import settings -from dapr.proto import api_v1, api_service_v1, common_v1 -from dapr.proto.runtime.v1.dapr_pb2 import UnsubscribeConfigurationResponse -from dapr.version import __version__ - -from dapr.aio.clients.grpc.interceptors import ( - DaprClientInterceptorAsync, - DaprClientTimeoutInterceptorAsync, -) -from dapr.clients.grpc._helpers import ( - MetadataTuple, - to_bytes, - validateNotNone, - validateNotBlankString, -) -from dapr.aio.clients.grpc._request import ( - EncryptRequestIterator, - DecryptRequestIterator, -) -from dapr.aio.clients.grpc._response import ( - EncryptResponse, - DecryptResponse, -) -from dapr.clients.grpc._request import ( - InvokeMethodRequest, - BindingRequest, - TransactionalStateOperation, -) -from dapr.clients.grpc._response import ( - BindingResponse, - DaprResponse, - GetSecretResponse, - GetBulkSecretResponse, - GetMetadataResponse, - InvokeMethodResponse, - UnlockResponseStatus, - StateResponse, - BulkStatesResponse, - BulkStateItem, - ConfigurationResponse, - QueryResponse, - QueryResponseItem, - RegisteredComponents, - ConfigurationWatcher, - TryLockResponse, - UnlockResponse, - GetWorkflowResponse, - StartWorkflowResponse, - TopicEventResponse, -) - - -class DaprGrpcClientAsync: - """The async convenient layer implementation of Dapr gRPC APIs. - - This provides the wrappers and helpers to allows developers to use Dapr runtime gRPC API - easily and consistently. - - Examples: - - >>> from dapr.aio.clients import DaprClient - >>> d = DaprClient() - >>> resp = await d.invoke_method('callee', 'method', b'data') - - With context manager and custom message size limit: - - >>> from dapr.aio.clients import DaprClient - >>> MAX = 64 * 1024 * 1024 # 64MB - >>> async with DaprClient(max_message_length=MAX) as d: - ... resp = await d.invoke_method('callee', 'method', b'data') - """ - - def __init__( - self, - address: Optional[str] = None, - interceptors: Optional[ - List[ - Union[ - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, - ] - ] - ] = None, - max_grpc_message_length: Optional[int] = None, - retry_policy: Optional[RetryPolicy] = None, - ): - """Connects to Dapr Runtime and initialize gRPC client stub. - - Args: - address (str, optional): Dapr Runtime gRPC endpoint address. - interceptors (list of UnaryUnaryClientInterceptor or - UnaryStreamClientInterceptor or - StreamUnaryClientInterceptor or - StreamStreamClientInterceptor, optional): gRPC interceptors. - max_grpc_message_length (int, optional): The maximum grpc send and receive - message length in bytes. - """ - DaprHealth.wait_until_ready() - self.retry_policy = retry_policy or RetryPolicy() - - useragent = f'dapr-sdk-python/{__version__}' - if not max_grpc_message_length: - options = [ - ('grpc.primary_user_agent', useragent), - ] - else: - options = [ - ('grpc.max_send_message_length', max_grpc_message_length), - ('grpc.max_receive_message_length', max_grpc_message_length), - ('grpc.primary_user_agent', useragent), - ] - - if not address: - address = settings.DAPR_GRPC_ENDPOINT or ( - f'{settings.DAPR_RUNTIME_HOST}:' f'{settings.DAPR_GRPC_PORT}' - ) - - try: - self._uri = GrpcEndpoint(address) - except ValueError as error: - raise DaprInternalError(f'{error}') from error - - # Prepare interceptors - if interceptors is None: - interceptors = [DaprClientTimeoutInterceptorAsync()] - else: - interceptors.append(DaprClientTimeoutInterceptorAsync()) - - if settings.DAPR_API_TOKEN: - api_token_interceptor = DaprClientInterceptorAsync( - [ - ('dapr-api-token', settings.DAPR_API_TOKEN), - ] - ) - interceptors.append(api_token_interceptor) - - # Create gRPC channel - if self._uri.tls: - self._channel = grpc.aio.secure_channel( - self._uri.endpoint, - credentials=self.get_credentials(), - options=options, - interceptors=interceptors, - ) # type: ignore - else: - self._channel = grpc.aio.insecure_channel( - self._uri.endpoint, options, interceptors=interceptors - ) # type: ignore - - self._stub = api_service_v1.DaprStub(self._channel) - - @staticmethod - def get_credentials(): - return grpc.ssl_channel_credentials() - - async def close(self): - """Closes Dapr runtime gRPC channel.""" - if hasattr(self, '_channel') and self._channel: - await self._channel.close() - - async def __aenter__(self) -> Self: # type: ignore - return self - - async def __aexit__(self, exc_type, exc_value, traceback) -> None: - await self.close() - - @staticmethod - def _get_http_extension( - http_verb: str, http_querystring: Optional[MetadataTuple] = None - ) -> common_v1.HTTPExtension: # type: ignore - verb = common_v1.HTTPExtension.Verb.Value(http_verb) # type: ignore - http_ext = common_v1.HTTPExtension(verb=verb) - if http_querystring is not None and len(http_querystring): - http_ext.querystring = urlencode(http_querystring) - return http_ext - - async def invoke_method( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage] = '', - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invokes the target service to call method. - - This can invoke the specified target service to call method with bytes array data or - custom protocol buffer message. If your callee application uses http appcallback, - http_verb and http_querystring must be specified. Otherwise, Dapr runtime will return - error. - - The example calls `callee` service with bytes data, which implements grpc appcallback: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - resp = await d.invoke_method( - app_id='callee', - method_name='method', - data=b'message', - content_type='text/plain', - ) - - # resp.content includes the content in bytes. - # resp.content_type specifies the content type of resp.content. - # Thus, resp.content can be deserialized properly. - - When sending custom protocol buffer message object, it doesn't requires content_type: - - from dapr.aio.clients import DaprClient - - req_data = dapr_example_v1.CustomRequestMessage(data='custom') - - async with DaprClient() as d: - resp = await d.invoke_method( - app_id='callee', - method_name='method', - data=req_data, - ) - # Create protocol buffer object - resp_data = dapr_example_v1.CustomResponseMessage() - # Deserialize to resp_data - resp.unpack(resp_data) - - The example calls `callee` service which implements http appcallback: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - resp = await d.invoke_method( - app_id='callee', - method_name='method', - data=b'message', - content_type='text/plain', - http_verb='POST', - http_querystring=( - ('key1', 'value1') - ), - ) - - # resp.content includes the content in bytes. - # resp.content_type specifies the content type of resp.content. - # Thus, resp.content can be deserialized properly. - - Args: - app_id (str): the callee app id - method (str): the method name which is called - data (bytes or :obj:`google.protobuf.message.Message`, optional): bytes - or Message for data which will be sent to app id - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - http_verb (str, optional): http method verb to call HTTP callee application - http_querystring (tuple, optional): the tuple to represent query string - timeout (int, optional): request timeout in seconds - - Returns: - :class:`InvokeMethodResponse` object returned from callee - """ - warn( - 'invoke_method with protocol gRPC is deprecated. Use gRPC proxying instead.', - DeprecationWarning, - stacklevel=2, - ) - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req_data = InvokeMethodRequest(data, content_type) - http_ext = None - if http_verb: - http_ext = self._get_http_extension(http_verb, http_querystring) - - content_type = '' - if req_data.content_type: - content_type = req_data.content_type - req = api_v1.InvokeServiceRequest( - id=app_id, - message=common_v1.InvokeRequest( - method=method_name, - data=req_data.proto, - content_type=content_type, - http_extension=http_ext, - ), - ) - - call = self._stub.InvokeService(req, metadata=metadata, timeout=timeout) - response = await call - - resp_data = InvokeMethodResponse(response.data, response.content_type) - resp_data.headers = await call.initial_metadata() # type: ignore - return resp_data - - async def invoke_binding( - self, - binding_name: str, - operation: str, - data: Union[bytes, str] = '', - binding_metadata: Dict[str, str] = {}, - metadata: Optional[MetadataTuple] = None, - ) -> BindingResponse: - """Invokes the output binding with the specified operation. - - The data field takes any JSON serializable value and acts as the - payload to be sent to the output binding. The metadata field is an - array of key/value pairs and allows you to set binding specific metadata - for each call. The operation field tells the Dapr binding which operation - it should perform. - - The example calls output `binding` service with bytes data: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - resp = await d.invoke_binding( - binding_name = 'kafkaBinding', - operation = 'create', - data = b'message', - ) - # resp.data includes the response data in bytes. - - Args: - binding_name (str): the name of the binding as defined in the components - operation (str): the operation to perform on the binding - data (bytes or str, optional): bytes or str for data which will sent to the binding - binding_metadata (dict, optional): Dapr metadata for output binding - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`InvokeBindingResponse` object returned from binding - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token ' - 'headers and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req_data = BindingRequest(data, binding_metadata) - - req = api_v1.InvokeBindingRequest( - name=binding_name, - data=req_data.data, - metadata=req_data.binding_metadata, - operation=operation, - ) - - call = self._stub.InvokeBinding(req, metadata=metadata) - response = await call - return BindingResponse( - response.data, dict(response.metadata), await call.initial_metadata() - ) - - async def publish_event( - self, - pubsub_name: str, - topic_name: str, - data: Union[bytes, str], - publish_metadata: Dict[str, str] = {}, - metadata: Optional[MetadataTuple] = None, - data_content_type: Optional[str] = None, - ) -> DaprResponse: - """Publish to a given topic. - This publishes an event with bytes array or str data to a specified topic and - specified pubsub component. The str data is encoded into bytes with default - charset of utf-8. Custom metadata can be passed with the metadata field which - will be passed on a gRPC metadata. - - The example publishes a byte array event to a topic: - - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.publish_event( - pubsub_name='pubsub_1', - topic_name='TOPIC_A', - data=b'message', - publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, - ) - # resp.headers includes the gRPC initial metadata. - - Args: - pubsub_name (str): the name of the pubsub component - topic_name (str): the topic name to publish to - data (bytes or str): bytes or str for data - publish_metadata (Dict[str, str], optional): Dapr metadata per Pub/Sub message - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - data_content_type: (str, optional): content type of the data payload - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not isinstance(data, bytes) and not isinstance(data, str): - raise ValueError(f'invalid type for data {type(data)}') - - req_data: bytes - if isinstance(data, bytes): - req_data = data - else: - if isinstance(data, str): - req_data = data.encode('utf-8') - - content_type = '' - if data_content_type: - content_type = data_content_type - req = api_v1.PublishEventRequest( - pubsub_name=pubsub_name, - topic=topic_name, - data=req_data, - data_content_type=content_type, - metadata=publish_metadata, - ) - - try: - call = self._stub.PublishEvent(req, metadata=metadata) - # response is google.protobuf.Empty - await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - return DaprResponse(await call.initial_metadata()) - - async def subscribe( - self, - pubsub_name: str, - topic: str, - metadata: Optional[dict] = None, - dead_letter_topic: Optional[str] = None, - ) -> Subscription: - """ - Subscribe to a topic with a bidirectional stream - - Args: - pubsub_name (str): The name of the pubsub component. - topic (str): The name of the topic. - metadata (Optional[dict]): Additional metadata for the subscription. - dead_letter_topic (Optional[str]): Name of the dead-letter topic. - - Returns: - Subscription: The Subscription object managing the stream. - """ - subscription = Subscription(self._stub, pubsub_name, topic, metadata, dead_letter_topic) - await subscription.start() - return subscription - - async def subscribe_with_handler( - self, - pubsub_name: str, - topic: str, - handler_fn: Callable[..., TopicEventResponse], - metadata: Optional[dict] = None, - dead_letter_topic: Optional[str] = None, - ) -> Callable[[], Awaitable[None]]: - """ - Subscribe to a topic with a bidirectional stream and a message handler function - - Args: - pubsub_name (str): The name of the pubsub component. - topic (str): The name of the topic. - handler_fn (Callable[..., TopicEventResponse]): The function to call when a message is received. - metadata (Optional[dict]): Additional metadata for the subscription. - dead_letter_topic (Optional[str]): Name of the dead-letter topic. - - Returns: - Callable[[], Awaitable[None]]: An async function to close the subscription. - """ - subscription = await self.subscribe(pubsub_name, topic, metadata, dead_letter_topic) - - async def stream_messages(sub: Subscription): - while True: - try: - message = await sub.next_message() - if message: - response = await handler_fn(message) - if response: - await subscription.respond(message, response.status) - else: - continue - except StreamInactiveError: - break - - async def close_subscription(): - await subscription.close() - - asyncio.create_task(stream_messages(subscription)) - - return close_subscription - - async def get_state( - self, - store_name: str, - key: str, - state_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> StateResponse: - """Gets value from a statestore with a key - - The example gets value from a statestore: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.get_state( - store_name='state_store' - key='key_1', - state={"key": "value"}, - state_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to get from - key (str): the key of the key-value pair to be gotten - state_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`StateResponse` gRPC metadata returned from callee - and value obtained from the state store - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - req = api_v1.GetStateRequest(store_name=store_name, key=key, metadata=state_metadata) - - try: - call = self._stub.GetState(req, metadata=metadata) - response = await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - return StateResponse( - data=response.data, etag=response.etag, headers=await call.initial_metadata() - ) - - async def get_bulk_state( - self, - store_name: str, - keys: Sequence[str], - parallelism: int = 1, - states_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> BulkStatesResponse: - """Gets values from a statestore with keys - - The example gets value from a statestore: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.get_bulk_state( - store_name='state_store', - keys=['key_1', key_2], - parallelism=2, - states_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to get from - keys (Sequence[str]): the keys to be retrieved - parallelism (int): number of items to be retrieved in parallel - states_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`BulkStatesResponse` gRPC metadata returned from callee - and value obtained from the state store - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req = api_v1.GetBulkStateRequest( - store_name=store_name, keys=keys, parallelism=parallelism, metadata=states_metadata - ) - - try: - call = self._stub.GetBulkState(req, metadata=metadata) - response = await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - items = [] - for item in response.items: - items.append( - BulkStateItem(key=item.key, data=item.data, etag=item.etag, error=item.error) - ) - return BulkStatesResponse(items=items, headers=await call.initial_metadata()) - - async def query_state( - self, store_name: str, query: str, states_metadata: Optional[Dict[str, str]] = dict() - ) -> QueryResponse: - """Queries a statestore with a query - - For details on supported queries see https://docs.dapr.io/ - - This example queries a statestore: - from dapr.aio.clients import DaprClient - - query = ''' - { - "filter": { - "EQ": { "state": "CA" } - }, - "sort": [ - { - "key": "person.id", - "order": "DESC" - } - ] - } - ''' - - async with DaprClient() as d: - resp = await d.query_state( - store_name='state_store', - query=query, - states_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to query - query (str): the query to be executed - states_metadata (Dict[str, str], optional): custom metadata for state request - - Returns: - :class:`QueryStateResponse` gRPC metadata returned from callee, - pagination token and results of the query - """ - warn( - 'The State Store Query API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req = api_v1.QueryStateRequest(store_name=store_name, query=query, metadata=states_metadata) - - try: - call = self._stub.QueryStateAlpha1(req) - response = await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - results = [] - for item in response.results: - results.append( - QueryResponseItem(key=item.key, value=item.data, etag=item.etag, error=item.error) - ) - - return QueryResponse( - token=response.token, - results=results, - metadata=response.metadata, - headers=await call.initial_metadata(), - ) - - async def save_state( - self, - store_name: str, - key: str, - value: Union[bytes, str], - etag: Optional[str] = None, - options: Optional[StateOptions] = None, - state_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> DaprResponse: - """Saves key-value pairs to a statestore - - This saves a value to the statestore with a given key and state store name. - Options for request can be passed with the options field and custom - metadata can be passed with metadata field. - - The example saves states to a statestore: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.save_state( - store_name='state_store', - key='key1', - value='value1', - etag='etag', - state_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to save to - key (str): the key to be saved - value (bytes or str): the value to be saved - etag (str, optional): the etag to save with - options (StateOptions, optional): custom options - for concurrency and consistency - state_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - Raises: - ValueError: value is not bytes or str - ValueError: store_name is empty - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not isinstance(value, (bytes, str)): - raise ValueError(f'invalid type for data {type(value)}') - - req_value = value - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - if options is None: - state_options = None - else: - state_options = options.get_proto() - - state = common_v1.StateItem( - key=key, - value=to_bytes(req_value), - etag=common_v1.Etag(value=etag) if etag is not None else None, - options=state_options, - metadata=state_metadata, - ) - - req = api_v1.SaveStateRequest(store_name=store_name, states=[state]) - try: - result, call = await self.retry_policy.run_rpc_async( - self._stub.SaveState, req, metadata=metadata - ) - return DaprResponse(headers=await call.initial_metadata()) - except AioRpcError as e: - raise DaprInternalError(e.details()) from e - - async def save_bulk_state( - self, store_name: str, states: List[StateItem], metadata: Optional[MetadataTuple] = None - ) -> DaprResponse: - """Saves state items to a statestore - - This saves a given state item into the statestore specified by store_name. - - The example saves states to a statestore: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.save_bulk_state( - store_name='state_store', - states=[StateItem(key='key1', value='value1'), - StateItem(key='key2', value='value2', etag='etag'),], - ) - - Args: - store_name (str): the state store name to save to - states (List[StateItem]): list of states to save - metadata (tuple, optional): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - Raises: - ValueError: states is empty - ValueError: store_name is empty - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not states or len(states) == 0: - raise ValueError('States to be saved cannot be empty') - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - req_states = [ - common_v1.StateItem( - key=i.key, - value=to_bytes(i.value), - etag=common_v1.Etag(value=i.etag) if i.etag is not None else None, - options=i.options, - metadata=i.metadata, - ) - for i in states - ] - - req = api_v1.SaveStateRequest(store_name=store_name, states=req_states) - - try: - call = self._stub.SaveState(req, metadata=metadata) - await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - return DaprResponse(headers=await call.initial_metadata()) - - async def execute_state_transaction( - self, - store_name: str, - operations: Sequence[TransactionalStateOperation], - transactional_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> DaprResponse: - """Saves or deletes key-value pairs to a statestore as a transaction - - This saves or deletes key-values to the statestore as part of a single transaction, - transaction_metadata is used for the transaction operation, while metadata is used - for the GRPC call. - - The example saves states to a statestore: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.execute_state_transaction( - store_name='state_store', - operations=[ - TransactionalStateOperation(key=key, data=value), - TransactionalStateOperation(key=another_key, data=another_value), - TransactionalStateOperation( - operation_type=TransactionOperationType.delete, - key=key_to_delete), - ], - transactional_metadata={"header1": "value1"}, - ) - - Args: - store_name (str): the state store name to save to - operations (Sequence[TransactionalStateOperation]): the transaction operations - transactional_metadata (Dict[str, str], optional): Dapr metadata for transaction - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req_ops = [ - api_v1.TransactionalStateOperation( - operationType=o.operation_type.value, - request=common_v1.StateItem( - key=o.key, - value=to_bytes(o.data), - etag=common_v1.Etag(value=o.etag) if o.etag is not None else None, - ), - ) - for o in operations - ] - - req = api_v1.ExecuteStateTransactionRequest( - storeName=store_name, operations=req_ops, metadata=transactional_metadata - ) - - try: - call = self._stub.ExecuteStateTransaction(req, metadata=metadata) - await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - return DaprResponse(headers=await call.initial_metadata()) - - async def delete_state( - self, - store_name: str, - key: str, - etag: Optional[str] = None, - options: Optional[StateOptions] = None, - state_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> DaprResponse: - """Deletes key-value pairs from a statestore - - This deletes a value from the statestore with a given key and state store name. - Options for request can be passed with the options field and custom - metadata can be passed with metadata field. - - The example deletes states from a statestore: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.delete_state( - store_name='state_store', - key='key1', - etag='etag', - state_metadata={"header1": "value1"}, - ) - - Args: - store_name (str): the state store name to delete from - key (str): the key of the key-value pair to delete - etag (str, optional): the etag to delete with - options (StateOptions, optional): custom options - for concurrency and consistency - state_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - if options is None: - state_options = None - else: - state_options = options.get_proto() - - etag_object = common_v1.Etag(value=etag) if etag is not None else None - req = api_v1.DeleteStateRequest( - store_name=store_name, - key=key, - etag=etag_object, - options=state_options, - metadata=state_metadata, - ) - - try: - call = self._stub.DeleteState(req, metadata=metadata) - await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - return DaprResponse(headers=await call.initial_metadata()) - - async def get_secret( - self, - store_name: str, - key: str, - secret_metadata: Optional[Dict[str, str]] = {}, - metadata: Optional[MetadataTuple] = None, - ) -> GetSecretResponse: - """Get secret with a given key. - - This gets a secret from secret store with a given key and secret store name. - Metadata for request can be passed with the secret_metadata field and custom - metadata can be passed with metadata field. - - - The example gets a secret from secret store: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - resp = await d.get_secret( - store_name='secretstoreA', - key='keyA', - secret_metadata={'header1', 'value1'} - ) - - # resp.headers includes the gRPC initial metadata. - # resp.trailers includes that gRPC trailing metadata. - - Args: - store_name (str): store name to get secret from - key (str): str for key - secret_metadata (Dict[str, str], Optional): Dapr metadata for secrets request - metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`GetSecretResponse` object with the secret and metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req = api_v1.GetSecretRequest(store_name=store_name, key=key, metadata=secret_metadata) - - call = self._stub.GetSecret(req, metadata=metadata) - response = await call - - return GetSecretResponse(secret=response.data, headers=await call.initial_metadata()) - - async def get_bulk_secret( - self, - store_name: str, - secret_metadata: Optional[Dict[str, str]] = {}, - metadata: Optional[MetadataTuple] = None, - ) -> GetBulkSecretResponse: - """Get all granted secrets. - - This gets all granted secrets from secret store. - Metadata for request can be passed with the secret_metadata field. - - - The example gets all secrets from secret store: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - resp = await d.get_bulk_secret( - store_name='secretstoreA', - secret_metadata={'header1', 'value1'} - ) - - # resp.headers includes the gRPC initial metadata. - # resp.trailers includes that gRPC trailing metadata. - - Args: - store_name (str): store name to get secret from - secret_metadata (Dict[str, Dict[str, str]], Optional): Dapr metadata of secrets request - metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`GetBulkSecretResponse` object with secrets and metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req = api_v1.GetBulkSecretRequest(store_name=store_name, metadata=secret_metadata) - - call = self._stub.GetBulkSecret(req, metadata=metadata) - response = await call - - secrets_map = {} - for key in response.data.keys(): - secret_response = response.data[key] - secrets_submap = {} - for subkey in secret_response.secrets.keys(): - secrets_submap[subkey] = secret_response.secrets[subkey] - secrets_map[key] = secrets_submap - - return GetBulkSecretResponse(secrets=secrets_map, headers=await call.initial_metadata()) - - async def get_configuration( - self, store_name: str, keys: List[str], config_metadata: Optional[Dict[str, str]] = dict() - ) -> ConfigurationResponse: - """Gets values from a config store with keys - - The example gets value from a config store: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.get_configuration( - store_name='state_store' - keys=['key_1'], - config_metadata={"metakey": "metavalue"} - ) - - Args: - store_name (str): the state store name to get from - keys (List[str]): the keys of the key-value pairs to be gotten - config_metadata (Dict[str, str], optional): Dapr metadata for configuration - - Returns: - :class:`ConfigurationResponse` gRPC metadata returned from callee - and value obtained from the config store - """ - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('Config store name cannot be empty to get the configuration') - - req = api_v1.GetConfigurationRequest( - store_name=store_name, keys=keys, metadata=config_metadata - ) - call = self._stub.GetConfiguration(req) - response: api_v1.GetConfigurationResponse = await call - return ConfigurationResponse(items=response.items, headers=await call.initial_metadata()) - - async def subscribe_configuration( - self, - store_name: str, - keys: List[str], - handler: Callable[[Text, ConfigurationResponse], None], - config_metadata: Optional[Dict[str, str]] = dict(), - ) -> Text: - """Gets changed value from a config store with a key - - The example gets value from a config store: - from dapr.aio.clients import DaprClient - async with DaprClient() as d: - resp = await d.subscribe_configuration( - store_name='state_store' - keys=['key_1'], - handler=handler, - config_metadata={"metakey": "metavalue"} - ) - - Args: - store_name (str): the state store name to get from - keys (str array): the keys of the key-value pairs to be gotten - handler(func (key, ConfigurationResponse)): the callback function to be called - config_metadata (Dict[str, str], optional): Dapr metadata for configuration - - Returns: - id (str): subscription id, which can be used to unsubscribe later - """ - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('Config store name cannot be empty to get the configuration') - - configWatcher = ConfigurationWatcher() - id = configWatcher.watch_configuration( - self._stub, store_name, keys, handler, config_metadata - ) - return id - - async def unsubscribe_configuration(self, store_name: str, id: str) -> bool: - """Unsubscribes from configuration changes. - - Args: - store_name (str): the state store name to unsubscribe from - id (str): the subscription id to unsubscribe - - Returns: - bool: True if unsubscribed successfully, False otherwise - """ - req = api_v1.UnsubscribeConfigurationRequest(store_name=store_name, id=id) - response: UnsubscribeConfigurationResponse = await self._stub.UnsubscribeConfiguration(req) - return response.ok - - async def try_lock( - self, store_name: str, resource_id: str, lock_owner: str, expiry_in_seconds: int - ) -> TryLockResponse: - """Tries to get a lock with an expiry. - - You can use the result of this operation directly on an `if` statement: - - if client.try_lock(store_name, resource_id, first_client_id, expiry_s): - # lock acquired successfully... - - You can also inspect the response's `success` attribute: - - response = client.try_lock(store_name, resource_id, first_client_id, expiry_s) - if response.success: - # lock acquired successfully... - - Finally, you can use this response with a `with` statement, and have the lock - be automatically unlocked after the with-statement scope ends - - with client.try_lock(store_name, resource_id, first_client_id, expiry_s) as lock: - if lock: - # lock acquired successfully... - # Lock automatically unlocked at this point, no need to call client->unlock(...) - - Args: - store_name (str): the lock store name, e.g. `redis`. - resource_id (str): the lock key. e.g. `order_id_111`. - It stands for "which resource I want to protect". - lock_owner (str): indicates the identifier of lock owner. - expiry_in_seconds (int): The length of time (in seconds) for which this lock - will be held and after which it expires. - - Returns: - :class:`TryLockResponse`: With the result of the try-lock operation. - """ - # Warnings and input validation - warn( - 'The Distributed Lock API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - store_name=store_name, resource_id=resource_id, lock_owner=lock_owner - ) - if not expiry_in_seconds or expiry_in_seconds < 1: - raise ValueError('expiry_in_seconds must be a positive number') - # Actual tryLock invocation - req = api_v1.TryLockRequest( - store_name=store_name, - resource_id=resource_id, - lock_owner=lock_owner, - expiry_in_seconds=expiry_in_seconds, - ) - call = self._stub.TryLockAlpha1(req) - response = await call - return TryLockResponse( - success=response.success, - client=self, - store_name=store_name, - resource_id=resource_id, - lock_owner=lock_owner, - headers=await call.initial_metadata(), - ) - - async def unlock(self, store_name: str, resource_id: str, lock_owner: str) -> UnlockResponse: - """Unlocks a lock. - - Args: - store_name (str): the lock store name, e.g. `redis`. - resource_id (str): the lock key. e.g. `order_id_111`. - It stands for "which resource I want to protect". - lock_owner (str): indicates the identifier of lock owner. - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`UnlockResponseStatus`: Status of the request, - `UnlockResponseStatus.success` if it was successful of some other - status otherwise. - """ - # Warnings and input validation - warn( - 'The Distributed Lock API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - store_name=store_name, resource_id=resource_id, lock_owner=lock_owner - ) - # Actual unlocking invocation - req = api_v1.UnlockRequest( - store_name=store_name, resource_id=resource_id, lock_owner=lock_owner - ) - call = self._stub.UnlockAlpha1(req) - response = await call - - return UnlockResponse( - status=UnlockResponseStatus(response.status), headers=await call.initial_metadata() - ) - - async def encrypt(self, data: Union[str, bytes], options: EncryptOptions): - """Encrypt a stream data with given options. - - The encrypt API encrypts a stream data with the given options. - - Example: - from dapr.aio.clients import DaprClient - from dapr.clients.grpc._crypto import EncryptOptions - - async with DaprClient() as d: - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = await d.encrypt( - data='hello dapr', - options=options, - ) - encrypted_data = await resp.read() - - Args: - data (Union[str, bytes]): Data to be encrypted. - options (EncryptOptions): Encryption options. - - Returns: - Readable stream of `api_v1.EncryptResponse`. - - Raises: - ValueError: If component_name, key_name, or key_wrap_algorithm is empty. - """ - # Warnings and input validation - warn( - 'The Encrypt API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - component_name=options.component_name, - key_name=options.key_name, - key_wrap_algorithm=options.key_wrap_algorithm, - ) - - req_iterator = EncryptRequestIterator(data, options) - resp_stream = self._stub.EncryptAlpha1(req_iterator) - return EncryptResponse(resp_stream) - - async def decrypt(self, data: Union[str, bytes], options: DecryptOptions): - """Decrypt a stream data with given options. - - The decrypt API decrypts a stream data with the given options. - - Example: - from dapr.aio.clients import DaprClient - from dapr.clients.grpc._crypto import DecryptOptions - - async with DaprClient() as d: - options = DecryptOptions( - component_name='crypto_component', - key_name='crypto_key', - ) - resp = await d.decrypt( - data='hello dapr', - options=options, - ) - decrypted_data = await resp.read() - - Args: - data (Union[str, bytes]): Data to be decrypted. - options (DecryptOptions): Decryption options. - - Returns: - Readable stream of `api_v1.DecryptResponse`. - - Raises: - ValueError: If component_name is empty. - """ - # Warnings and input validation - warn( - 'The Decrypt API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - component_name=options.component_name, - ) - - req_iterator = DecryptRequestIterator(data, options) - resp_stream = self._stub.DecryptAlpha1(req_iterator) - return DecryptResponse(resp_stream) - - async def start_workflow( - self, - workflow_component: str, - workflow_name: str, - input: Optional[Union[Any, bytes]] = None, - instance_id: Optional[str] = None, - workflow_options: Optional[Dict[str, str]] = dict(), - send_raw_bytes: bool = False, - ) -> StartWorkflowResponse: - """Starts a workflow. - - Args: - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - workflow_name (str): the name of the workflow that will be executed. - input (Optional[Union[Any, bytes]]): the input that the workflow will receive. - The input value will be serialized to JSON - by default. Use the send_raw_bytes param - to send unencoded binary input. - instance_id (Optional[str]): the name of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_options (Optional[Dict[str, str]]): the key-value options - that the workflow will receive. - send_raw_bytes (bool) if true, no serialization will be performed on the input - bytes - - Returns: - :class:`StartWorkflowResponse`: Instance ID associated with the started workflow - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - instance_id=instance_id, - workflow_component=workflow_component, - workflow_name=workflow_name, - ) - - if instance_id is None: - instance_id = str(uuid.uuid4()) - - if isinstance(input, bytes) and send_raw_bytes: - encoded_data = input - else: - try: - encoded_data = json.dumps(input).encode('utf-8') if input is not None else bytes([]) - except TypeError: - raise DaprInternalError('start_workflow: input data must be JSON serializable') - except ValueError as e: - raise DaprInternalError(f'start_workflow JSON serialization error: {e}') - - # Actual start workflow invocation - req = api_v1.StartWorkflowRequest( - instance_id=instance_id, - workflow_component=workflow_component, - workflow_name=workflow_name, - options=workflow_options, - input=encoded_data, - ) - - try: - response = self._stub.StartWorkflowBeta1(req) - return StartWorkflowResponse(instance_id=response.instance_id) - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def get_workflow(self, instance_id: str, workflow_component: str) -> GetWorkflowResponse: - """Gets information on a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`GetWorkflowResponse`: Instance ID associated with the started workflow - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual get workflow invocation - req = api_v1.GetWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - resp = self._stub.GetWorkflowBeta1(req) - if resp.created_at is None: - resp.created_at = datetime.now - if resp.last_updated_at is None: - resp.last_updated_at = datetime.now - return GetWorkflowResponse( - instance_id=instance_id, - workflow_name=resp.workflow_name, - created_at=resp.created_at, - last_updated_at=resp.last_updated_at, - runtime_status=getWorkflowRuntimeStatus(resp.runtime_status), - properties=resp.properties, - ) - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def terminate_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Terminates a workflow. - - Args: - instance_id (str): the ID of the workflow instance, e.g. - `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual terminate workflow invocation - req = api_v1.TerminateWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self._stub.TerminateWorkflowBeta1.with_call(req) - return DaprResponse(headers=call.initial_metadata()) - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def raise_workflow_event( - self, - instance_id: str, - workflow_component: str, - event_name: str, - event_data: Optional[Union[Any, bytes]] = None, - send_raw_bytes: bool = False, - ) -> DaprResponse: - """Raises an event on a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - event_name (str): the name of the event to be raised on - the workflow. - event_data (Optional[Union[Any, bytes]]): the input that the workflow will receive. - The input value will be serialized to JSON - by default. Use the send_raw_bytes param - to send unencoded binary input. - send_raw_bytes (bool) if true, no serialization will be performed on the input - bytes - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - instance_id=instance_id, workflow_component=workflow_component, event_name=event_name - ) - if isinstance(event_data, bytes) and send_raw_bytes: - encoded_data = event_data - else: - if event_data is not None: - try: - encoded_data = ( - json.dumps(event_data).encode('utf-8') - if event_data is not None - else bytes([]) - ) - except TypeError: - raise DaprInternalError( - 'raise_workflow_event:\ - event_data must be JSON serializable' - ) - except ValueError as e: - raise DaprInternalError(f'raise_workflow_event JSON serialization error: {e}') - encoded_data = json.dumps(event_data).encode('utf-8') - else: - encoded_data = bytes([]) - # Actual workflow raise event invocation - req = api_v1.raise_workflow_event( - instance_id=instance_id, - workflow_component=workflow_component, - event_name=event_name, - event_data=encoded_data, - ) - - try: - _, call = self._stub.RaiseEventWorkflowBeta1.with_call(req) - return DaprResponse(headers=call.initial_metadata()) - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def pause_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Pause a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual pause workflow invocation - req = api_v1.PauseWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self._stub.PauseWorkflowBeta1.with_call(req) - - return DaprResponse(headers=call.initial_metadata()) - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def resume_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Resumes a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual resume workflow invocation - req = api_v1.ResumeWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self._stub.ResumeWorkflowBeta1.with_call(req) - - return DaprResponse(headers=call.initial_metadata()) - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def purge_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Purges a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual purge workflow invocation - req = api_v1.PurgeWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self._stub.PurgeWorkflowBeta1.with_call(req) - - return DaprResponse(headers=call.initial_metadata()) - - except grpc.aio.AioRpcError as err: - raise DaprInternalError(err.details()) - - async def wait(self, timeout_s: float): - """Waits for sidecar to be available within the timeout. - - It checks if sidecar socket is available within the given timeout. - - The example gets a secret from secret store: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - await d.wait(1) # waits for 1 second. - # Sidecar is available after this. - - Args: - timeout_s (float): timeout in seconds - """ - warn( - 'The wait method is deprecated. A health check is now done automatically on client ' - 'initialization.', - DeprecationWarning, - stacklevel=2, - ) - - start = time.time() - while True: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(timeout_s) - try: - s.connect((self._uri.hostname, self._uri.port_as_int)) - return - except Exception as e: - remaining = (start + timeout_s) - time.time() - if remaining < 0: - raise e - asyncio.sleep(min(1, remaining)) - - async def get_metadata(self) -> GetMetadataResponse: - """Returns information about the sidecar allowing for runtime - discoverability. - - The metadata API returns a list of the components loaded, - the activated actors (if present) and attributes with information - attached. - - Each loaded component provides its name, type and version and also - information about supported features in the form of component - capabilities. - """ - - try: - call = self._stub.GetMetadata(GrpcEmpty()) - _resp = await call - except AioRpcError as err: - raise DaprGrpcError(err) from err - - response: api_v1.GetMetadataResponse = _resp # type alias - # Convert to more pythonic formats - active_actors_count = { - type_count.type: type_count.count for type_count in response.active_actors_count - } - registered_components = [ - RegisteredComponents( - name=i.name, type=i.type, version=i.version, capabilities=i.capabilities - ) - for i in response.registered_components - ] - extended_metadata = dict(response.extended_metadata.items()) - - return GetMetadataResponse( - application_id=response.id, - active_actors_count=active_actors_count, - registered_components=registered_components, - extended_metadata=extended_metadata, - headers=await call.initial_metadata(), - ) - - async def set_metadata(self, attributeName: str, attributeValue: str) -> DaprResponse: - """Adds a custom (extended) metadata attribute to the Dapr sidecar - information stored by the Metadata endpoint. - - The metadata API allows you to store additional attribute information - in the format of key-value pairs. These are ephemeral in-memory and - are not persisted if a sidecar is reloaded. This information should - be added at the time of a sidecar creation, for example, after the - application has started. - - Args: - attributeName (str): Custom attribute name. This is they key name - in the key-value pair. - attributeValue (str): Custom attribute value we want to store. - """ - # input validation - validateNotBlankString(attributeName=attributeName) - # Type-checking should catch this but better safe at run-time than sorry. - validateNotNone(attributeValue=attributeValue) - # Actual invocation - req = api_v1.SetMetadataRequest(key=attributeName, value=attributeValue) - call = self._stub.SetMetadata(req) - await call - - return DaprResponse(await call.initial_metadata()) - - async def shutdown(self) -> DaprResponse: - """Shutdown the sidecar. - - This will ask the sidecar to gracefully shutdown. - - The example shutdown the sidecar: - - from dapr.aio.clients import DaprClient - - async with DaprClient() as d: - resp = await d.shutdown() - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - - call = self._stub.Shutdown(GrpcEmpty()) - await call - - return DaprResponse(await call.initial_metadata()) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import asyncio +import time +import socket +import json +import uuid + +from datetime import datetime +from urllib.parse import urlencode + +from warnings import warn + +from typing import Callable, Dict, Optional, Text, Union, Sequence, List, Any, Awaitable +from typing_extensions import Self + +from google.protobuf.message import Message as GrpcMessage +from google.protobuf.empty_pb2 import Empty as GrpcEmpty + +import grpc.aio # type: ignore +from grpc.aio import ( # type: ignore + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, + AioRpcError, +) + +from dapr.aio.clients.grpc.subscription import Subscription +from dapr.clients.exceptions import DaprInternalError, DaprGrpcError +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.clients.grpc._state import StateOptions, StateItem +from dapr.clients.grpc._helpers import getWorkflowRuntimeStatus +from dapr.clients.health import DaprHealth +from dapr.clients.retry import RetryPolicy +from dapr.common.pubsub.subscription import StreamInactiveError +from dapr.conf.helpers import GrpcEndpoint +from dapr.conf import settings +from dapr.proto import api_v1, api_service_v1, common_v1 +from dapr.proto.runtime.v1.dapr_pb2 import UnsubscribeConfigurationResponse +from dapr.version import __version__ + +from dapr.aio.clients.grpc.interceptors import ( + DaprClientInterceptorAsync, + DaprClientTimeoutInterceptorAsync, +) +from dapr.clients.grpc._helpers import ( + MetadataTuple, + to_bytes, + validateNotNone, + validateNotBlankString, +) +from dapr.aio.clients.grpc._request import ( + EncryptRequestIterator, + DecryptRequestIterator, +) +from dapr.aio.clients.grpc._response import ( + EncryptResponse, + DecryptResponse, +) +from dapr.clients.grpc._request import ( + InvokeMethodRequest, + BindingRequest, + TransactionalStateOperation, +) +from dapr.clients.grpc._response import ( + BindingResponse, + DaprResponse, + GetSecretResponse, + GetBulkSecretResponse, + GetMetadataResponse, + InvokeMethodResponse, + UnlockResponseStatus, + StateResponse, + BulkStatesResponse, + BulkStateItem, + ConfigurationResponse, + QueryResponse, + QueryResponseItem, + RegisteredComponents, + ConfigurationWatcher, + TryLockResponse, + UnlockResponse, + GetWorkflowResponse, + StartWorkflowResponse, + TopicEventResponse, +) + + +class DaprGrpcClientAsync: + """The async convenient layer implementation of Dapr gRPC APIs. + + This provides the wrappers and helpers to allows developers to use Dapr runtime gRPC API + easily and consistently. + + Examples: + + >>> from dapr.aio.clients import DaprClient + >>> d = DaprClient() + >>> resp = await d.invoke_method('callee', 'method', b'data') + + With context manager and custom message size limit: + + >>> from dapr.aio.clients import DaprClient + >>> MAX = 64 * 1024 * 1024 # 64MB + >>> async with DaprClient(max_message_length=MAX) as d: + ... resp = await d.invoke_method('callee', 'method', b'data') + """ + + def __init__( + self, + address: Optional[str] = None, + interceptors: Optional[ + List[ + Union[ + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, + ] + ] + ] = None, + max_grpc_message_length: Optional[int] = None, + retry_policy: Optional[RetryPolicy] = None, + ): + """Connects to Dapr Runtime and initialize gRPC client stub. + + Args: + address (str, optional): Dapr Runtime gRPC endpoint address. + interceptors (list of UnaryUnaryClientInterceptor or + UnaryStreamClientInterceptor or + StreamUnaryClientInterceptor or + StreamStreamClientInterceptor, optional): gRPC interceptors. + max_grpc_message_length (int, optional): The maximum grpc send and receive + message length in bytes. + """ + DaprHealth.wait_until_ready() + self.retry_policy = retry_policy or RetryPolicy() + + useragent = f'dapr-sdk-python/{__version__}' + if not max_grpc_message_length: + options = [ + ('grpc.primary_user_agent', useragent), + ] + else: + options = [ + ('grpc.max_send_message_length', max_grpc_message_length), + ('grpc.max_receive_message_length', max_grpc_message_length), + ('grpc.primary_user_agent', useragent), + ] + + if not address: + address = settings.DAPR_GRPC_ENDPOINT or ( + f'{settings.DAPR_RUNTIME_HOST}:' f'{settings.DAPR_GRPC_PORT}' + ) + + try: + self._uri = GrpcEndpoint(address) + except ValueError as error: + raise DaprInternalError(f'{error}') from error + + # Prepare interceptors + if interceptors is None: + interceptors = [DaprClientTimeoutInterceptorAsync()] + else: + interceptors.append(DaprClientTimeoutInterceptorAsync()) + + if settings.DAPR_API_TOKEN: + api_token_interceptor = DaprClientInterceptorAsync( + [ + ('dapr-api-token', settings.DAPR_API_TOKEN), + ] + ) + interceptors.append(api_token_interceptor) + + # Create gRPC channel + if self._uri.tls: + self._channel = grpc.aio.secure_channel( + self._uri.endpoint, + credentials=self.get_credentials(), + options=options, + interceptors=interceptors, + ) # type: ignore + else: + self._channel = grpc.aio.insecure_channel( + self._uri.endpoint, options, interceptors=interceptors + ) # type: ignore + + self._stub = api_service_v1.DaprStub(self._channel) + + @staticmethod + def get_credentials(): + return grpc.ssl_channel_credentials() + + async def close(self): + """Closes Dapr runtime gRPC channel.""" + if hasattr(self, '_channel') and self._channel: + await self._channel.close() + + async def __aenter__(self) -> Self: # type: ignore + return self + + async def __aexit__(self, exc_type, exc_value, traceback) -> None: + await self.close() + + @staticmethod + def _get_http_extension( + http_verb: str, http_querystring: Optional[MetadataTuple] = None + ) -> common_v1.HTTPExtension: # type: ignore + verb = common_v1.HTTPExtension.Verb.Value(http_verb) # type: ignore + http_ext = common_v1.HTTPExtension(verb=verb) + if http_querystring is not None and len(http_querystring): + http_ext.querystring = urlencode(http_querystring) + return http_ext + + async def invoke_method( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage] = '', + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invokes the target service to call method. + + This can invoke the specified target service to call method with bytes array data or + custom protocol buffer message. If your callee application uses http appcallback, + http_verb and http_querystring must be specified. Otherwise, Dapr runtime will return + error. + + The example calls `callee` service with bytes data, which implements grpc appcallback: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + resp = await d.invoke_method( + app_id='callee', + method_name='method', + data=b'message', + content_type='text/plain', + ) + + # resp.content includes the content in bytes. + # resp.content_type specifies the content type of resp.content. + # Thus, resp.content can be deserialized properly. + + When sending custom protocol buffer message object, it doesn't requires content_type: + + from dapr.aio.clients import DaprClient + + req_data = dapr_example_v1.CustomRequestMessage(data='custom') + + async with DaprClient() as d: + resp = await d.invoke_method( + app_id='callee', + method_name='method', + data=req_data, + ) + # Create protocol buffer object + resp_data = dapr_example_v1.CustomResponseMessage() + # Deserialize to resp_data + resp.unpack(resp_data) + + The example calls `callee` service which implements http appcallback: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + resp = await d.invoke_method( + app_id='callee', + method_name='method', + data=b'message', + content_type='text/plain', + http_verb='POST', + http_querystring=( + ('key1', 'value1') + ), + ) + + # resp.content includes the content in bytes. + # resp.content_type specifies the content type of resp.content. + # Thus, resp.content can be deserialized properly. + + Args: + app_id (str): the callee app id + method (str): the method name which is called + data (bytes or :obj:`google.protobuf.message.Message`, optional): bytes + or Message for data which will be sent to app id + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + http_verb (str, optional): http method verb to call HTTP callee application + http_querystring (tuple, optional): the tuple to represent query string + timeout (int, optional): request timeout in seconds + + Returns: + :class:`InvokeMethodResponse` object returned from callee + """ + warn( + 'invoke_method with protocol gRPC is deprecated. Use gRPC proxying instead.', + DeprecationWarning, + stacklevel=2, + ) + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req_data = InvokeMethodRequest(data, content_type) + http_ext = None + if http_verb: + http_ext = self._get_http_extension(http_verb, http_querystring) + + content_type = '' + if req_data.content_type: + content_type = req_data.content_type + req = api_v1.InvokeServiceRequest( + id=app_id, + message=common_v1.InvokeRequest( + method=method_name, + data=req_data.proto, + content_type=content_type, + http_extension=http_ext, + ), + ) + + call = self._stub.InvokeService(req, metadata=metadata, timeout=timeout) + response = await call + + resp_data = InvokeMethodResponse(response.data, response.content_type) + resp_data.headers = await call.initial_metadata() # type: ignore + return resp_data + + async def invoke_binding( + self, + binding_name: str, + operation: str, + data: Union[bytes, str] = '', + binding_metadata: Dict[str, str] = {}, + metadata: Optional[MetadataTuple] = None, + ) -> BindingResponse: + """Invokes the output binding with the specified operation. + + The data field takes any JSON serializable value and acts as the + payload to be sent to the output binding. The metadata field is an + array of key/value pairs and allows you to set binding specific metadata + for each call. The operation field tells the Dapr binding which operation + it should perform. + + The example calls output `binding` service with bytes data: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + resp = await d.invoke_binding( + binding_name = 'kafkaBinding', + operation = 'create', + data = b'message', + ) + # resp.data includes the response data in bytes. + + Args: + binding_name (str): the name of the binding as defined in the components + operation (str): the operation to perform on the binding + data (bytes or str, optional): bytes or str for data which will sent to the binding + binding_metadata (dict, optional): Dapr metadata for output binding + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`InvokeBindingResponse` object returned from binding + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token ' + 'headers and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req_data = BindingRequest(data, binding_metadata) + + req = api_v1.InvokeBindingRequest( + name=binding_name, + data=req_data.data, + metadata=req_data.binding_metadata, + operation=operation, + ) + + call = self._stub.InvokeBinding(req, metadata=metadata) + response = await call + return BindingResponse( + response.data, dict(response.metadata), await call.initial_metadata() + ) + + async def publish_event( + self, + pubsub_name: str, + topic_name: str, + data: Union[bytes, str], + publish_metadata: Dict[str, str] = {}, + metadata: Optional[MetadataTuple] = None, + data_content_type: Optional[str] = None, + ) -> DaprResponse: + """Publish to a given topic. + This publishes an event with bytes array or str data to a specified topic and + specified pubsub component. The str data is encoded into bytes with default + charset of utf-8. Custom metadata can be passed with the metadata field which + will be passed on a gRPC metadata. + + The example publishes a byte array event to a topic: + + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.publish_event( + pubsub_name='pubsub_1', + topic_name='TOPIC_A', + data=b'message', + publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, + ) + # resp.headers includes the gRPC initial metadata. + + Args: + pubsub_name (str): the name of the pubsub component + topic_name (str): the topic name to publish to + data (bytes or str): bytes or str for data + publish_metadata (Dict[str, str], optional): Dapr metadata per Pub/Sub message + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + data_content_type: (str, optional): content type of the data payload + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not isinstance(data, bytes) and not isinstance(data, str): + raise ValueError(f'invalid type for data {type(data)}') + + req_data: bytes + if isinstance(data, bytes): + req_data = data + else: + if isinstance(data, str): + req_data = data.encode('utf-8') + + content_type = '' + if data_content_type: + content_type = data_content_type + req = api_v1.PublishEventRequest( + pubsub_name=pubsub_name, + topic=topic_name, + data=req_data, + data_content_type=content_type, + metadata=publish_metadata, + ) + + try: + call = self._stub.PublishEvent(req, metadata=metadata) + # response is google.protobuf.Empty + await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + return DaprResponse(await call.initial_metadata()) + + async def subscribe( + self, + pubsub_name: str, + topic: str, + metadata: Optional[dict] = None, + dead_letter_topic: Optional[str] = None, + ) -> Subscription: + """ + Subscribe to a topic with a bidirectional stream + + Args: + pubsub_name (str): The name of the pubsub component. + topic (str): The name of the topic. + metadata (Optional[dict]): Additional metadata for the subscription. + dead_letter_topic (Optional[str]): Name of the dead-letter topic. + + Returns: + Subscription: The Subscription object managing the stream. + """ + subscription = Subscription(self._stub, pubsub_name, topic, metadata, dead_letter_topic) + await subscription.start() + return subscription + + async def subscribe_with_handler( + self, + pubsub_name: str, + topic: str, + handler_fn: Callable[..., TopicEventResponse], + metadata: Optional[dict] = None, + dead_letter_topic: Optional[str] = None, + ) -> Callable[[], Awaitable[None]]: + """ + Subscribe to a topic with a bidirectional stream and a message handler function + + Args: + pubsub_name (str): The name of the pubsub component. + topic (str): The name of the topic. + handler_fn (Callable[..., TopicEventResponse]): The function to call when a message is received. + metadata (Optional[dict]): Additional metadata for the subscription. + dead_letter_topic (Optional[str]): Name of the dead-letter topic. + + Returns: + Callable[[], Awaitable[None]]: An async function to close the subscription. + """ + subscription = await self.subscribe(pubsub_name, topic, metadata, dead_letter_topic) + + async def stream_messages(sub: Subscription): + while True: + try: + message = await sub.next_message() + if message: + response = await handler_fn(message) + if response: + await subscription.respond(message, response.status) + else: + continue + except StreamInactiveError: + break + + async def close_subscription(): + await subscription.close() + + asyncio.create_task(stream_messages(subscription)) + + return close_subscription + + async def get_state( + self, + store_name: str, + key: str, + state_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> StateResponse: + """Gets value from a statestore with a key + + The example gets value from a statestore: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.get_state( + store_name='state_store' + key='key_1', + state={"key": "value"}, + state_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to get from + key (str): the key of the key-value pair to be gotten + state_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`StateResponse` gRPC metadata returned from callee + and value obtained from the state store + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + req = api_v1.GetStateRequest(store_name=store_name, key=key, metadata=state_metadata) + + try: + call = self._stub.GetState(req, metadata=metadata) + response = await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + return StateResponse( + data=response.data, etag=response.etag, headers=await call.initial_metadata() + ) + + async def get_bulk_state( + self, + store_name: str, + keys: Sequence[str], + parallelism: int = 1, + states_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> BulkStatesResponse: + """Gets values from a statestore with keys + + The example gets value from a statestore: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.get_bulk_state( + store_name='state_store', + keys=['key_1', key_2], + parallelism=2, + states_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to get from + keys (Sequence[str]): the keys to be retrieved + parallelism (int): number of items to be retrieved in parallel + states_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`BulkStatesResponse` gRPC metadata returned from callee + and value obtained from the state store + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req = api_v1.GetBulkStateRequest( + store_name=store_name, keys=keys, parallelism=parallelism, metadata=states_metadata + ) + + try: + call = self._stub.GetBulkState(req, metadata=metadata) + response = await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + items = [] + for item in response.items: + items.append( + BulkStateItem(key=item.key, data=item.data, etag=item.etag, error=item.error) + ) + return BulkStatesResponse(items=items, headers=await call.initial_metadata()) + + async def query_state( + self, store_name: str, query: str, states_metadata: Optional[Dict[str, str]] = dict() + ) -> QueryResponse: + """Queries a statestore with a query + + For details on supported queries see https://docs.dapr.io/ + + This example queries a statestore: + from dapr.aio.clients import DaprClient + + query = ''' + { + "filter": { + "EQ": { "state": "CA" } + }, + "sort": [ + { + "key": "person.id", + "order": "DESC" + } + ] + } + ''' + + async with DaprClient() as d: + resp = await d.query_state( + store_name='state_store', + query=query, + states_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to query + query (str): the query to be executed + states_metadata (Dict[str, str], optional): custom metadata for state request + + Returns: + :class:`QueryStateResponse` gRPC metadata returned from callee, + pagination token and results of the query + """ + warn( + 'The State Store Query API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req = api_v1.QueryStateRequest(store_name=store_name, query=query, metadata=states_metadata) + + try: + call = self._stub.QueryStateAlpha1(req) + response = await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + results = [] + for item in response.results: + results.append( + QueryResponseItem(key=item.key, value=item.data, etag=item.etag, error=item.error) + ) + + return QueryResponse( + token=response.token, + results=results, + metadata=response.metadata, + headers=await call.initial_metadata(), + ) + + async def save_state( + self, + store_name: str, + key: str, + value: Union[bytes, str], + etag: Optional[str] = None, + options: Optional[StateOptions] = None, + state_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> DaprResponse: + """Saves key-value pairs to a statestore + + This saves a value to the statestore with a given key and state store name. + Options for request can be passed with the options field and custom + metadata can be passed with metadata field. + + The example saves states to a statestore: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.save_state( + store_name='state_store', + key='key1', + value='value1', + etag='etag', + state_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to save to + key (str): the key to be saved + value (bytes or str): the value to be saved + etag (str, optional): the etag to save with + options (StateOptions, optional): custom options + for concurrency and consistency + state_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + Raises: + ValueError: value is not bytes or str + ValueError: store_name is empty + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not isinstance(value, (bytes, str)): + raise ValueError(f'invalid type for data {type(value)}') + + req_value = value + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + if options is None: + state_options = None + else: + state_options = options.get_proto() + + state = common_v1.StateItem( + key=key, + value=to_bytes(req_value), + etag=common_v1.Etag(value=etag) if etag is not None else None, + options=state_options, + metadata=state_metadata, + ) + + req = api_v1.SaveStateRequest(store_name=store_name, states=[state]) + try: + result, call = await self.retry_policy.run_rpc_async( + self._stub.SaveState, req, metadata=metadata + ) + return DaprResponse(headers=await call.initial_metadata()) + except AioRpcError as e: + raise DaprInternalError(e.details()) from e + + async def save_bulk_state( + self, store_name: str, states: List[StateItem], metadata: Optional[MetadataTuple] = None + ) -> DaprResponse: + """Saves state items to a statestore + + This saves a given state item into the statestore specified by store_name. + + The example saves states to a statestore: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.save_bulk_state( + store_name='state_store', + states=[StateItem(key='key1', value='value1'), + StateItem(key='key2', value='value2', etag='etag'),], + ) + + Args: + store_name (str): the state store name to save to + states (List[StateItem]): list of states to save + metadata (tuple, optional): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + Raises: + ValueError: states is empty + ValueError: store_name is empty + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not states or len(states) == 0: + raise ValueError('States to be saved cannot be empty') + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + req_states = [ + common_v1.StateItem( + key=i.key, + value=to_bytes(i.value), + etag=common_v1.Etag(value=i.etag) if i.etag is not None else None, + options=i.options, + metadata=i.metadata, + ) + for i in states + ] + + req = api_v1.SaveStateRequest(store_name=store_name, states=req_states) + + try: + call = self._stub.SaveState(req, metadata=metadata) + await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + return DaprResponse(headers=await call.initial_metadata()) + + async def execute_state_transaction( + self, + store_name: str, + operations: Sequence[TransactionalStateOperation], + transactional_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> DaprResponse: + """Saves or deletes key-value pairs to a statestore as a transaction + + This saves or deletes key-values to the statestore as part of a single transaction, + transaction_metadata is used for the transaction operation, while metadata is used + for the GRPC call. + + The example saves states to a statestore: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.execute_state_transaction( + store_name='state_store', + operations=[ + TransactionalStateOperation(key=key, data=value), + TransactionalStateOperation(key=another_key, data=another_value), + TransactionalStateOperation( + operation_type=TransactionOperationType.delete, + key=key_to_delete), + ], + transactional_metadata={"header1": "value1"}, + ) + + Args: + store_name (str): the state store name to save to + operations (Sequence[TransactionalStateOperation]): the transaction operations + transactional_metadata (Dict[str, str], optional): Dapr metadata for transaction + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req_ops = [ + api_v1.TransactionalStateOperation( + operationType=o.operation_type.value, + request=common_v1.StateItem( + key=o.key, + value=to_bytes(o.data), + etag=common_v1.Etag(value=o.etag) if o.etag is not None else None, + ), + ) + for o in operations + ] + + req = api_v1.ExecuteStateTransactionRequest( + storeName=store_name, operations=req_ops, metadata=transactional_metadata + ) + + try: + call = self._stub.ExecuteStateTransaction(req, metadata=metadata) + await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + return DaprResponse(headers=await call.initial_metadata()) + + async def delete_state( + self, + store_name: str, + key: str, + etag: Optional[str] = None, + options: Optional[StateOptions] = None, + state_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> DaprResponse: + """Deletes key-value pairs from a statestore + + This deletes a value from the statestore with a given key and state store name. + Options for request can be passed with the options field and custom + metadata can be passed with metadata field. + + The example deletes states from a statestore: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.delete_state( + store_name='state_store', + key='key1', + etag='etag', + state_metadata={"header1": "value1"}, + ) + + Args: + store_name (str): the state store name to delete from + key (str): the key of the key-value pair to delete + etag (str, optional): the etag to delete with + options (StateOptions, optional): custom options + for concurrency and consistency + state_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + if options is None: + state_options = None + else: + state_options = options.get_proto() + + etag_object = common_v1.Etag(value=etag) if etag is not None else None + req = api_v1.DeleteStateRequest( + store_name=store_name, + key=key, + etag=etag_object, + options=state_options, + metadata=state_metadata, + ) + + try: + call = self._stub.DeleteState(req, metadata=metadata) + await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + return DaprResponse(headers=await call.initial_metadata()) + + async def get_secret( + self, + store_name: str, + key: str, + secret_metadata: Optional[Dict[str, str]] = {}, + metadata: Optional[MetadataTuple] = None, + ) -> GetSecretResponse: + """Get secret with a given key. + + This gets a secret from secret store with a given key and secret store name. + Metadata for request can be passed with the secret_metadata field and custom + metadata can be passed with metadata field. + + + The example gets a secret from secret store: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + resp = await d.get_secret( + store_name='secretstoreA', + key='keyA', + secret_metadata={'header1', 'value1'} + ) + + # resp.headers includes the gRPC initial metadata. + # resp.trailers includes that gRPC trailing metadata. + + Args: + store_name (str): store name to get secret from + key (str): str for key + secret_metadata (Dict[str, str], Optional): Dapr metadata for secrets request + metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`GetSecretResponse` object with the secret and metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req = api_v1.GetSecretRequest(store_name=store_name, key=key, metadata=secret_metadata) + + call = self._stub.GetSecret(req, metadata=metadata) + response = await call + + return GetSecretResponse(secret=response.data, headers=await call.initial_metadata()) + + async def get_bulk_secret( + self, + store_name: str, + secret_metadata: Optional[Dict[str, str]] = {}, + metadata: Optional[MetadataTuple] = None, + ) -> GetBulkSecretResponse: + """Get all granted secrets. + + This gets all granted secrets from secret store. + Metadata for request can be passed with the secret_metadata field. + + + The example gets all secrets from secret store: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + resp = await d.get_bulk_secret( + store_name='secretstoreA', + secret_metadata={'header1', 'value1'} + ) + + # resp.headers includes the gRPC initial metadata. + # resp.trailers includes that gRPC trailing metadata. + + Args: + store_name (str): store name to get secret from + secret_metadata (Dict[str, Dict[str, str]], Optional): Dapr metadata of secrets request + metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`GetBulkSecretResponse` object with secrets and metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req = api_v1.GetBulkSecretRequest(store_name=store_name, metadata=secret_metadata) + + call = self._stub.GetBulkSecret(req, metadata=metadata) + response = await call + + secrets_map = {} + for key in response.data.keys(): + secret_response = response.data[key] + secrets_submap = {} + for subkey in secret_response.secrets.keys(): + secrets_submap[subkey] = secret_response.secrets[subkey] + secrets_map[key] = secrets_submap + + return GetBulkSecretResponse(secrets=secrets_map, headers=await call.initial_metadata()) + + async def get_configuration( + self, store_name: str, keys: List[str], config_metadata: Optional[Dict[str, str]] = dict() + ) -> ConfigurationResponse: + """Gets values from a config store with keys + + The example gets value from a config store: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.get_configuration( + store_name='state_store' + keys=['key_1'], + config_metadata={"metakey": "metavalue"} + ) + + Args: + store_name (str): the state store name to get from + keys (List[str]): the keys of the key-value pairs to be gotten + config_metadata (Dict[str, str], optional): Dapr metadata for configuration + + Returns: + :class:`ConfigurationResponse` gRPC metadata returned from callee + and value obtained from the config store + """ + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('Config store name cannot be empty to get the configuration') + + req = api_v1.GetConfigurationRequest( + store_name=store_name, keys=keys, metadata=config_metadata + ) + call = self._stub.GetConfiguration(req) + response: api_v1.GetConfigurationResponse = await call + return ConfigurationResponse(items=response.items, headers=await call.initial_metadata()) + + async def subscribe_configuration( + self, + store_name: str, + keys: List[str], + handler: Callable[[Text, ConfigurationResponse], None], + config_metadata: Optional[Dict[str, str]] = dict(), + ) -> Text: + """Gets changed value from a config store with a key + + The example gets value from a config store: + from dapr.aio.clients import DaprClient + async with DaprClient() as d: + resp = await d.subscribe_configuration( + store_name='state_store' + keys=['key_1'], + handler=handler, + config_metadata={"metakey": "metavalue"} + ) + + Args: + store_name (str): the state store name to get from + keys (str array): the keys of the key-value pairs to be gotten + handler(func (key, ConfigurationResponse)): the callback function to be called + config_metadata (Dict[str, str], optional): Dapr metadata for configuration + + Returns: + id (str): subscription id, which can be used to unsubscribe later + """ + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('Config store name cannot be empty to get the configuration') + + configWatcher = ConfigurationWatcher() + id = configWatcher.watch_configuration( + self._stub, store_name, keys, handler, config_metadata + ) + return id + + async def unsubscribe_configuration(self, store_name: str, id: str) -> bool: + """Unsubscribes from configuration changes. + + Args: + store_name (str): the state store name to unsubscribe from + id (str): the subscription id to unsubscribe + + Returns: + bool: True if unsubscribed successfully, False otherwise + """ + req = api_v1.UnsubscribeConfigurationRequest(store_name=store_name, id=id) + response: UnsubscribeConfigurationResponse = await self._stub.UnsubscribeConfiguration(req) + return response.ok + + async def try_lock( + self, store_name: str, resource_id: str, lock_owner: str, expiry_in_seconds: int + ) -> TryLockResponse: + """Tries to get a lock with an expiry. + + You can use the result of this operation directly on an `if` statement: + + if client.try_lock(store_name, resource_id, first_client_id, expiry_s): + # lock acquired successfully... + + You can also inspect the response's `success` attribute: + + response = client.try_lock(store_name, resource_id, first_client_id, expiry_s) + if response.success: + # lock acquired successfully... + + Finally, you can use this response with a `with` statement, and have the lock + be automatically unlocked after the with-statement scope ends + + with client.try_lock(store_name, resource_id, first_client_id, expiry_s) as lock: + if lock: + # lock acquired successfully... + # Lock automatically unlocked at this point, no need to call client->unlock(...) + + Args: + store_name (str): the lock store name, e.g. `redis`. + resource_id (str): the lock key. e.g. `order_id_111`. + It stands for "which resource I want to protect". + lock_owner (str): indicates the identifier of lock owner. + expiry_in_seconds (int): The length of time (in seconds) for which this lock + will be held and after which it expires. + + Returns: + :class:`TryLockResponse`: With the result of the try-lock operation. + """ + # Warnings and input validation + warn( + 'The Distributed Lock API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + store_name=store_name, resource_id=resource_id, lock_owner=lock_owner + ) + if not expiry_in_seconds or expiry_in_seconds < 1: + raise ValueError('expiry_in_seconds must be a positive number') + # Actual tryLock invocation + req = api_v1.TryLockRequest( + store_name=store_name, + resource_id=resource_id, + lock_owner=lock_owner, + expiry_in_seconds=expiry_in_seconds, + ) + call = self._stub.TryLockAlpha1(req) + response = await call + return TryLockResponse( + success=response.success, + client=self, + store_name=store_name, + resource_id=resource_id, + lock_owner=lock_owner, + headers=await call.initial_metadata(), + ) + + async def unlock(self, store_name: str, resource_id: str, lock_owner: str) -> UnlockResponse: + """Unlocks a lock. + + Args: + store_name (str): the lock store name, e.g. `redis`. + resource_id (str): the lock key. e.g. `order_id_111`. + It stands for "which resource I want to protect". + lock_owner (str): indicates the identifier of lock owner. + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`UnlockResponseStatus`: Status of the request, + `UnlockResponseStatus.success` if it was successful of some other + status otherwise. + """ + # Warnings and input validation + warn( + 'The Distributed Lock API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + store_name=store_name, resource_id=resource_id, lock_owner=lock_owner + ) + # Actual unlocking invocation + req = api_v1.UnlockRequest( + store_name=store_name, resource_id=resource_id, lock_owner=lock_owner + ) + call = self._stub.UnlockAlpha1(req) + response = await call + + return UnlockResponse( + status=UnlockResponseStatus(response.status), headers=await call.initial_metadata() + ) + + async def encrypt(self, data: Union[str, bytes], options: EncryptOptions): + """Encrypt a stream data with given options. + + The encrypt API encrypts a stream data with the given options. + + Example: + from dapr.aio.clients import DaprClient + from dapr.clients.grpc._crypto import EncryptOptions + + async with DaprClient() as d: + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = await d.encrypt( + data='hello dapr', + options=options, + ) + encrypted_data = await resp.read() + + Args: + data (Union[str, bytes]): Data to be encrypted. + options (EncryptOptions): Encryption options. + + Returns: + Readable stream of `api_v1.EncryptResponse`. + + Raises: + ValueError: If component_name, key_name, or key_wrap_algorithm is empty. + """ + # Warnings and input validation + warn( + 'The Encrypt API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + component_name=options.component_name, + key_name=options.key_name, + key_wrap_algorithm=options.key_wrap_algorithm, + ) + + req_iterator = EncryptRequestIterator(data, options) + resp_stream = self._stub.EncryptAlpha1(req_iterator) + return EncryptResponse(resp_stream) + + async def decrypt(self, data: Union[str, bytes], options: DecryptOptions): + """Decrypt a stream data with given options. + + The decrypt API decrypts a stream data with the given options. + + Example: + from dapr.aio.clients import DaprClient + from dapr.clients.grpc._crypto import DecryptOptions + + async with DaprClient() as d: + options = DecryptOptions( + component_name='crypto_component', + key_name='crypto_key', + ) + resp = await d.decrypt( + data='hello dapr', + options=options, + ) + decrypted_data = await resp.read() + + Args: + data (Union[str, bytes]): Data to be decrypted. + options (DecryptOptions): Decryption options. + + Returns: + Readable stream of `api_v1.DecryptResponse`. + + Raises: + ValueError: If component_name is empty. + """ + # Warnings and input validation + warn( + 'The Decrypt API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + component_name=options.component_name, + ) + + req_iterator = DecryptRequestIterator(data, options) + resp_stream = self._stub.DecryptAlpha1(req_iterator) + return DecryptResponse(resp_stream) + + async def start_workflow( + self, + workflow_component: str, + workflow_name: str, + input: Optional[Union[Any, bytes]] = None, + instance_id: Optional[str] = None, + workflow_options: Optional[Dict[str, str]] = dict(), + send_raw_bytes: bool = False, + ) -> StartWorkflowResponse: + """Starts a workflow. + + Args: + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + workflow_name (str): the name of the workflow that will be executed. + input (Optional[Union[Any, bytes]]): the input that the workflow will receive. + The input value will be serialized to JSON + by default. Use the send_raw_bytes param + to send unencoded binary input. + instance_id (Optional[str]): the name of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_options (Optional[Dict[str, str]]): the key-value options + that the workflow will receive. + send_raw_bytes (bool) if true, no serialization will be performed on the input + bytes + + Returns: + :class:`StartWorkflowResponse`: Instance ID associated with the started workflow + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + instance_id=instance_id, + workflow_component=workflow_component, + workflow_name=workflow_name, + ) + + if instance_id is None: + instance_id = str(uuid.uuid4()) + + if isinstance(input, bytes) and send_raw_bytes: + encoded_data = input + else: + try: + encoded_data = json.dumps(input).encode('utf-8') if input is not None else bytes([]) + except TypeError: + raise DaprInternalError('start_workflow: input data must be JSON serializable') + except ValueError as e: + raise DaprInternalError(f'start_workflow JSON serialization error: {e}') + + # Actual start workflow invocation + req = api_v1.StartWorkflowRequest( + instance_id=instance_id, + workflow_component=workflow_component, + workflow_name=workflow_name, + options=workflow_options, + input=encoded_data, + ) + + try: + response = self._stub.StartWorkflowBeta1(req) + return StartWorkflowResponse(instance_id=response.instance_id) + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def get_workflow(self, instance_id: str, workflow_component: str) -> GetWorkflowResponse: + """Gets information on a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`GetWorkflowResponse`: Instance ID associated with the started workflow + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual get workflow invocation + req = api_v1.GetWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + resp = self._stub.GetWorkflowBeta1(req) + if resp.created_at is None: + resp.created_at = datetime.now + if resp.last_updated_at is None: + resp.last_updated_at = datetime.now + return GetWorkflowResponse( + instance_id=instance_id, + workflow_name=resp.workflow_name, + created_at=resp.created_at, + last_updated_at=resp.last_updated_at, + runtime_status=getWorkflowRuntimeStatus(resp.runtime_status), + properties=resp.properties, + ) + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def terminate_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Terminates a workflow. + + Args: + instance_id (str): the ID of the workflow instance, e.g. + `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual terminate workflow invocation + req = api_v1.TerminateWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self._stub.TerminateWorkflowBeta1.with_call(req) + return DaprResponse(headers=call.initial_metadata()) + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def raise_workflow_event( + self, + instance_id: str, + workflow_component: str, + event_name: str, + event_data: Optional[Union[Any, bytes]] = None, + send_raw_bytes: bool = False, + ) -> DaprResponse: + """Raises an event on a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + event_name (str): the name of the event to be raised on + the workflow. + event_data (Optional[Union[Any, bytes]]): the input that the workflow will receive. + The input value will be serialized to JSON + by default. Use the send_raw_bytes param + to send unencoded binary input. + send_raw_bytes (bool) if true, no serialization will be performed on the input + bytes + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + instance_id=instance_id, workflow_component=workflow_component, event_name=event_name + ) + if isinstance(event_data, bytes) and send_raw_bytes: + encoded_data = event_data + else: + if event_data is not None: + try: + encoded_data = ( + json.dumps(event_data).encode('utf-8') + if event_data is not None + else bytes([]) + ) + except TypeError: + raise DaprInternalError( + 'raise_workflow_event:\ + event_data must be JSON serializable' + ) + except ValueError as e: + raise DaprInternalError(f'raise_workflow_event JSON serialization error: {e}') + encoded_data = json.dumps(event_data).encode('utf-8') + else: + encoded_data = bytes([]) + # Actual workflow raise event invocation + req = api_v1.raise_workflow_event( + instance_id=instance_id, + workflow_component=workflow_component, + event_name=event_name, + event_data=encoded_data, + ) + + try: + _, call = self._stub.RaiseEventWorkflowBeta1.with_call(req) + return DaprResponse(headers=call.initial_metadata()) + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def pause_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Pause a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual pause workflow invocation + req = api_v1.PauseWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self._stub.PauseWorkflowBeta1.with_call(req) + + return DaprResponse(headers=call.initial_metadata()) + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def resume_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Resumes a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual resume workflow invocation + req = api_v1.ResumeWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self._stub.ResumeWorkflowBeta1.with_call(req) + + return DaprResponse(headers=call.initial_metadata()) + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def purge_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Purges a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual purge workflow invocation + req = api_v1.PurgeWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self._stub.PurgeWorkflowBeta1.with_call(req) + + return DaprResponse(headers=call.initial_metadata()) + + except grpc.aio.AioRpcError as err: + raise DaprInternalError(err.details()) + + async def wait(self, timeout_s: float): + """Waits for sidecar to be available within the timeout. + + It checks if sidecar socket is available within the given timeout. + + The example gets a secret from secret store: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + await d.wait(1) # waits for 1 second. + # Sidecar is available after this. + + Args: + timeout_s (float): timeout in seconds + """ + warn( + 'The wait method is deprecated. A health check is now done automatically on client ' + 'initialization.', + DeprecationWarning, + stacklevel=2, + ) + + start = time.time() + while True: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(timeout_s) + try: + s.connect((self._uri.hostname, self._uri.port_as_int)) + return + except Exception as e: + remaining = (start + timeout_s) - time.time() + if remaining < 0: + raise e + asyncio.sleep(min(1, remaining)) + + async def get_metadata(self) -> GetMetadataResponse: + """Returns information about the sidecar allowing for runtime + discoverability. + + The metadata API returns a list of the components loaded, + the activated actors (if present) and attributes with information + attached. + + Each loaded component provides its name, type and version and also + information about supported features in the form of component + capabilities. + """ + + try: + call = self._stub.GetMetadata(GrpcEmpty()) + _resp = await call + except AioRpcError as err: + raise DaprGrpcError(err) from err + + response: api_v1.GetMetadataResponse = _resp # type alias + # Convert to more pythonic formats + active_actors_count = { + type_count.type: type_count.count for type_count in response.active_actors_count + } + registered_components = [ + RegisteredComponents( + name=i.name, type=i.type, version=i.version, capabilities=i.capabilities + ) + for i in response.registered_components + ] + extended_metadata = dict(response.extended_metadata.items()) + + return GetMetadataResponse( + application_id=response.id, + active_actors_count=active_actors_count, + registered_components=registered_components, + extended_metadata=extended_metadata, + headers=await call.initial_metadata(), + ) + + async def set_metadata(self, attributeName: str, attributeValue: str) -> DaprResponse: + """Adds a custom (extended) metadata attribute to the Dapr sidecar + information stored by the Metadata endpoint. + + The metadata API allows you to store additional attribute information + in the format of key-value pairs. These are ephemeral in-memory and + are not persisted if a sidecar is reloaded. This information should + be added at the time of a sidecar creation, for example, after the + application has started. + + Args: + attributeName (str): Custom attribute name. This is they key name + in the key-value pair. + attributeValue (str): Custom attribute value we want to store. + """ + # input validation + validateNotBlankString(attributeName=attributeName) + # Type-checking should catch this but better safe at run-time than sorry. + validateNotNone(attributeValue=attributeValue) + # Actual invocation + req = api_v1.SetMetadataRequest(key=attributeName, value=attributeValue) + call = self._stub.SetMetadata(req) + await call + + return DaprResponse(await call.initial_metadata()) + + async def shutdown(self) -> DaprResponse: + """Shutdown the sidecar. + + This will ask the sidecar to gracefully shutdown. + + The example shutdown the sidecar: + + from dapr.aio.clients import DaprClient + + async with DaprClient() as d: + resp = await d.shutdown() + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + + call = self._stub.Shutdown(GrpcEmpty()) + await call + + return DaprResponse(await call.initial_metadata()) diff --git a/dapr/aio/clients/grpc/interceptors.py b/dapr/aio/clients/grpc/interceptors.py index bf83cf56a..89dccdfd7 100644 --- a/dapr/aio/clients/grpc/interceptors.py +++ b/dapr/aio/clients/grpc/interceptors.py @@ -1,139 +1,139 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from collections import namedtuple -from typing import List, Tuple - -from grpc.aio import UnaryUnaryClientInterceptor, StreamStreamClientInterceptor, ClientCallDetails # type: ignore - -from dapr.conf import settings - - -class _ClientCallDetailsAsync( - namedtuple( - '_ClientCallDetails', ['method', 'timeout', 'metadata', 'credentials', 'wait_for_ready'] - ), - ClientCallDetails, -): - """This is an implementation of the ClientCallDetails interface needed for interceptors. - This class takes five named values and inherits the ClientCallDetails from grpc package. - This class encloses the values that describe a RPC to be invoked. - """ - - pass - - -class DaprClientTimeoutInterceptorAsync(UnaryUnaryClientInterceptor): - def intercept_unary_unary(self, continuation, client_call_details, request): - # If a specific timeout is not set, create a new ClientCallDetails with the default timeout - if client_call_details.timeout is None: - new_client_call_details = _ClientCallDetailsAsync( - client_call_details.method, - settings.DAPR_API_TIMEOUT_SECONDS, - client_call_details.metadata, - client_call_details.credentials, - client_call_details.wait_for_ready, - ) - return continuation(new_client_call_details, request) - - return continuation(client_call_details, request) - - -class DaprClientInterceptorAsync(UnaryUnaryClientInterceptor, StreamStreamClientInterceptor): - """The class implements a UnaryUnaryClientInterceptor from grpc to add an interceptor to add - additional headers to all calls as needed. - - Examples: - - interceptor = HeaderInterceptor([('header', 'value', )]) - intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) - - With multiple header values: - - interceptor = HeaderInterceptor([('header1', 'value1', ), ('header2', 'value2', )]) - intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) - """ - - def __init__(self, metadata: List[Tuple[str, str]]): - """Initializes the metadata field for the class. - - Args: - metadata list[tuple[str, str]]: list of tuple of (key, value) strings - representing header values - """ - - self._metadata = metadata - - async def _intercept_call(self, client_call_details: ClientCallDetails) -> ClientCallDetails: - """Internal intercept_call implementation which adds metadata to grpc metadata in the RPC - call details. - - Args: - client_call_details :class: `ClientCallDetails`: object that describes a RPC - to be invoked - - Returns: - :class: `ClientCallDetails` modified call details - """ - - metadata = [] - if client_call_details.metadata is not None: - metadata = list(client_call_details.metadata) - metadata.extend(self._metadata) - - new_call_details = _ClientCallDetailsAsync( - client_call_details.method, - client_call_details.timeout, - metadata, - client_call_details.credentials, - client_call_details.wait_for_ready, - ) - return new_call_details - - async def intercept_unary_unary(self, continuation, client_call_details, request): - """This method intercepts a unary-unary gRPC call. This is the implementation of the - abstract method defined in UnaryUnaryClientInterceptor defined in grpc. This is invoked - automatically by grpc based on the order in which interceptors are added to the channel. - - Args: - continuation: a callable to be invoked to continue with the RPC or next interceptor - client_call_details: a ClientCallDetails object describing the outgoing RPC - request: the request value for the RPC - - Returns: - A response object after invoking the continuation callable - """ - new_call_details = await self._intercept_call(client_call_details) - # Call continuation - response = await continuation(new_call_details, request) - return response - - async def intercept_stream_stream(self, continuation, client_call_details, request): - """This method intercepts a stream-stream gRPC call. This is the implementation of the - abstract method defined in StreamStreamClientInterceptor defined in grpc. This is invoked - automatically by grpc based on the order in which interceptors are added to the channel. - - Args: - continuation: a callable to be invoked to continue with the RPC or next interceptor - client_call_details: a ClientCallDetails object describing the outgoing RPC - request: the request value for the RPC - - Returns: - A response object after invoking the continuation callable - """ - new_call_details = await self._intercept_call(client_call_details) - # Call continuation - response = await continuation(new_call_details, request) - return response +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from collections import namedtuple +from typing import List, Tuple + +from grpc.aio import UnaryUnaryClientInterceptor, StreamStreamClientInterceptor, ClientCallDetails # type: ignore + +from dapr.conf import settings + + +class _ClientCallDetailsAsync( + namedtuple( + '_ClientCallDetails', ['method', 'timeout', 'metadata', 'credentials', 'wait_for_ready'] + ), + ClientCallDetails, +): + """This is an implementation of the ClientCallDetails interface needed for interceptors. + This class takes five named values and inherits the ClientCallDetails from grpc package. + This class encloses the values that describe a RPC to be invoked. + """ + + pass + + +class DaprClientTimeoutInterceptorAsync(UnaryUnaryClientInterceptor): + def intercept_unary_unary(self, continuation, client_call_details, request): + # If a specific timeout is not set, create a new ClientCallDetails with the default timeout + if client_call_details.timeout is None: + new_client_call_details = _ClientCallDetailsAsync( + client_call_details.method, + settings.DAPR_API_TIMEOUT_SECONDS, + client_call_details.metadata, + client_call_details.credentials, + client_call_details.wait_for_ready, + ) + return continuation(new_client_call_details, request) + + return continuation(client_call_details, request) + + +class DaprClientInterceptorAsync(UnaryUnaryClientInterceptor, StreamStreamClientInterceptor): + """The class implements a UnaryUnaryClientInterceptor from grpc to add an interceptor to add + additional headers to all calls as needed. + + Examples: + + interceptor = HeaderInterceptor([('header', 'value', )]) + intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) + + With multiple header values: + + interceptor = HeaderInterceptor([('header1', 'value1', ), ('header2', 'value2', )]) + intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) + """ + + def __init__(self, metadata: List[Tuple[str, str]]): + """Initializes the metadata field for the class. + + Args: + metadata list[tuple[str, str]]: list of tuple of (key, value) strings + representing header values + """ + + self._metadata = metadata + + async def _intercept_call(self, client_call_details: ClientCallDetails) -> ClientCallDetails: + """Internal intercept_call implementation which adds metadata to grpc metadata in the RPC + call details. + + Args: + client_call_details :class: `ClientCallDetails`: object that describes a RPC + to be invoked + + Returns: + :class: `ClientCallDetails` modified call details + """ + + metadata = [] + if client_call_details.metadata is not None: + metadata = list(client_call_details.metadata) + metadata.extend(self._metadata) + + new_call_details = _ClientCallDetailsAsync( + client_call_details.method, + client_call_details.timeout, + metadata, + client_call_details.credentials, + client_call_details.wait_for_ready, + ) + return new_call_details + + async def intercept_unary_unary(self, continuation, client_call_details, request): + """This method intercepts a unary-unary gRPC call. This is the implementation of the + abstract method defined in UnaryUnaryClientInterceptor defined in grpc. This is invoked + automatically by grpc based on the order in which interceptors are added to the channel. + + Args: + continuation: a callable to be invoked to continue with the RPC or next interceptor + client_call_details: a ClientCallDetails object describing the outgoing RPC + request: the request value for the RPC + + Returns: + A response object after invoking the continuation callable + """ + new_call_details = await self._intercept_call(client_call_details) + # Call continuation + response = await continuation(new_call_details, request) + return response + + async def intercept_stream_stream(self, continuation, client_call_details, request): + """This method intercepts a stream-stream gRPC call. This is the implementation of the + abstract method defined in StreamStreamClientInterceptor defined in grpc. This is invoked + automatically by grpc based on the order in which interceptors are added to the channel. + + Args: + continuation: a callable to be invoked to continue with the RPC or next interceptor + client_call_details: a ClientCallDetails object describing the outgoing RPC + request: the request value for the RPC + + Returns: + A response object after invoking the continuation callable + """ + new_call_details = await self._intercept_call(client_call_details) + # Call continuation + response = await continuation(new_call_details, request) + return response diff --git a/dapr/aio/clients/grpc/subscription.py b/dapr/aio/clients/grpc/subscription.py index a526ee862..85b4f6d19 100644 --- a/dapr/aio/clients/grpc/subscription.py +++ b/dapr/aio/clients/grpc/subscription.py @@ -1,116 +1,116 @@ -import asyncio -from grpc import StatusCode -from grpc.aio import AioRpcError - -from dapr.clients.grpc._response import TopicEventResponse -from dapr.clients.health import DaprHealth -from dapr.common.pubsub.subscription import ( - StreamInactiveError, - SubscriptionMessage, - StreamCancelledError, -) -from dapr.proto import api_v1, appcallback_v1 - - -class Subscription: - def __init__(self, stub, pubsub_name, topic, metadata=None, dead_letter_topic=None): - self._stub = stub - self._pubsub_name = pubsub_name - self._topic = topic - self._metadata = metadata or {} - self._dead_letter_topic = dead_letter_topic or '' - self._stream = None - self._send_queue = asyncio.Queue() - self._stream_active = asyncio.Event() - - async def start(self): - async def outgoing_request_iterator(): - try: - initial_request = api_v1.SubscribeTopicEventsRequestAlpha1( - initial_request=api_v1.SubscribeTopicEventsRequestInitialAlpha1( - pubsub_name=self._pubsub_name, - topic=self._topic, - metadata=self._metadata, - dead_letter_topic=self._dead_letter_topic, - ) - ) - yield initial_request - - while self._stream_active.is_set(): - try: - response = await asyncio.wait_for(self._send_queue.get(), timeout=1.0) - yield response - except asyncio.TimeoutError: - continue - except Exception as e: - raise Exception(f'Error while writing to stream: {e}') - - self._stream = self._stub.SubscribeTopicEventsAlpha1(outgoing_request_iterator()) - self._stream_active.set() - await self._stream.read() # discard the initial message - - async def reconnect_stream(self): - await self.close() - DaprHealth.wait_until_ready() - print('Attempting to reconnect...') - await self.start() - - async def next_message(self): - if not self._stream_active.is_set(): - raise StreamInactiveError('Stream is not active') - - try: - if self._stream is not None: - message = await self._stream.read() - if message is None: - return None - return SubscriptionMessage(message.event_message) - except AioRpcError as e: - if e.code() == StatusCode.UNAVAILABLE: - print( - f'gRPC error while reading from stream: {e.details()}, ' - f'Status Code: {e.code()}. ' - f'Attempting to reconnect...' - ) - await self.reconnect_stream() - elif e.code() == StatusCode.CANCELLED: - raise StreamCancelledError('Stream has been cancelled') - else: - raise Exception(f'gRPC error while reading from subscription stream: {e} ') - except Exception as e: - raise Exception(f'Error while fetching message: {e}') - - return None - - async def respond(self, message, status): - try: - status = appcallback_v1.TopicEventResponse(status=status.value) - response = api_v1.SubscribeTopicEventsRequestProcessedAlpha1( - id=message.id(), status=status - ) - msg = api_v1.SubscribeTopicEventsRequestAlpha1(event_processed=response) - if not self._stream_active.is_set(): - raise StreamInactiveError('Stream is not active') - await self._send_queue.put(msg) - except Exception as e: - print(f"Can't send message: {e}") - - async def respond_success(self, message): - await self.respond(message, TopicEventResponse('success').status) - - async def respond_retry(self, message): - await self.respond(message, TopicEventResponse('retry').status) - - async def respond_drop(self, message): - await self.respond(message, TopicEventResponse('drop').status) - - async def close(self): - if self._stream: - try: - self._stream.cancel() - self._stream_active.clear() - except AioRpcError as e: - if e.code() != StatusCode.CANCELLED: - raise Exception(f'Error while closing stream: {e}') - except Exception as e: - raise Exception(f'Error while closing stream: {e}') +import asyncio +from grpc import StatusCode +from grpc.aio import AioRpcError + +from dapr.clients.grpc._response import TopicEventResponse +from dapr.clients.health import DaprHealth +from dapr.common.pubsub.subscription import ( + StreamInactiveError, + SubscriptionMessage, + StreamCancelledError, +) +from dapr.proto import api_v1, appcallback_v1 + + +class Subscription: + def __init__(self, stub, pubsub_name, topic, metadata=None, dead_letter_topic=None): + self._stub = stub + self._pubsub_name = pubsub_name + self._topic = topic + self._metadata = metadata or {} + self._dead_letter_topic = dead_letter_topic or '' + self._stream = None + self._send_queue = asyncio.Queue() + self._stream_active = asyncio.Event() + + async def start(self): + async def outgoing_request_iterator(): + try: + initial_request = api_v1.SubscribeTopicEventsRequestAlpha1( + initial_request=api_v1.SubscribeTopicEventsRequestInitialAlpha1( + pubsub_name=self._pubsub_name, + topic=self._topic, + metadata=self._metadata, + dead_letter_topic=self._dead_letter_topic, + ) + ) + yield initial_request + + while self._stream_active.is_set(): + try: + response = await asyncio.wait_for(self._send_queue.get(), timeout=1.0) + yield response + except asyncio.TimeoutError: + continue + except Exception as e: + raise Exception(f'Error while writing to stream: {e}') + + self._stream = self._stub.SubscribeTopicEventsAlpha1(outgoing_request_iterator()) + self._stream_active.set() + await self._stream.read() # discard the initial message + + async def reconnect_stream(self): + await self.close() + DaprHealth.wait_until_ready() + print('Attempting to reconnect...') + await self.start() + + async def next_message(self): + if not self._stream_active.is_set(): + raise StreamInactiveError('Stream is not active') + + try: + if self._stream is not None: + message = await self._stream.read() + if message is None: + return None + return SubscriptionMessage(message.event_message) + except AioRpcError as e: + if e.code() == StatusCode.UNAVAILABLE: + print( + f'gRPC error while reading from stream: {e.details()}, ' + f'Status Code: {e.code()}. ' + f'Attempting to reconnect...' + ) + await self.reconnect_stream() + elif e.code() == StatusCode.CANCELLED: + raise StreamCancelledError('Stream has been cancelled') + else: + raise Exception(f'gRPC error while reading from subscription stream: {e} ') + except Exception as e: + raise Exception(f'Error while fetching message: {e}') + + return None + + async def respond(self, message, status): + try: + status = appcallback_v1.TopicEventResponse(status=status.value) + response = api_v1.SubscribeTopicEventsRequestProcessedAlpha1( + id=message.id(), status=status + ) + msg = api_v1.SubscribeTopicEventsRequestAlpha1(event_processed=response) + if not self._stream_active.is_set(): + raise StreamInactiveError('Stream is not active') + await self._send_queue.put(msg) + except Exception as e: + print(f"Can't send message: {e}") + + async def respond_success(self, message): + await self.respond(message, TopicEventResponse('success').status) + + async def respond_retry(self, message): + await self.respond(message, TopicEventResponse('retry').status) + + async def respond_drop(self, message): + await self.respond(message, TopicEventResponse('drop').status) + + async def close(self): + if self._stream: + try: + self._stream.cancel() + self._stream_active.clear() + except AioRpcError as e: + if e.code() != StatusCode.CANCELLED: + raise Exception(f'Error while closing stream: {e}') + except Exception as e: + raise Exception(f'Error while closing stream: {e}') diff --git a/dapr/clients/__init__.py b/dapr/clients/__init__.py index 20c147856..ac651b55c 100644 --- a/dapr/clients/__init__.py +++ b/dapr/clients/__init__.py @@ -1,195 +1,195 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Callable, Dict, List, Optional, Union -from warnings import warn - -from dapr.clients.base import DaprActorClientBase -from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN -from dapr.clients.grpc.client import DaprGrpcClient, MetadataTuple, InvokeMethodResponse -from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient -from dapr.clients.http.dapr_invocation_http_client import DaprInvocationHttpClient -from dapr.clients.retry import RetryPolicy -from dapr.conf import settings -from google.protobuf.message import Message as GrpcMessage - - -__all__ = [ - 'DaprClient', - 'DaprActorClientBase', - 'DaprActorHttpClient', - 'DaprInternalError', - 'ERROR_CODE_UNKNOWN', -] - - -from grpc import ( # type: ignore - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, -) - - -class DaprClient(DaprGrpcClient): - """The Dapr python-sdk uses gRPC for most operations. The exception being - service invocation which needs to support HTTP to HTTP invocations. The sdk defaults - to HTTP but can be overridden with the DAPR_API_METHOD_INVOCATION_PROTOCOL environment - variable. See: https://github.com/dapr/python-sdk/issues/176 for more details""" - - def __init__( - self, - address: Optional[str] = None, - headers_callback: Optional[Callable[[], Dict[str, str]]] = None, - interceptors: Optional[ - List[ - Union[ - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, - ] - ] - ] = None, - http_timeout_seconds: Optional[int] = None, - max_grpc_message_length: Optional[int] = None, - retry_policy: Optional[RetryPolicy] = None, - ): - """Connects to Dapr Runtime via gRPC and HTTP. - - Args: - address (str, optional): Dapr Runtime gRPC endpoint address. - headers_callback (lambda: Dict[str, str], optional): Generates header for each request. - interceptors (list of UnaryUnaryClientInterceptor or - UnaryStreamClientInterceptor or - StreamUnaryClientInterceptor or - StreamStreamClientInterceptor, optional): gRPC interceptors. - http_timeout_seconds (int): specify a timeout for http connections - max_grpc_message_length (int, optional): The maximum grpc send and receive - message length in bytes. - """ - super().__init__(address, interceptors, max_grpc_message_length, retry_policy) - self.invocation_client = None - - invocation_protocol = settings.DAPR_API_METHOD_INVOCATION_PROTOCOL.upper() - - if invocation_protocol == 'HTTP': - if http_timeout_seconds is None: - http_timeout_seconds = settings.DAPR_HTTP_TIMEOUT_SECONDS - self.invocation_client = DaprInvocationHttpClient( - headers_callback=headers_callback, timeout=http_timeout_seconds - ) - elif invocation_protocol == 'GRPC': - pass - else: - raise DaprInternalError( - f'Unknown value for DAPR_API_METHOD_INVOCATION_PROTOCOL: {invocation_protocol}' - ) - - def invoke_method( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage] = '', - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invoke a service method over gRPC or HTTP. - - Args: - app_id (str): Application ID. - method_name (str): Method to be invoked. - data (bytes or str or GrpcMessage, optional): Data for request's body. - content_type (str, optional): Content type of the data. - metadata (MetadataTuple, optional): Additional metadata or headers. - http_verb (str, optional): HTTP verb for the request. - http_querystring (MetadataTuple, optional): Query parameters. - timeout (int, optional): request timeout in seconds. - - Returns: - InvokeMethodResponse: the response from the method invocation. - """ - if self.invocation_client: - return self.invocation_client.invoke_method( - app_id, - method_name, - data, - content_type=content_type, - metadata=metadata, - http_verb=http_verb, - http_querystring=http_querystring, - timeout=timeout, - ) - else: - return super().invoke_method( - app_id, - method_name, - data, - content_type=content_type, - metadata=metadata, - http_verb=http_verb, - http_querystring=http_querystring, - timeout=timeout, - ) - - async def invoke_method_async( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage], - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invoke a service method over gRPC or HTTP. - - Args: - app_id (str): Application ID. - method_name (str): Method to be invoked. - data (bytes or str or GrpcMessage, optional): Data for request's body. - content_type (str, optional): Content type of the data. - metadata (MetadataTuple, optional): Additional metadata or headers. - http_verb (str, optional): HTTP verb for the request. - http_querystring (MetadataTuple, optional): Query parameters. - timeout (int, optional): Request timeout in seconds. - - Returns: - InvokeMethodResponse: the method invocation response. - """ - if self.invocation_client: - warn( - 'Async invocation is deprecated. Please use `dapr.aio.clients.DaprClient`.', - DeprecationWarning, - stacklevel=2, - ) - return await self.invocation_client.invoke_method_async( - app_id, - method_name, - data, - content_type=content_type, - metadata=metadata, - http_verb=http_verb, - http_querystring=http_querystring, - timeout=timeout, - ) - else: - raise NotImplementedError( - 'Please use `dapr.aio.clients.DaprClient` for async invocation' - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Callable, Dict, List, Optional, Union +from warnings import warn + +from dapr.clients.base import DaprActorClientBase +from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN +from dapr.clients.grpc.client import DaprGrpcClient, MetadataTuple, InvokeMethodResponse +from dapr.clients.http.dapr_actor_http_client import DaprActorHttpClient +from dapr.clients.http.dapr_invocation_http_client import DaprInvocationHttpClient +from dapr.clients.retry import RetryPolicy +from dapr.conf import settings +from google.protobuf.message import Message as GrpcMessage + + +__all__ = [ + 'DaprClient', + 'DaprActorClientBase', + 'DaprActorHttpClient', + 'DaprInternalError', + 'ERROR_CODE_UNKNOWN', +] + + +from grpc import ( # type: ignore + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, +) + + +class DaprClient(DaprGrpcClient): + """The Dapr python-sdk uses gRPC for most operations. The exception being + service invocation which needs to support HTTP to HTTP invocations. The sdk defaults + to HTTP but can be overridden with the DAPR_API_METHOD_INVOCATION_PROTOCOL environment + variable. See: https://github.com/dapr/python-sdk/issues/176 for more details""" + + def __init__( + self, + address: Optional[str] = None, + headers_callback: Optional[Callable[[], Dict[str, str]]] = None, + interceptors: Optional[ + List[ + Union[ + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, + ] + ] + ] = None, + http_timeout_seconds: Optional[int] = None, + max_grpc_message_length: Optional[int] = None, + retry_policy: Optional[RetryPolicy] = None, + ): + """Connects to Dapr Runtime via gRPC and HTTP. + + Args: + address (str, optional): Dapr Runtime gRPC endpoint address. + headers_callback (lambda: Dict[str, str], optional): Generates header for each request. + interceptors (list of UnaryUnaryClientInterceptor or + UnaryStreamClientInterceptor or + StreamUnaryClientInterceptor or + StreamStreamClientInterceptor, optional): gRPC interceptors. + http_timeout_seconds (int): specify a timeout for http connections + max_grpc_message_length (int, optional): The maximum grpc send and receive + message length in bytes. + """ + super().__init__(address, interceptors, max_grpc_message_length, retry_policy) + self.invocation_client = None + + invocation_protocol = settings.DAPR_API_METHOD_INVOCATION_PROTOCOL.upper() + + if invocation_protocol == 'HTTP': + if http_timeout_seconds is None: + http_timeout_seconds = settings.DAPR_HTTP_TIMEOUT_SECONDS + self.invocation_client = DaprInvocationHttpClient( + headers_callback=headers_callback, timeout=http_timeout_seconds + ) + elif invocation_protocol == 'GRPC': + pass + else: + raise DaprInternalError( + f'Unknown value for DAPR_API_METHOD_INVOCATION_PROTOCOL: {invocation_protocol}' + ) + + def invoke_method( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage] = '', + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invoke a service method over gRPC or HTTP. + + Args: + app_id (str): Application ID. + method_name (str): Method to be invoked. + data (bytes or str or GrpcMessage, optional): Data for request's body. + content_type (str, optional): Content type of the data. + metadata (MetadataTuple, optional): Additional metadata or headers. + http_verb (str, optional): HTTP verb for the request. + http_querystring (MetadataTuple, optional): Query parameters. + timeout (int, optional): request timeout in seconds. + + Returns: + InvokeMethodResponse: the response from the method invocation. + """ + if self.invocation_client: + return self.invocation_client.invoke_method( + app_id, + method_name, + data, + content_type=content_type, + metadata=metadata, + http_verb=http_verb, + http_querystring=http_querystring, + timeout=timeout, + ) + else: + return super().invoke_method( + app_id, + method_name, + data, + content_type=content_type, + metadata=metadata, + http_verb=http_verb, + http_querystring=http_querystring, + timeout=timeout, + ) + + async def invoke_method_async( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage], + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invoke a service method over gRPC or HTTP. + + Args: + app_id (str): Application ID. + method_name (str): Method to be invoked. + data (bytes or str or GrpcMessage, optional): Data for request's body. + content_type (str, optional): Content type of the data. + metadata (MetadataTuple, optional): Additional metadata or headers. + http_verb (str, optional): HTTP verb for the request. + http_querystring (MetadataTuple, optional): Query parameters. + timeout (int, optional): Request timeout in seconds. + + Returns: + InvokeMethodResponse: the method invocation response. + """ + if self.invocation_client: + warn( + 'Async invocation is deprecated. Please use `dapr.aio.clients.DaprClient`.', + DeprecationWarning, + stacklevel=2, + ) + return await self.invocation_client.invoke_method_async( + app_id, + method_name, + data, + content_type=content_type, + metadata=metadata, + http_verb=http_verb, + http_querystring=http_querystring, + timeout=timeout, + ) + else: + raise NotImplementedError( + 'Please use `dapr.aio.clients.DaprClient` for async invocation' + ) diff --git a/dapr/clients/base.py b/dapr/clients/base.py index dccb96240..9f18151bd 100644 --- a/dapr/clients/base.py +++ b/dapr/clients/base.py @@ -1,57 +1,57 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from abc import ABC, abstractmethod -from typing import Optional - - -DEFAULT_ENCODING = 'utf-8' -DEFAULT_JSON_CONTENT_TYPE = f'application/json; charset={DEFAULT_ENCODING}' - - -class DaprActorClientBase(ABC): - """A base class that represents Dapr Actor Client.""" - - @abstractmethod - async def invoke_method( - self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None - ) -> bytes: - ... - - @abstractmethod - async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: - ... - - @abstractmethod - async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: - ... - - @abstractmethod - async def register_reminder( - self, actor_type: str, actor_id: str, name: str, data: bytes - ) -> None: - ... - - @abstractmethod - async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: - ... - - @abstractmethod - async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: - ... - - @abstractmethod - async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: - ... +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from abc import ABC, abstractmethod +from typing import Optional + + +DEFAULT_ENCODING = 'utf-8' +DEFAULT_JSON_CONTENT_TYPE = f'application/json; charset={DEFAULT_ENCODING}' + + +class DaprActorClientBase(ABC): + """A base class that represents Dapr Actor Client.""" + + @abstractmethod + async def invoke_method( + self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None + ) -> bytes: + ... + + @abstractmethod + async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: + ... + + @abstractmethod + async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: + ... + + @abstractmethod + async def register_reminder( + self, actor_type: str, actor_id: str, name: str, data: bytes + ) -> None: + ... + + @abstractmethod + async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: + ... + + @abstractmethod + async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: + ... + + @abstractmethod + async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: + ... diff --git a/dapr/clients/exceptions.py b/dapr/clients/exceptions.py index 91bc04a83..d8135d9e7 100644 --- a/dapr/clients/exceptions.py +++ b/dapr/clients/exceptions.py @@ -1,134 +1,134 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import json -from typing import Optional - -from google.protobuf.json_format import MessageToDict -from grpc import RpcError # type: ignore -from grpc_status import rpc_status # type: ignore -from google.rpc import error_details_pb2 # type: ignore - -ERROR_CODE_UNKNOWN = 'UNKNOWN' -ERROR_CODE_DOES_NOT_EXIST = 'ERR_DOES_NOT_EXIST' - - -class DaprInternalError(Exception): - """DaprInternalError encapsulates all Dapr exceptions""" - - def __init__( - self, - message: Optional[str], - error_code: Optional[str] = ERROR_CODE_UNKNOWN, - raw_response_bytes: Optional[bytes] = None, - ): - self._message = message - self._error_code = error_code - self._raw_response_bytes = raw_response_bytes - - def as_dict(self): - return { - 'message': self._message, - 'errorCode': self._error_code, - 'raw_response_bytes': self._raw_response_bytes, - } - - -class StatusDetails: - def __init__(self): - self.error_info = None - self.retry_info = None - self.debug_info = None - self.quota_failure = None - self.precondition_failure = None - self.bad_request = None - self.request_info = None - self.resource_info = None - self.help = None - self.localized_message = None - - def as_dict(self): - return {attr: getattr(self, attr) for attr in self.__dict__} - - -class DaprGrpcError(RpcError): - def __init__(self, err: RpcError): - self._status_code = err.code() - self._err_message = err.details() - self._details = StatusDetails() - - self._grpc_status = rpc_status.from_call(err) - self._parse_details() - - def _parse_details(self): - if self._grpc_status is None: - return - - for detail in self._grpc_status.details: - if detail.Is(error_details_pb2.ErrorInfo.DESCRIPTOR): - self._details.error_info = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.RetryInfo.DESCRIPTOR): - self._details.retry_info = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.DebugInfo.DESCRIPTOR): - self._details.debug_info = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.QuotaFailure.DESCRIPTOR): - self._details.quota_failure = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.PreconditionFailure.DESCRIPTOR): - self._details.precondition_failure = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.BadRequest.DESCRIPTOR): - self._details.bad_request = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.RequestInfo.DESCRIPTOR): - self._details.request_info = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.ResourceInfo.DESCRIPTOR): - self._details.resource_info = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.Help.DESCRIPTOR): - self._details.help = serialize_status_detail(detail) - elif detail.Is(error_details_pb2.LocalizedMessage.DESCRIPTOR): - self._details.localized_message = serialize_status_detail(detail) - - def code(self): - return self._status_code - - def details(self): - """ - We're keeping the method name details() so it matches the grpc.RpcError interface. - @return: - """ - return self._err_message - - def error_code(self): - if not self.status_details() or not self.status_details().error_info: - return ERROR_CODE_UNKNOWN - return self.status_details().error_info.get('reason', ERROR_CODE_UNKNOWN) - - def status_details(self): - return self._details - - def get_grpc_status(self): - return self._grpc_status - - def json(self): - error_details = { - 'status_code': self.code().name, - 'message': self.details(), - 'error_code': self.error_code(), - 'details': self._details.as_dict(), - } - return json.dumps(error_details) - - -def serialize_status_detail(status_detail): - if not status_detail: - return None - return MessageToDict(status_detail, preserving_proto_field_name=True) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import json +from typing import Optional + +from google.protobuf.json_format import MessageToDict +from grpc import RpcError # type: ignore +from grpc_status import rpc_status # type: ignore +from google.rpc import error_details_pb2 # type: ignore + +ERROR_CODE_UNKNOWN = 'UNKNOWN' +ERROR_CODE_DOES_NOT_EXIST = 'ERR_DOES_NOT_EXIST' + + +class DaprInternalError(Exception): + """DaprInternalError encapsulates all Dapr exceptions""" + + def __init__( + self, + message: Optional[str], + error_code: Optional[str] = ERROR_CODE_UNKNOWN, + raw_response_bytes: Optional[bytes] = None, + ): + self._message = message + self._error_code = error_code + self._raw_response_bytes = raw_response_bytes + + def as_dict(self): + return { + 'message': self._message, + 'errorCode': self._error_code, + 'raw_response_bytes': self._raw_response_bytes, + } + + +class StatusDetails: + def __init__(self): + self.error_info = None + self.retry_info = None + self.debug_info = None + self.quota_failure = None + self.precondition_failure = None + self.bad_request = None + self.request_info = None + self.resource_info = None + self.help = None + self.localized_message = None + + def as_dict(self): + return {attr: getattr(self, attr) for attr in self.__dict__} + + +class DaprGrpcError(RpcError): + def __init__(self, err: RpcError): + self._status_code = err.code() + self._err_message = err.details() + self._details = StatusDetails() + + self._grpc_status = rpc_status.from_call(err) + self._parse_details() + + def _parse_details(self): + if self._grpc_status is None: + return + + for detail in self._grpc_status.details: + if detail.Is(error_details_pb2.ErrorInfo.DESCRIPTOR): + self._details.error_info = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.RetryInfo.DESCRIPTOR): + self._details.retry_info = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.DebugInfo.DESCRIPTOR): + self._details.debug_info = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.QuotaFailure.DESCRIPTOR): + self._details.quota_failure = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.PreconditionFailure.DESCRIPTOR): + self._details.precondition_failure = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.BadRequest.DESCRIPTOR): + self._details.bad_request = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.RequestInfo.DESCRIPTOR): + self._details.request_info = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.ResourceInfo.DESCRIPTOR): + self._details.resource_info = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.Help.DESCRIPTOR): + self._details.help = serialize_status_detail(detail) + elif detail.Is(error_details_pb2.LocalizedMessage.DESCRIPTOR): + self._details.localized_message = serialize_status_detail(detail) + + def code(self): + return self._status_code + + def details(self): + """ + We're keeping the method name details() so it matches the grpc.RpcError interface. + @return: + """ + return self._err_message + + def error_code(self): + if not self.status_details() or not self.status_details().error_info: + return ERROR_CODE_UNKNOWN + return self.status_details().error_info.get('reason', ERROR_CODE_UNKNOWN) + + def status_details(self): + return self._details + + def get_grpc_status(self): + return self._grpc_status + + def json(self): + error_details = { + 'status_code': self.code().name, + 'message': self.details(), + 'error_code': self.error_code(), + 'details': self._details.as_dict(), + } + return json.dumps(error_details) + + +def serialize_status_detail(status_detail): + if not status_detail: + return None + return MessageToDict(status_detail, preserving_proto_field_name=True) diff --git a/dapr/clients/grpc/__init__.py b/dapr/clients/grpc/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/clients/grpc/__init__.py +++ b/dapr/clients/grpc/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/clients/grpc/_crypto.py b/dapr/clients/grpc/_crypto.py index ae6fd5e11..f6dcdc6cb 100644 --- a/dapr/clients/grpc/_crypto.py +++ b/dapr/clients/grpc/_crypto.py @@ -1,71 +1,71 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dataclasses import dataclass - -from dapr.proto import api_v1 - - -@dataclass -class EncryptOptions: - """EncryptOptions contains options passed to the encrypt method. - - Args: - component_name (str): The name of the component. - key_name (str): The name of the key to use for the encryption operation. - key_wrap_algorithm (str): The key wrap algorithm to use. - data_encryption_cipher (str, optional): The cipher to use for the encryption operation. - omit_decryption_key_name (bool, optional): If True, omits the decryption key name from - header `dapr-decryption-key-name` from the output. If False, includes the specified - decryption key name specified in header `dapr-decryption-key-name`. - decryption_key_name (str, optional): If `dapr-omit-decryption-key-name` is True, this - contains the name of the intended decryption key to include in the output. - """ - - component_name: str - key_name: str - key_wrap_algorithm: str - data_encryption_cipher: str = 'aes-gcm' - omit_decryption_key_name: bool = False - decryption_key_name: str = '' - - def get_proto(self) -> api_v1.EncryptRequestOptions: - return api_v1.EncryptRequestOptions( - component_name=self.component_name, - key_name=self.key_name, - key_wrap_algorithm=self.key_wrap_algorithm, - data_encryption_cipher=self.data_encryption_cipher, - omit_decryption_key_name=self.omit_decryption_key_name, - decryption_key_name=self.decryption_key_name, - ) - - -@dataclass -class DecryptOptions: - """DecryptOptions contains options passed to the decrypt method. - - Args: - component_name (str): The name of the component. - key_name (str, optional): The name of the key to use for the decryption operation. - """ - - component_name: str - key_name: str = '' - - def get_proto(self) -> api_v1.DecryptRequestOptions: - return api_v1.DecryptRequestOptions( - component_name=self.component_name, - key_name=self.key_name, - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dataclasses import dataclass + +from dapr.proto import api_v1 + + +@dataclass +class EncryptOptions: + """EncryptOptions contains options passed to the encrypt method. + + Args: + component_name (str): The name of the component. + key_name (str): The name of the key to use for the encryption operation. + key_wrap_algorithm (str): The key wrap algorithm to use. + data_encryption_cipher (str, optional): The cipher to use for the encryption operation. + omit_decryption_key_name (bool, optional): If True, omits the decryption key name from + header `dapr-decryption-key-name` from the output. If False, includes the specified + decryption key name specified in header `dapr-decryption-key-name`. + decryption_key_name (str, optional): If `dapr-omit-decryption-key-name` is True, this + contains the name of the intended decryption key to include in the output. + """ + + component_name: str + key_name: str + key_wrap_algorithm: str + data_encryption_cipher: str = 'aes-gcm' + omit_decryption_key_name: bool = False + decryption_key_name: str = '' + + def get_proto(self) -> api_v1.EncryptRequestOptions: + return api_v1.EncryptRequestOptions( + component_name=self.component_name, + key_name=self.key_name, + key_wrap_algorithm=self.key_wrap_algorithm, + data_encryption_cipher=self.data_encryption_cipher, + omit_decryption_key_name=self.omit_decryption_key_name, + decryption_key_name=self.decryption_key_name, + ) + + +@dataclass +class DecryptOptions: + """DecryptOptions contains options passed to the decrypt method. + + Args: + component_name (str): The name of the component. + key_name (str, optional): The name of the key to use for the decryption operation. + """ + + component_name: str + key_name: str = '' + + def get_proto(self) -> api_v1.DecryptRequestOptions: + return api_v1.DecryptRequestOptions( + component_name=self.component_name, + key_name=self.key_name, + ) diff --git a/dapr/clients/grpc/_helpers.py b/dapr/clients/grpc/_helpers.py index 7da35dc27..c44836dfd 100644 --- a/dapr/clients/grpc/_helpers.py +++ b/dapr/clients/grpc/_helpers.py @@ -1,107 +1,107 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -from typing import Dict, List, Union, Tuple, Optional -from enum import Enum -from google.protobuf.any_pb2 import Any as GrpcAny -from google.protobuf.message import Message as GrpcMessage - -MetadataDict = Dict[str, List[Union[bytes, str]]] -MetadataTuple = Tuple[Tuple[str, Union[bytes, str]], ...] - - -def tuple_to_dict(tupledata: MetadataTuple) -> MetadataDict: - """Converts tuple to dict. - - Args: - tupledata (tuple): tuple storing metadata - - Returns: - A dict which is converted from tuple - """ - - d: MetadataDict = {} - for k, v in tupledata: # type: ignore - d.setdefault(k, []).append(v) - return d - - -def unpack(data: GrpcAny, message: GrpcMessage) -> None: - """Unpack the serialized protocol buffer message. - - Args: - data (:obj:`google.protobuf.message.Any`): the serialized protocol buffer message. - message (:obj:`google.protobuf.message.Message`): the protocol buffer message object - to which the response data is deserialized. - - Raises: - ValueError: message is not protocol buffer message object or message's type is not - matched with the response data type - """ - if not isinstance(message, GrpcMessage): - raise ValueError('output message is not protocol buffer message object') - if not data.Is(message.DESCRIPTOR): - raise ValueError(f'invalid type. serialized message type: {data.type_url}') - data.Unpack(message) - - -def to_bytes(data: Union[str, bytes]) -> bytes: - """Convert str data to bytes.""" - if isinstance(data, bytes): - return data - elif isinstance(data, str): - return data.encode('utf-8') - else: - raise f'invalid data type {type(data)}' - - -def to_str(data: Union[str, bytes]) -> str: - """Convert bytes data to str.""" - if isinstance(data, str): - return data - elif isinstance(data, bytes): - return data.decode('utf-8') - else: - raise f'invalid data type {type(data)}' - - -# Data validation helpers -def validateNotNone(**kwargs: Optional[str]): - for field_name, value in kwargs.items(): - if value is None: - raise ValueError(f'{field_name} name cannot be None') - - -def validateNotBlankString(**kwargs: Optional[str]): - for field_name, value in kwargs.items(): - if not value or not value.strip(): - raise ValueError(f'{field_name} name cannot be empty or blank') - - -class WorkflowRuntimeStatus(Enum): - UNKNOWN = 'Unknown' - RUNNING = 'Running' - COMPLETED = 'Completed' - FAILED = 'Failed' - TERMINATED = 'Terminated' - PENDING = 'Pending' - SUSPENDED = 'Suspended' - - -# Will return the enum entry if it is present, otherwise returns "unknown" -def getWorkflowRuntimeStatus(inputString): - try: - return WorkflowRuntimeStatus[inputString].value - except KeyError: - return WorkflowRuntimeStatus.UNKNOWN +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from typing import Dict, List, Union, Tuple, Optional +from enum import Enum +from google.protobuf.any_pb2 import Any as GrpcAny +from google.protobuf.message import Message as GrpcMessage + +MetadataDict = Dict[str, List[Union[bytes, str]]] +MetadataTuple = Tuple[Tuple[str, Union[bytes, str]], ...] + + +def tuple_to_dict(tupledata: MetadataTuple) -> MetadataDict: + """Converts tuple to dict. + + Args: + tupledata (tuple): tuple storing metadata + + Returns: + A dict which is converted from tuple + """ + + d: MetadataDict = {} + for k, v in tupledata: # type: ignore + d.setdefault(k, []).append(v) + return d + + +def unpack(data: GrpcAny, message: GrpcMessage) -> None: + """Unpack the serialized protocol buffer message. + + Args: + data (:obj:`google.protobuf.message.Any`): the serialized protocol buffer message. + message (:obj:`google.protobuf.message.Message`): the protocol buffer message object + to which the response data is deserialized. + + Raises: + ValueError: message is not protocol buffer message object or message's type is not + matched with the response data type + """ + if not isinstance(message, GrpcMessage): + raise ValueError('output message is not protocol buffer message object') + if not data.Is(message.DESCRIPTOR): + raise ValueError(f'invalid type. serialized message type: {data.type_url}') + data.Unpack(message) + + +def to_bytes(data: Union[str, bytes]) -> bytes: + """Convert str data to bytes.""" + if isinstance(data, bytes): + return data + elif isinstance(data, str): + return data.encode('utf-8') + else: + raise f'invalid data type {type(data)}' + + +def to_str(data: Union[str, bytes]) -> str: + """Convert bytes data to str.""" + if isinstance(data, str): + return data + elif isinstance(data, bytes): + return data.decode('utf-8') + else: + raise f'invalid data type {type(data)}' + + +# Data validation helpers +def validateNotNone(**kwargs: Optional[str]): + for field_name, value in kwargs.items(): + if value is None: + raise ValueError(f'{field_name} name cannot be None') + + +def validateNotBlankString(**kwargs: Optional[str]): + for field_name, value in kwargs.items(): + if not value or not value.strip(): + raise ValueError(f'{field_name} name cannot be empty or blank') + + +class WorkflowRuntimeStatus(Enum): + UNKNOWN = 'Unknown' + RUNNING = 'Running' + COMPLETED = 'Completed' + FAILED = 'Failed' + TERMINATED = 'Terminated' + PENDING = 'Pending' + SUSPENDED = 'Suspended' + + +# Will return the enum entry if it is present, otherwise returns "unknown" +def getWorkflowRuntimeStatus(inputString): + try: + return WorkflowRuntimeStatus[inputString].value + except KeyError: + return WorkflowRuntimeStatus.UNKNOWN diff --git a/dapr/clients/grpc/_request.py b/dapr/clients/grpc/_request.py index 0f149e1f9..1601af9da 100644 --- a/dapr/clients/grpc/_request.py +++ b/dapr/clients/grpc/_request.py @@ -1,420 +1,420 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import io -from enum import Enum -from typing import Dict, Optional, Union - -from google.protobuf.any_pb2 import Any as GrpcAny -from google.protobuf.message import Message as GrpcMessage -from dapr.proto import api_v1, common_v1 - -from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.clients.grpc._helpers import ( - MetadataDict, - MetadataTuple, - tuple_to_dict, - to_bytes, - to_str, - unpack, -) - - -class DaprRequest: - """A base class for Dapr Request. - - This is the base class for Dapr Request. User can get the metadata. - - Attributes: - metadata(dict): A dict to include the headers from Dapr Request. - """ - - def __init__(self, metadata: MetadataTuple = ()): - self.metadata = metadata # type: ignore - - @property - def metadata(self) -> MetadataDict: - """Get metadata from :class:`DaprRequest`.""" - return self.get_metadata(as_dict=True) # type: ignore - - @metadata.setter - def metadata(self, val) -> None: - """Sets metadata.""" - if not isinstance(val, tuple): - raise ValueError('val is not tuple') - self._metadata = val - - def get_metadata(self, as_dict: bool = False) -> Union[MetadataDict, MetadataTuple]: - """Gets metadata from the request. - - Args: - as_dict (bool): dict type metadata if as_dict is True. Otherwise, return - tuple metadata. - - Returns: - dict or tuple: request metadata. - """ - if as_dict: - return tuple_to_dict(self._metadata) - return self._metadata - - -class InvokeMethodRequest(DaprRequest): - """A request data representation for invoke_method API. - - This stores the request data with the proper serialization. This seralizes - data to :obj:`google.protobuf.any_pb2.Any` if data is the type of protocol - buffer message. - - Attributes: - metadata(dict): A dict to include the headers from Dapr Request. - data (str, bytes, GrpcAny, GrpcMessage, optional): the serialized data - for invoke_method request. - content_type (str, optional): the content type of data which is valid - only for bytes array data. - """ - - HTTP_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE'] - - def __init__( - self, - data: Union[str, bytes, GrpcAny, GrpcMessage, None] = None, - content_type: Optional[str] = None, - ): - """Inits InvokeMethodRequestData with data and content_type. - - Args: - data (bytes, str, GrpcAny, GrpcMessage, optional): the data - which is used for invoke_method request. - content_type (str): the content_type of data when the data is bytes. - The default content type is application/json. - - Raises: - ValueError: data is not supported. - """ - super(InvokeMethodRequest, self).__init__(()) - - self._content_type = content_type - self._http_verb = None - self._http_querystring: Dict[str, str] = {} - - self.set_data(data) - - # Set content_type to application/json type if content_type - # is not given and date is bytes or str type. - if not self.is_proto() and not content_type: - self.content_type = DEFAULT_JSON_CONTENT_TYPE - - @property - def http_verb(self) -> Optional[str]: - """Gets HTTP method in Dapr invocation request.""" - return self._http_verb - - @http_verb.setter - def http_verb(self, val: Optional[str]) -> None: - """Sets HTTP method to Dapr invocation request.""" - if val not in self.HTTP_METHODS: - raise ValueError(f'{val} is the invalid HTTP verb.') - self._http_verb = val - - @property - def http_querystring(self) -> Dict[str, str]: - """Gets HTTP querystring as dict.""" - return self._http_querystring - - def is_http(self) -> bool: - """Return true if this request is http compatible.""" - return hasattr(self, '_http_verb') and not (not self._http_verb) - - @property - def proto(self) -> GrpcAny: - """Gets raw data as proto any type.""" - return self._data - - def is_proto(self) -> bool: - """Returns true if data is protocol-buffer serialized.""" - return hasattr(self, '_data') and self._data.type_url != '' - - def pack(self, val: Union[GrpcAny, GrpcMessage]) -> None: - """Serializes protocol buffer message. - - Args: - message (:class:`GrpcMessage`, :class:`GrpcAny`): the protocol buffer message object - - Raises: - ValueError: message is neither GrpcAny nor GrpcMessage. - """ - if isinstance(val, GrpcAny): - self._data = val - elif isinstance(val, GrpcMessage): - self._data = GrpcAny() - self._data.Pack(val) - else: - raise ValueError('invalid data type') - - def unpack(self, message: GrpcMessage) -> None: - """Deserializes the serialized protocol buffer message. - - Args: - message (:obj:`GrpcMessage`): the protocol buffer message object - to which the response data is deserialized. - - Raises: - ValueError: message is not protocol buffer message object or message's type is not - matched with the response data type - """ - unpack(self.proto, message) - - @property - def data(self) -> bytes: - """Gets request data as bytes.""" - if self.is_proto(): - raise ValueError('data is protocol buffer message object.') - return self._data.value - - @data.setter - def data(self, val: Union[str, bytes]) -> None: - """Sets str or bytes type data to request data.""" - self.set_data(to_bytes(val)) - - def set_data(self, val: Union[str, bytes, GrpcAny, GrpcMessage, None]) -> None: - """Sets data to request data.""" - if val is None: - self._data = GrpcAny() - elif isinstance(val, (bytes, str)): - self._data = GrpcAny(value=to_bytes(val)) - elif isinstance(val, (GrpcAny, GrpcMessage)): - self.pack(val) - else: - raise ValueError(f'invalid data type {type(val)}') - - def text(self) -> str: - """Gets the request data as str.""" - return to_str(self.data) - - @property - def content_type(self) -> Optional[str]: - """Gets content_type for bytes data.""" - return self._content_type - - @content_type.setter - def content_type(self, val: Optional[str]) -> None: - """Sets content type for bytes data.""" - self._content_type = val - - -class BindingRequest(DaprRequest): - """A request data representation for invoke_binding API. - - This stores the request data and metadata with the proper serialization. - This seralizes data to bytes and metadata to a dictionary of key value pairs. - - Attributes: - data (bytes): the data which is used for invoke_binding request. - metadata (Dict[str, str]): the metadata sent to the binding. - """ - - def __init__(self, data: Union[str, bytes], binding_metadata: Dict[str, str] = {}): - """Inits BindingRequest with data and metadata if given. - - Args: - data (bytes, str): the data which is used for invoke_binding request. - binding_metadata (tuple, optional): the metadata to be sent to the binding. - - Raises: - ValueError: data is not bytes or str. - """ - super(BindingRequest, self).__init__(()) - self.data = data # type: ignore - self._binding_metadata = binding_metadata - - @property - def data(self) -> bytes: - """Gets request data as bytes.""" - return self._data - - @data.setter - def data(self, val: Union[str, bytes]) -> None: - """Sets str or bytes type data to request data.""" - self._data = to_bytes(val) - - def text(self) -> str: - """Gets the request data as str.""" - return to_str(self.data) - - @property - def binding_metadata(self): - """Gets the metadata for output binding.""" - return self._binding_metadata - - -class TransactionOperationType(Enum): - """Represents the type of operation for a Dapr Transaction State Api Call""" - - upsert = 'upsert' - delete = 'delete' - - -class TransactionalStateOperation: - """An upsert or delete operation for a state transaction, 'upsert' by default. - - Attributes: - key (str): state's key. - data (Union[bytes, str]): state's data. - etag (str): state's etag. - operation_type (TransactionOperationType): operation to be performed. - """ - - def __init__( - self, - key: str, - data: Union[bytes, str], - etag: Optional[str] = None, - operation_type: TransactionOperationType = TransactionOperationType.upsert, - ): - """Initializes TransactionalStateOperation item from - :obj:`runtime_v1.TransactionalStateOperation`. - - Args: - key (str): state's key. - data (Union[bytes, str]): state's data. - etag (str): state's etag. - operationType (Optional[TransactionOperationType]): operation to be performed. - - Raises: - ValueError: data is not bytes or str. - """ - if not isinstance(data, (bytes, str)): - raise ValueError(f'invalid type for data {type(data)}') - - self._key = key - self._data = data # type: ignore - self._etag = etag - self._operation_type = operation_type - - @property - def key(self) -> str: - """Gets key.""" - return self._key - - @property - def data(self) -> Union[bytes, str]: - """Gets raw data.""" - return self._data - - @property - def etag(self) -> Optional[str]: - """Gets etag.""" - return self._etag - - @property - def operation_type(self) -> TransactionOperationType: - """Gets etag.""" - return self._operation_type - - -class EncryptRequestIterator(DaprRequest): - """An iterator for cryptography encrypt API requests. - - This reads data from a given stream by chunks and converts it to an iterator of - cryptography encrypt API requests. - This iterator will be used for encrypt gRPC bidirectional streaming requests. - """ - - def __init__( - self, - data: Union[str, bytes], - options: EncryptOptions, - ): - """Initialize EncryptRequestIterator with data and encryption options. - - Args: - data (Union[str, bytes]): data to be encrypted - options (EncryptOptions): encryption options - """ - self.data = io.BytesIO(to_bytes(data)) - self.options = options.get_proto() - self.buffer_size = 2 << 10 # 2KiB - self.seq = 0 - - def __iter__(self): - """Returns the iterator object itself.""" - return self - - def __next__(self): - """Read the next chunk of data from the input stream and create a gRPC stream request.""" - # Read data from the input stream, in chunks of up to 2KiB - # Send the data until we reach the end of the input stream - chunk = self.data.read(self.buffer_size) - if not chunk: - raise StopIteration - - payload = common_v1.StreamPayload(data=chunk, seq=self.seq) - if self.seq == 0: - # If this is the first chunk, add the options - request_proto = api_v1.EncryptRequest(payload=payload, options=self.options) - else: - request_proto = api_v1.EncryptRequest(payload=payload) - - self.seq += 1 - return request_proto - - -class DecryptRequestIterator(DaprRequest): - """An iterator for cryptography decrypt API requests. - - This reads data from a given stream by chunks and converts it to an iterator of decrypt - cryptography API requests. - This iterator will be used for decrypt gRPC bidirectional streaming requests. - """ - - def __init__( - self, - data: Union[str, bytes], - options: DecryptOptions, - ): - """Initialize DecryptRequestIterator with data and decryption options. - - Args: - data (Union[str, bytes]): data to be decrypted - options (DecryptOptions): decryption options - """ - self.data = io.BytesIO(to_bytes(data)) - self.options = options.get_proto() - self.buffer_size = 2 << 10 # 2KiB - self.seq = 0 - - def __iter__(self): - """Returns the iterator object itself.""" - return self - - def __next__(self): - """Read the next chunk of data from the input stream and create a gRPC stream request.""" - # Read data from the input stream, in chunks of up to 2KiB - # Send the data until we reach the end of the input stream - chunk = self.data.read(self.buffer_size) - if not chunk: - raise StopIteration - - payload = common_v1.StreamPayload(data=chunk, seq=self.seq) - if self.seq == 0: - # If this is the first chunk, add the options - request_proto = api_v1.DecryptRequest(payload=payload, options=self.options) - else: - request_proto = api_v1.DecryptRequest(payload=payload) - - self.seq += 1 - return request_proto +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import io +from enum import Enum +from typing import Dict, Optional, Union + +from google.protobuf.any_pb2 import Any as GrpcAny +from google.protobuf.message import Message as GrpcMessage +from dapr.proto import api_v1, common_v1 + +from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.clients.grpc._helpers import ( + MetadataDict, + MetadataTuple, + tuple_to_dict, + to_bytes, + to_str, + unpack, +) + + +class DaprRequest: + """A base class for Dapr Request. + + This is the base class for Dapr Request. User can get the metadata. + + Attributes: + metadata(dict): A dict to include the headers from Dapr Request. + """ + + def __init__(self, metadata: MetadataTuple = ()): + self.metadata = metadata # type: ignore + + @property + def metadata(self) -> MetadataDict: + """Get metadata from :class:`DaprRequest`.""" + return self.get_metadata(as_dict=True) # type: ignore + + @metadata.setter + def metadata(self, val) -> None: + """Sets metadata.""" + if not isinstance(val, tuple): + raise ValueError('val is not tuple') + self._metadata = val + + def get_metadata(self, as_dict: bool = False) -> Union[MetadataDict, MetadataTuple]: + """Gets metadata from the request. + + Args: + as_dict (bool): dict type metadata if as_dict is True. Otherwise, return + tuple metadata. + + Returns: + dict or tuple: request metadata. + """ + if as_dict: + return tuple_to_dict(self._metadata) + return self._metadata + + +class InvokeMethodRequest(DaprRequest): + """A request data representation for invoke_method API. + + This stores the request data with the proper serialization. This seralizes + data to :obj:`google.protobuf.any_pb2.Any` if data is the type of protocol + buffer message. + + Attributes: + metadata(dict): A dict to include the headers from Dapr Request. + data (str, bytes, GrpcAny, GrpcMessage, optional): the serialized data + for invoke_method request. + content_type (str, optional): the content type of data which is valid + only for bytes array data. + """ + + HTTP_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE'] + + def __init__( + self, + data: Union[str, bytes, GrpcAny, GrpcMessage, None] = None, + content_type: Optional[str] = None, + ): + """Inits InvokeMethodRequestData with data and content_type. + + Args: + data (bytes, str, GrpcAny, GrpcMessage, optional): the data + which is used for invoke_method request. + content_type (str): the content_type of data when the data is bytes. + The default content type is application/json. + + Raises: + ValueError: data is not supported. + """ + super(InvokeMethodRequest, self).__init__(()) + + self._content_type = content_type + self._http_verb = None + self._http_querystring: Dict[str, str] = {} + + self.set_data(data) + + # Set content_type to application/json type if content_type + # is not given and date is bytes or str type. + if not self.is_proto() and not content_type: + self.content_type = DEFAULT_JSON_CONTENT_TYPE + + @property + def http_verb(self) -> Optional[str]: + """Gets HTTP method in Dapr invocation request.""" + return self._http_verb + + @http_verb.setter + def http_verb(self, val: Optional[str]) -> None: + """Sets HTTP method to Dapr invocation request.""" + if val not in self.HTTP_METHODS: + raise ValueError(f'{val} is the invalid HTTP verb.') + self._http_verb = val + + @property + def http_querystring(self) -> Dict[str, str]: + """Gets HTTP querystring as dict.""" + return self._http_querystring + + def is_http(self) -> bool: + """Return true if this request is http compatible.""" + return hasattr(self, '_http_verb') and not (not self._http_verb) + + @property + def proto(self) -> GrpcAny: + """Gets raw data as proto any type.""" + return self._data + + def is_proto(self) -> bool: + """Returns true if data is protocol-buffer serialized.""" + return hasattr(self, '_data') and self._data.type_url != '' + + def pack(self, val: Union[GrpcAny, GrpcMessage]) -> None: + """Serializes protocol buffer message. + + Args: + message (:class:`GrpcMessage`, :class:`GrpcAny`): the protocol buffer message object + + Raises: + ValueError: message is neither GrpcAny nor GrpcMessage. + """ + if isinstance(val, GrpcAny): + self._data = val + elif isinstance(val, GrpcMessage): + self._data = GrpcAny() + self._data.Pack(val) + else: + raise ValueError('invalid data type') + + def unpack(self, message: GrpcMessage) -> None: + """Deserializes the serialized protocol buffer message. + + Args: + message (:obj:`GrpcMessage`): the protocol buffer message object + to which the response data is deserialized. + + Raises: + ValueError: message is not protocol buffer message object or message's type is not + matched with the response data type + """ + unpack(self.proto, message) + + @property + def data(self) -> bytes: + """Gets request data as bytes.""" + if self.is_proto(): + raise ValueError('data is protocol buffer message object.') + return self._data.value + + @data.setter + def data(self, val: Union[str, bytes]) -> None: + """Sets str or bytes type data to request data.""" + self.set_data(to_bytes(val)) + + def set_data(self, val: Union[str, bytes, GrpcAny, GrpcMessage, None]) -> None: + """Sets data to request data.""" + if val is None: + self._data = GrpcAny() + elif isinstance(val, (bytes, str)): + self._data = GrpcAny(value=to_bytes(val)) + elif isinstance(val, (GrpcAny, GrpcMessage)): + self.pack(val) + else: + raise ValueError(f'invalid data type {type(val)}') + + def text(self) -> str: + """Gets the request data as str.""" + return to_str(self.data) + + @property + def content_type(self) -> Optional[str]: + """Gets content_type for bytes data.""" + return self._content_type + + @content_type.setter + def content_type(self, val: Optional[str]) -> None: + """Sets content type for bytes data.""" + self._content_type = val + + +class BindingRequest(DaprRequest): + """A request data representation for invoke_binding API. + + This stores the request data and metadata with the proper serialization. + This seralizes data to bytes and metadata to a dictionary of key value pairs. + + Attributes: + data (bytes): the data which is used for invoke_binding request. + metadata (Dict[str, str]): the metadata sent to the binding. + """ + + def __init__(self, data: Union[str, bytes], binding_metadata: Dict[str, str] = {}): + """Inits BindingRequest with data and metadata if given. + + Args: + data (bytes, str): the data which is used for invoke_binding request. + binding_metadata (tuple, optional): the metadata to be sent to the binding. + + Raises: + ValueError: data is not bytes or str. + """ + super(BindingRequest, self).__init__(()) + self.data = data # type: ignore + self._binding_metadata = binding_metadata + + @property + def data(self) -> bytes: + """Gets request data as bytes.""" + return self._data + + @data.setter + def data(self, val: Union[str, bytes]) -> None: + """Sets str or bytes type data to request data.""" + self._data = to_bytes(val) + + def text(self) -> str: + """Gets the request data as str.""" + return to_str(self.data) + + @property + def binding_metadata(self): + """Gets the metadata for output binding.""" + return self._binding_metadata + + +class TransactionOperationType(Enum): + """Represents the type of operation for a Dapr Transaction State Api Call""" + + upsert = 'upsert' + delete = 'delete' + + +class TransactionalStateOperation: + """An upsert or delete operation for a state transaction, 'upsert' by default. + + Attributes: + key (str): state's key. + data (Union[bytes, str]): state's data. + etag (str): state's etag. + operation_type (TransactionOperationType): operation to be performed. + """ + + def __init__( + self, + key: str, + data: Union[bytes, str], + etag: Optional[str] = None, + operation_type: TransactionOperationType = TransactionOperationType.upsert, + ): + """Initializes TransactionalStateOperation item from + :obj:`runtime_v1.TransactionalStateOperation`. + + Args: + key (str): state's key. + data (Union[bytes, str]): state's data. + etag (str): state's etag. + operationType (Optional[TransactionOperationType]): operation to be performed. + + Raises: + ValueError: data is not bytes or str. + """ + if not isinstance(data, (bytes, str)): + raise ValueError(f'invalid type for data {type(data)}') + + self._key = key + self._data = data # type: ignore + self._etag = etag + self._operation_type = operation_type + + @property + def key(self) -> str: + """Gets key.""" + return self._key + + @property + def data(self) -> Union[bytes, str]: + """Gets raw data.""" + return self._data + + @property + def etag(self) -> Optional[str]: + """Gets etag.""" + return self._etag + + @property + def operation_type(self) -> TransactionOperationType: + """Gets etag.""" + return self._operation_type + + +class EncryptRequestIterator(DaprRequest): + """An iterator for cryptography encrypt API requests. + + This reads data from a given stream by chunks and converts it to an iterator of + cryptography encrypt API requests. + This iterator will be used for encrypt gRPC bidirectional streaming requests. + """ + + def __init__( + self, + data: Union[str, bytes], + options: EncryptOptions, + ): + """Initialize EncryptRequestIterator with data and encryption options. + + Args: + data (Union[str, bytes]): data to be encrypted + options (EncryptOptions): encryption options + """ + self.data = io.BytesIO(to_bytes(data)) + self.options = options.get_proto() + self.buffer_size = 2 << 10 # 2KiB + self.seq = 0 + + def __iter__(self): + """Returns the iterator object itself.""" + return self + + def __next__(self): + """Read the next chunk of data from the input stream and create a gRPC stream request.""" + # Read data from the input stream, in chunks of up to 2KiB + # Send the data until we reach the end of the input stream + chunk = self.data.read(self.buffer_size) + if not chunk: + raise StopIteration + + payload = common_v1.StreamPayload(data=chunk, seq=self.seq) + if self.seq == 0: + # If this is the first chunk, add the options + request_proto = api_v1.EncryptRequest(payload=payload, options=self.options) + else: + request_proto = api_v1.EncryptRequest(payload=payload) + + self.seq += 1 + return request_proto + + +class DecryptRequestIterator(DaprRequest): + """An iterator for cryptography decrypt API requests. + + This reads data from a given stream by chunks and converts it to an iterator of decrypt + cryptography API requests. + This iterator will be used for decrypt gRPC bidirectional streaming requests. + """ + + def __init__( + self, + data: Union[str, bytes], + options: DecryptOptions, + ): + """Initialize DecryptRequestIterator with data and decryption options. + + Args: + data (Union[str, bytes]): data to be decrypted + options (DecryptOptions): decryption options + """ + self.data = io.BytesIO(to_bytes(data)) + self.options = options.get_proto() + self.buffer_size = 2 << 10 # 2KiB + self.seq = 0 + + def __iter__(self): + """Returns the iterator object itself.""" + return self + + def __next__(self): + """Read the next chunk of data from the input stream and create a gRPC stream request.""" + # Read data from the input stream, in chunks of up to 2KiB + # Send the data until we reach the end of the input stream + chunk = self.data.read(self.buffer_size) + if not chunk: + raise StopIteration + + payload = common_v1.StreamPayload(data=chunk, seq=self.seq) + if self.seq == 0: + # If this is the first chunk, add the options + request_proto = api_v1.DecryptRequest(payload=payload, options=self.options) + else: + request_proto = api_v1.DecryptRequest(payload=payload) + + self.seq += 1 + return request_proto diff --git a/dapr/clients/grpc/_response.py b/dapr/clients/grpc/_response.py index 2beb1a2f5..435b98e4b 100644 --- a/dapr/clients/grpc/_response.py +++ b/dapr/clients/grpc/_response.py @@ -1,1072 +1,1072 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from __future__ import annotations - -import contextlib -import json -import threading -from datetime import datetime -from enum import Enum -from typing import ( - Callable, - Dict, - List, - Optional, - Text, - Union, - Sequence, - TYPE_CHECKING, - NamedTuple, - Generator, - TypeVar, - Generic, - Mapping, -) - -from google.protobuf.any_pb2 import Any as GrpcAny -from google.protobuf.message import Message as GrpcMessage - -from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE -from dapr.clients.grpc._helpers import ( - MetadataDict, - MetadataTuple, - to_bytes, - to_str, - tuple_to_dict, - unpack, - WorkflowRuntimeStatus, -) -from dapr.proto import api_service_v1, api_v1, appcallback_v1, common_v1 - -# Avoid circular import dependency by only importing DaprGrpcClient -# for type checking -if TYPE_CHECKING: - from dapr.clients.grpc.client import DaprGrpcClient - -TCryptoResponse = TypeVar( - 'TCryptoResponse', bound=Union[api_v1.EncryptResponse, api_v1.DecryptResponse] -) - - -class DaprResponse: - """A base class for Dapr Response. - - This is the base class for Dapr Response. User can get the headers as a dict. - - Attributes: - headers(dict): A dict to include the headers from Dapr gRPC Response. - """ - - def __init__(self, headers: MetadataTuple = ()): - """Inits DapResponse with headers and trailers. - - Args: - headers (tuple, optional): the tuple for the headers from response. - """ - self.headers = headers # type: ignore - - @property - def headers(self) -> MetadataDict: - """Gets headers as a dict.""" - return self.get_headers(as_dict=True) # type: ignore - - @headers.setter - def headers(self, val: MetadataTuple) -> None: - """Sets response headers.""" - self._headers = val - - def get_headers(self, as_dict: bool = False) -> Union[MetadataDict, MetadataTuple]: - """Gets headers from the response. - - Args: - as_dict (bool): dict type headers if as_dict is True. Otherwise, return - tuple headers. - - Returns: - dict or tuple: response headers. - """ - if as_dict: - return tuple_to_dict(self._headers) - return self._headers - - -class InvokeMethodResponse(DaprResponse): - """The response of invoke_method API. - - This inherits from DaprResponse and has the helpers to handle bytes array - and protocol buffer data. - - Attributes: - headers (tuple, optional): the tuple for the headers from response. - data (str, bytes, GrpcAny, GrpcMessage, optional): the serialized protocol - buffer raw message - content (bytes, optional): bytes data if response data is not serialized - protocol buffer message - content_type (str, optional): the type of `content` - status_code (int, optional): the status code of the response - """ - - def __init__( - self, - data: Union[str, bytes, GrpcAny, GrpcMessage, None] = None, - content_type: Optional[str] = None, - headers: MetadataTuple = (), - status_code: Optional[int] = None, - ): - """Initializes InvokeMethodReponse from :obj:`common_v1.InvokeResponse`. - - Args: - data (str, bytes, GrpcAny, GrpcMessage, optional): the response data - from Dapr response - content_type (str, optional): the content type of the bytes data - headers (tuple, optional): the headers from Dapr gRPC response - status_code (int, optional): the status code of the response - """ - super(InvokeMethodResponse, self).__init__(headers) - self._content_type = content_type - self.set_data(data) - self._status_code = status_code - - # Set content_type to application/json type if content_type - # is not given and date is bytes or str type. - if not self.is_proto() and not content_type: - self.content_type = DEFAULT_JSON_CONTENT_TYPE - - @property - def proto(self) -> GrpcAny: - """Gets raw serialized protocol buffer message. - - Raises: - ValueError: data is not protocol buffer message object - """ - return self._data - - def is_proto(self) -> bool: - """Returns True if the response data is the serialized protocol buffer message.""" - return hasattr(self, '_data') and self._data.type_url != '' - - @property - def data(self) -> bytes: - """Gets raw bytes data if the response data content is not serialized - protocol buffer message. - - Raises: - ValueError: the response data is the serialized protocol buffer message - """ - if self.is_proto(): - raise ValueError('data is protocol buffer message object.') - return self._data.value - - @data.setter - def data(self, val: Union[str, bytes]) -> None: - """Sets str or bytes type data to request data.""" - self.set_data(val) - - def set_data(self, val: Union[str, bytes, GrpcAny, GrpcMessage, None]) -> None: - """Sets data to request data.""" - if val is None: - self._data = GrpcAny() - elif isinstance(val, (bytes, str)): - self._data = GrpcAny(value=to_bytes(val)) - elif isinstance(val, (GrpcAny, GrpcMessage)): - self.pack(val) - else: - raise ValueError(f'invalid data type {type(val)}') - - def text(self) -> str: - """Gets content as str if the response data content is not serialized - protocol buffer message. - - Raises: - ValueError: the response data is the serialized protocol buffer message - """ - return to_str(self.data) - - def json(self) -> Dict[str, object]: - """Gets the content as json if the response data content is not a serialized - protocol buffer message. - - Returns: - str: [description] - """ - return json.loads(to_str(self.data)) - - @property - def content_type(self) -> Optional[str]: - """Gets the content type of content attribute.""" - return self._content_type - - @content_type.setter - def content_type(self, val: Optional[str]) -> None: - """Sets content type for bytes data.""" - self._content_type = val - - def pack(self, val: Union[GrpcAny, GrpcMessage]) -> None: - """Serializes protocol buffer message. - - Args: - message (:class:`GrpcMessage`, :class:`GrpcAny`): the protocol buffer message object - - Raises: - ValueError: message is neither GrpcAny nor GrpcMessage. - """ - if isinstance(val, GrpcAny): - self._data = val - elif isinstance(val, GrpcMessage): - self._data = GrpcAny() - self._data.Pack(val) - else: - raise ValueError('invalid data type') - - @property - def status_code(self) -> Optional[int]: - """Gets the response status code attribute.""" - return self._status_code - - @status_code.setter - def status_code(self, val: Optional[int]) -> None: - """Sets the response status code.""" - self._status_code = val - - def unpack(self, message: GrpcMessage) -> None: - """Deserializes the serialized protocol buffer message. - - Args: - message (:class:`GrpcMessage`): the protocol buffer message object - to which the response data is deserialized. - - Raises: - ValueError: message is not protocol buffer message object or message's type is not - matched with the response data type - """ - - if self.content_type is not None and self.content_type.lower() == 'application/x-protobuf': - message.ParseFromString(self.data) - return - - unpack(self.proto, message) - - -class BindingResponse(DaprResponse): - """The response of invoke_binding API. - - This inherits from DaprResponse and has the helpers to handle bytes array data. - - Attributes: - data (bytes): the data in response from the invoke_binding call - binding_metadata (Dict[str, str]): metadata sent as a reponse by the binding - """ - - def __init__( - self, - data: Union[bytes, str], - binding_metadata: Dict[str, str] = {}, - headers: MetadataTuple = (), - ): - """Initializes InvokeBindingReponse from :obj:`runtime_v1.InvokeBindingResponse`. - - Args: - data (bytes): the data in response from the invoke_binding call - binding_metadata (Dict[str, str]): metadata sent as a reponse by the binding - headers (Tuple, optional): the headers from Dapr gRPC response - - Raises: - ValueError: if the response data is not :class:`google.protobuf.any_pb2.Any` - object. - """ - super(BindingResponse, self).__init__(headers) - self.data = data # type: ignore - self._metadata = binding_metadata - - def text(self) -> str: - """Gets content as str.""" - return to_str(self._data) - - def json(self) -> Dict[str, object]: - """Gets content as deserialized JSON dictionary.""" - return json.loads(to_str(self._data)) - - @property - def data(self) -> bytes: - """Gets raw bytes data.""" - return self._data - - @data.setter - def data(self, val: Union[bytes, str]) -> None: - """Sets str or bytes type data to request data.""" - self._data = to_bytes(val) - - @property - def binding_metadata(self) -> Dict[str, str]: - """Gets the metadata in the response.""" - return self._metadata - - -class GetSecretResponse(DaprResponse): - """The response of get_secret API. - - This inherits from DaprResponse - - Attributes: - secret (Dict[str, str]): secret received from response - """ - - def __init__(self, secret: Dict[str, str], headers: MetadataTuple = ()): - """Initializes GetSecretReponse from :obj:`dapr_v1.GetSecretResponse`. - - Args: - secret (Dict[Str, str]): the secret from Dapr response - headers (Tuple, optional): the headers from Dapr gRPC response - """ - super(GetSecretResponse, self).__init__(headers) - self._secret = secret - - @property - def secret(self) -> Dict[str, str]: - """Gets secret as a dict.""" - return self._secret - - -class GetBulkSecretResponse(DaprResponse): - """The response of get_bulk_secret API. - - This inherits from DaprResponse - - Attributes: - secret (Dict[str, Dict[str, str]]): secret received from response - """ - - def __init__(self, secrets: Dict[str, Dict[str, str]], headers: MetadataTuple = ()): - """Initializes GetBulkSecretReponse from :obj:`dapr_v1.GetBulkSecretResponse`. - - Args: - secrets (Dict[Str, Dict[str, str]]): the secret from Dapr response - headers (Tuple, optional): the headers from Dapr gRPC response - """ - super(GetBulkSecretResponse, self).__init__(headers) - self._secrets = secrets - - @property - def secrets(self) -> Dict[str, Dict[str, str]]: - """Gets secrets as a dict.""" - return self._secrets - - -class StateResponse(DaprResponse): - """The response of get_state API. - - This inherits from DaprResponse - - Attributes: - data (bytes): the data in response from the get_state call - etag (str): state's etag. - headers (Tuple, optional): the headers from Dapr gRPC response - """ - - def __init__(self, data: Union[bytes, str], etag: str = '', headers: MetadataTuple = ()): - """Initializes StateResponse from :obj:`runtime_v1.GetStateResponse`. - - Args: - data (bytes): the data in response from the get_state call - etag (str): state's etag. - headers (Tuple, optional): the headers from Dapr gRPC response - - Raises: - ValueError: if the response data is not :class:`google.protobuf.any_pb2.Any` - object. - """ - super(StateResponse, self).__init__(headers) - self.data = data # type: ignore - self._etag = etag - - def text(self) -> str: - """Gets content as str.""" - return to_str(self._data) - - def json(self) -> Dict[str, object]: - """Gets content as deserialized JSON dictionary.""" - return json.loads(to_str(self._data)) - - @property - def etag(self) -> str: - """Gets etag.""" - return self._etag - - @property - def data(self) -> bytes: - """Gets raw bytes data.""" - return self._data - - @data.setter - def data(self, val: Union[bytes, str]) -> None: - """Sets str or bytes type data to request data.""" - self._data = to_bytes(val) - - -class BulkStateItem: - """A state item from bulk_get_state API. - - Attributes: - key (str): state's key. - data (Union[bytes, str]): state's data. - etag (str): state's etag. - error (str): error when state was retrieved - """ - - def __init__(self, key: str, data: Union[bytes, str], etag: str = '', error: str = ''): - """Initializes BulkStateItem item from :obj:`runtime_v1.BulkStateItem`. - - Args: - key (str): state's key. - data (Union[bytes, str]): state's data. - etag (str): state's etag. - error (str): error when state was retrieved - """ - self._key = key - self._data = data # type: ignore - self._etag = etag - self._error = error - - def text(self) -> str: - """Gets content as str.""" - return to_str(self._data) - - def json(self) -> Dict[str, object]: - """Gets content as deserialized JSON dictionary.""" - return json.loads(to_str(self._data)) - - @property - def key(self) -> str: - """Gets key.""" - return self._key - - @property - def data(self) -> Union[bytes, str]: - """Gets raw data.""" - return self._data - - @property - def etag(self) -> str: - """Gets etag.""" - return self._etag - - @property - def error(self) -> str: - """Gets error.""" - return self._error - - -class BulkStatesResponse(DaprResponse): - """The response of bulk_get_state API. - - This inherits from DaprResponse - - Attributes: - data (Union[bytes, str]): state's data. - """ - - def __init__(self, items: Sequence[BulkStateItem], headers: MetadataTuple = ()): - """Initializes BulkStatesResponse from :obj:`runtime_v1.GetBulkStateResponse`. - - Args: - items (Sequence[BulkStatesItem]): the items retrieved. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super(BulkStatesResponse, self).__init__(headers) - self._items = items - - @property - def items(self) -> Sequence[BulkStateItem]: - """Gets the items.""" - return self._items - - -class QueryResponseItem: - """A query response item from state store query API. - - Attributes: - key (str): query reponse item's key. - value (bytes): query reponse item's data. - etag (str): query reponse item's etag. - error (str): error when state was retrieved - """ - - def __init__(self, key: str, value: bytes, etag: str = '', error: str = ''): - """Initializes QueryResponseItem item from :obj:`runtime_v1.QueryStateItem`. - - Args: - key (str): query response item's key. - value (bytes): query response item's data. - etag (str): query response item's etag. - error (str): error when state was retrieved - """ - self._key = key - self._value = value - self._etag = etag - self._error = error - - def text(self) -> str: - """Gets value as str.""" - return to_str(self._value) - - def json(self) -> Dict[str, object]: - """Gets value as deserialized JSON dictionary.""" - return json.loads(to_str(self._value)) - - @property - def key(self) -> str: - """Gets key.""" - return self._key - - @property - def value(self) -> bytes: - """Gets raw value.""" - return self._value - - @property - def etag(self) -> str: - """Gets etag.""" - return self._etag - - @property - def error(self) -> str: - """Gets error.""" - return self._error - - -class QueryResponse(DaprResponse): - """The response of state store query API. - - This inherits from DaprResponse - - Attributes: - results (Sequence[QueryResponseItem]): the query results. - token (str): query response token for pagination. - metadata (Dict[str, str]): query response metadata. - """ - - def __init__( - self, - results: Sequence[QueryResponseItem], - token: str = '', - metadata: Dict[str, str] = dict(), - headers: MetadataTuple = (), - ): - """Initializes QueryResponse from :obj:`runtime_v1.QueryStateResponse`. - - Args: - results (Sequence[QueryResponseItem]): the query results. - token (str): query response token for pagination. - metadata (Dict[str, str]): query response metadata. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super(QueryResponse, self).__init__(headers) - self._metadata = metadata - self._results = results - self._token = token - - @property - def results(self) -> Sequence[QueryResponseItem]: - """Gets the query results.""" - return self._results - - @property - def token(self) -> str: - """Gets the query pagination token.""" - return self._token - - @property - def metadata(self) -> Dict[str, str]: - """Gets the query response metadata.""" - return self._metadata - - -class ConfigurationItem: - """A config item from get_configuration API. - - Attributes: - value (Union[bytes, str]): config's value. - version (str): config's version. - metadata (str): metadata - """ - - def __init__(self, value: str, version: str, metadata: Optional[Dict[str, str]] = dict()): - """Initializes ConfigurationItem item from :obj:`runtime_v1.ConfigurationItem`. - - Args: - value (str): config's value. - version (str): config's version. - metadata (Optional[Dict[str, str]] = dict()): metadata - """ - self._value = value - self._version = version - self._metadata = metadata - - def text(self) -> str: - """Gets content as str.""" - return to_str(self._value) - - def json(self) -> Dict[str, object]: - """Gets content as deserialized JSON dictionary.""" - return json.loads(to_str(self._value)) - - @property - def value(self) -> str: - """Gets value.""" - return self._value - - @property - def version(self) -> str: - """Gets version.""" - return self._version - - @property - def metadata(self) -> Optional[Dict[str, str]]: - """Gets metadata.""" - return self._metadata - - -class ConfigurationResponse(DaprResponse): - """The response of get_configuration API. - - This inherits from DaprResponse - - Attributes: - - items (Mapping[Text, ConfigurationItem]): state's data. - """ - - def __init__( - self, items: Mapping[Text, common_v1.ConfigurationItem], headers: MetadataTuple = () - ): - """Initializes ConfigurationResponse from :obj:`runtime_v1.GetConfigurationResponse`. - - Args: - items (Mapping[str, common_v1.ConfigurationItem]): the items retrieved. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super(ConfigurationResponse, self).__init__(headers) - self._items: Dict[Text, ConfigurationItem] = dict() - k: Text - v: common_v1.ConfigurationItem - for k, v in items.items(): - self._items[k] = ConfigurationItem(v.value, v.version, dict(v.metadata)) - - @property - def items(self) -> Dict[Text, ConfigurationItem]: - """Gets the items.""" - return self._items - - -class ConfigurationWatcher: - def __init__(self): - self.store_name = None - self.keys = None - self.event: threading.Event = threading.Event() - self.id: str = '' - - def watch_configuration( - self, - stub: api_service_v1.DaprStub, - store_name: str, - keys: List[str], - handler: Callable[[Text, ConfigurationResponse], None], - config_metadata: Optional[Dict[str, str]] = dict(), - ): - req = api_v1.SubscribeConfigurationRequest( - store_name=store_name, keys=keys, metadata=config_metadata - ) - thread = threading.Thread(target=self._read_subscribe_config, args=(stub, req, handler)) - thread.daemon = True - thread.start() - self.keys = keys - self.store_name = store_name - check = self.event.wait(timeout=5) - if not check: - print(f'Unable to get configuration id for keys {self.keys}') - return None - return self.id - - def _read_subscribe_config( - self, - stub: api_service_v1.DaprStub, - req: api_v1.SubscribeConfigurationRequest, - handler: Callable[[Text, ConfigurationResponse], None], - ): - try: - responses: List[ - api_v1.SubscribeConfigurationResponse - ] = stub.SubscribeConfigurationAlpha1(req) - isFirst = True - for response in responses: - if isFirst: - self.id = response.id - self.event.set() - isFirst = False - if len(response.items) > 0: - handler(response.id, ConfigurationResponse(response.items)) - except Exception: - print(f'{self.store_name} configuration watcher for keys ' f'{self.keys} stopped.') - pass - - -class TopicEventResponseStatus(Enum): - # success is the default behavior: message is acknowledged and not retried - success = appcallback_v1.TopicEventResponse.TopicEventResponseStatus.SUCCESS - retry = appcallback_v1.TopicEventResponse.TopicEventResponseStatus.RETRY - drop = appcallback_v1.TopicEventResponse.TopicEventResponseStatus.DROP - - -class TopicEventResponse(DaprResponse): - """The response of subscribed topic events. - - This inherits from DaprResponse - - Attributes: - status (Union[str, TopicEventResponseStatus]): status of the response - """ - - def __init__( - self, - status: Union[str, TopicEventResponseStatus], - headers: MetadataTuple = (), - ): - """Initializes a TopicEventResponse. - - Args: - status (TopicEventResponseStatus): The status of the response. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super(TopicEventResponse, self).__init__(headers) - values = [e.name for e in TopicEventResponseStatus] - errormsg = f'`status` must be one of {values} or a TopicEventResponseStatus' - - if isinstance(status, str): - try: - status = TopicEventResponseStatus[status.lower()] - except KeyError as e: - raise KeyError(errormsg) from e - if not isinstance(status, TopicEventResponseStatus): - raise ValueError(errormsg) - self._status = status - - @property - def status(self) -> TopicEventResponseStatus: - """Gets the status.""" - return self._status - - -class UnlockResponseStatus(Enum): - success = api_v1.UnlockResponse.Status.SUCCESS - """The Unlock operation for the referred lock was successful.""" - - lock_does_not_exist = api_v1.UnlockResponse.Status.LOCK_DOES_NOT_EXIST - """'The unlock operation failed: the referred lock does not exist.""" - - lock_belongs_to_others = api_v1.UnlockResponse.Status.LOCK_BELONGS_TO_OTHERS - """The unlock operation failed: the referred lock belongs to another owner.""" - - internal_error = api_v1.UnlockResponse.Status.INTERNAL_ERROR - """An internal error happened while handling the Unlock operation""" - - -class UnlockResponse(DaprResponse): - """The response of an unlock operation. - - This inherits from DaprResponse - - Attributes: - status (UnlockResponseStatus): the status of the unlock operation. - """ - - def __init__( - self, - status: UnlockResponseStatus, - headers: MetadataTuple = (), - ): - """Initializes a UnlockResponse. - - Args: - status (UnlockResponseStatus): The status of the response. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super().__init__(headers) - self._status = status - - @property - def status(self) -> UnlockResponseStatus: - """Gets the status.""" - return self._status - - -class TryLockResponse(contextlib.AbstractContextManager, DaprResponse): - """The response of a try_lock operation. - - This inherits from DaprResponse and AbstractContextManager. - - Attributes: - success (bool): the result of the try_lock operation. - """ - - def __init__( - self, - success: bool, - client: DaprGrpcClient, - store_name: str, - resource_id: str, - lock_owner: str, - headers: MetadataTuple = (), - ): - """Initializes a TryLockResponse. - - Args: - success (bool): the result of the try_lock operation. - client (DaprClient): a reference to the dapr client used for the TryLock request. - store_name (str): the lock store name used in the TryLock request. - resource_id (str): the lock key or identifier used in the TryLock request. - lock_owner (str): the lock owner identifier used in the TryLock request. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super().__init__(headers) - self._success = success - self._client = client - self._store_name = store_name - self._resource_id = resource_id - self._lock_owner = lock_owner - - def __bool__(self) -> bool: - return self._success - - @property - def success(self) -> bool: - """Gets the response success status.""" - return self._success - - def __exit__(self, *exc) -> None: - """'Automatically unlocks the lock if this TryLockResponse was used as - a ContextManager / `with` statement. - - Notice: we are not checking the result of the unlock operation. - If this is something you care about it might be wiser creating - your own ContextManager that logs or otherwise raises exceptions - if unlock doesn't return `UnlockResponseStatus.success`. - """ - if self._success: - self._client.unlock(self._store_name, self._resource_id, self._lock_owner) - # else: there is no point unlocking a lock we did not acquire. - - async def __aexit__(self, *exc) -> None: - """'Automatically unlocks the lock if this TryLockResponse was used as - a ContextManager / `with` statement. - - Notice: we are not checking the result of the unlock operation. - If this is something you care about it might be wiser creating - your own ContextManager that logs or otherwise raises exceptions - if unlock doesn't return `UnlockResponseStatus.success`. - """ - if self._success: - await self._client.unlock( - self._store_name, # type: ignore - self._resource_id, - self._lock_owner, - ) - # else: there is no point unlocking a lock we did not acquire. - - async def __aenter__(self) -> 'TryLockResponse': - """Returns self as the context manager object.""" - return self - - -class GetMetadataResponse(DaprResponse): - """GetMetadataResponse is a message that is returned on GetMetadata rpc call.""" - - def __init__( - self, - application_id: str, - active_actors_count: Dict[str, int], - registered_components: Sequence[RegisteredComponents], - extended_metadata: Dict[str, str], - headers: MetadataTuple = (), - ): - """Initializes GetMetadataResponse. - - Args: - application_id (str): The Application ID. - active_actors_count (Dict[str, int]): mapping from the type of - registered actors to their number of running instances. - registered_components (Sequence[RegisteredComponents]): list of - loaded components metadata. - extended_metadata (Dict[str, str]): mapping of custom (extended) - attributes to their respective values. - headers (Tuple, optional): the headers from Dapr gRPC response. - """ - super().__init__(headers) - self._application_id = application_id - self._active_actors_count = active_actors_count - self._registered_components = registered_components - self._extended_metadata = extended_metadata - - @property - def application_id(self) -> str: - """The Application ID.""" - return self._application_id - - @property - def active_actors_count(self) -> Dict[str, int]: - """Mapping from the type of registered actors to their number of running instances.""" - return self._active_actors_count - - @property - def registered_components(self) -> Sequence[RegisteredComponents]: - """List of loaded components metadata.""" - return self._registered_components - - @property - def extended_metadata(self) -> Dict[str, str]: - """Mapping of custom (extended) attributes to their respective values.""" - return self._extended_metadata - - -class GetWorkflowResponse: - """The response of get_workflow operation.""" - - def __init__( - self, - instance_id: str, - workflow_name: str, - created_at: datetime, - last_updated_at: str, - runtime_status: WorkflowRuntimeStatus, - properties: Dict[str, str] = {}, - ): - """Initializes a GetWorkflowResponse. - - Args: - instance_id (str): the instance ID assocated with this response. - workflow_name (str): the name of the workflow that was started. - created_at (datetime): the time at which the workflow started executing. - last_updated_at (datetime): the time at which the workflow was last updated. - runtime_status (WorkflowRuntimeStatus): the current runtime status of the workflow. - properties (Dict[str, str]): properties sent as a reponse by the workflow. - """ - self.instance_id = instance_id - self.workflow_name = workflow_name - self.created_at = created_at - self.last_updated_at = last_updated_at - self.runtime_status = runtime_status - self.properties = properties - - -class StartWorkflowResponse: - """The response of start_workflow operation.""" - - def __init__( - self, - instance_id: str, - ): - """Initializes a StartWorkflowResponse. - - Args: - instance_id (str): the instance ID assocated with this response. - """ - self.instance_id = instance_id - - -class RegisteredComponents(NamedTuple): - """Describes a loaded Dapr component.""" - - name: str - """Name of the component.""" - - type: str - """Component type.""" - - version: str - """Component version.""" - - capabilities: Sequence[str] - """Supported capabilities for this component type and version.""" - - -class CryptoResponse(DaprResponse, Generic[TCryptoResponse]): - """An iterable of cryptography API responses.""" - - def __init__(self, stream: Generator[TCryptoResponse, None, None]): - """Initialize a CryptoResponse. - - Args: - stream (Generator[TCryptoResponse, None, None]): A stream of cryptography API responses. - """ - self._stream = stream - self._buffer = bytearray() - self._expected_seq = 0 - - def __iter__(self) -> Generator[bytes, None, None]: - """Read the next chunk of data from the stream. - - Yields: - bytes: The payload data of the next chunk from the stream. - - Raises: - ValueError: If the sequence number of the next chunk is incorrect. - """ - for chunk in self._stream: - if chunk.payload.seq != self._expected_seq: - raise ValueError('invalid sequence number in chunk') - self._expected_seq += 1 - yield chunk.payload.data - - def read(self, size: int = -1) -> bytes: - """Read bytes from the stream. - - If size is -1, the entire stream is read and returned as bytes. - Otherwise, up to `size` bytes are read from the stream and returned. - If the stream ends before `size` bytes are available, the remaining - bytes are returned. - - Args: - size (int): The maximum number of bytes to read. If -1 (the default), - read until the end of the stream. - - Returns: - bytes: The bytes read from the stream. - """ - if size == -1: - # Read the entire stream - return b''.join(self) - - # Read the requested number of bytes - data = bytes(self._buffer) - self._buffer.clear() - - for chunk in self: - data += chunk - if len(data) >= size: - break - - # Update the buffer - remaining = data[size:] - self._buffer.extend(remaining) - - # Return the requested number of bytes - return data[:size] - - -class EncryptResponse(CryptoResponse[TCryptoResponse]): - ... - - -class DecryptResponse(CryptoResponse[TCryptoResponse]): - ... +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from __future__ import annotations + +import contextlib +import json +import threading +from datetime import datetime +from enum import Enum +from typing import ( + Callable, + Dict, + List, + Optional, + Text, + Union, + Sequence, + TYPE_CHECKING, + NamedTuple, + Generator, + TypeVar, + Generic, + Mapping, +) + +from google.protobuf.any_pb2 import Any as GrpcAny +from google.protobuf.message import Message as GrpcMessage + +from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE +from dapr.clients.grpc._helpers import ( + MetadataDict, + MetadataTuple, + to_bytes, + to_str, + tuple_to_dict, + unpack, + WorkflowRuntimeStatus, +) +from dapr.proto import api_service_v1, api_v1, appcallback_v1, common_v1 + +# Avoid circular import dependency by only importing DaprGrpcClient +# for type checking +if TYPE_CHECKING: + from dapr.clients.grpc.client import DaprGrpcClient + +TCryptoResponse = TypeVar( + 'TCryptoResponse', bound=Union[api_v1.EncryptResponse, api_v1.DecryptResponse] +) + + +class DaprResponse: + """A base class for Dapr Response. + + This is the base class for Dapr Response. User can get the headers as a dict. + + Attributes: + headers(dict): A dict to include the headers from Dapr gRPC Response. + """ + + def __init__(self, headers: MetadataTuple = ()): + """Inits DapResponse with headers and trailers. + + Args: + headers (tuple, optional): the tuple for the headers from response. + """ + self.headers = headers # type: ignore + + @property + def headers(self) -> MetadataDict: + """Gets headers as a dict.""" + return self.get_headers(as_dict=True) # type: ignore + + @headers.setter + def headers(self, val: MetadataTuple) -> None: + """Sets response headers.""" + self._headers = val + + def get_headers(self, as_dict: bool = False) -> Union[MetadataDict, MetadataTuple]: + """Gets headers from the response. + + Args: + as_dict (bool): dict type headers if as_dict is True. Otherwise, return + tuple headers. + + Returns: + dict or tuple: response headers. + """ + if as_dict: + return tuple_to_dict(self._headers) + return self._headers + + +class InvokeMethodResponse(DaprResponse): + """The response of invoke_method API. + + This inherits from DaprResponse and has the helpers to handle bytes array + and protocol buffer data. + + Attributes: + headers (tuple, optional): the tuple for the headers from response. + data (str, bytes, GrpcAny, GrpcMessage, optional): the serialized protocol + buffer raw message + content (bytes, optional): bytes data if response data is not serialized + protocol buffer message + content_type (str, optional): the type of `content` + status_code (int, optional): the status code of the response + """ + + def __init__( + self, + data: Union[str, bytes, GrpcAny, GrpcMessage, None] = None, + content_type: Optional[str] = None, + headers: MetadataTuple = (), + status_code: Optional[int] = None, + ): + """Initializes InvokeMethodReponse from :obj:`common_v1.InvokeResponse`. + + Args: + data (str, bytes, GrpcAny, GrpcMessage, optional): the response data + from Dapr response + content_type (str, optional): the content type of the bytes data + headers (tuple, optional): the headers from Dapr gRPC response + status_code (int, optional): the status code of the response + """ + super(InvokeMethodResponse, self).__init__(headers) + self._content_type = content_type + self.set_data(data) + self._status_code = status_code + + # Set content_type to application/json type if content_type + # is not given and date is bytes or str type. + if not self.is_proto() and not content_type: + self.content_type = DEFAULT_JSON_CONTENT_TYPE + + @property + def proto(self) -> GrpcAny: + """Gets raw serialized protocol buffer message. + + Raises: + ValueError: data is not protocol buffer message object + """ + return self._data + + def is_proto(self) -> bool: + """Returns True if the response data is the serialized protocol buffer message.""" + return hasattr(self, '_data') and self._data.type_url != '' + + @property + def data(self) -> bytes: + """Gets raw bytes data if the response data content is not serialized + protocol buffer message. + + Raises: + ValueError: the response data is the serialized protocol buffer message + """ + if self.is_proto(): + raise ValueError('data is protocol buffer message object.') + return self._data.value + + @data.setter + def data(self, val: Union[str, bytes]) -> None: + """Sets str or bytes type data to request data.""" + self.set_data(val) + + def set_data(self, val: Union[str, bytes, GrpcAny, GrpcMessage, None]) -> None: + """Sets data to request data.""" + if val is None: + self._data = GrpcAny() + elif isinstance(val, (bytes, str)): + self._data = GrpcAny(value=to_bytes(val)) + elif isinstance(val, (GrpcAny, GrpcMessage)): + self.pack(val) + else: + raise ValueError(f'invalid data type {type(val)}') + + def text(self) -> str: + """Gets content as str if the response data content is not serialized + protocol buffer message. + + Raises: + ValueError: the response data is the serialized protocol buffer message + """ + return to_str(self.data) + + def json(self) -> Dict[str, object]: + """Gets the content as json if the response data content is not a serialized + protocol buffer message. + + Returns: + str: [description] + """ + return json.loads(to_str(self.data)) + + @property + def content_type(self) -> Optional[str]: + """Gets the content type of content attribute.""" + return self._content_type + + @content_type.setter + def content_type(self, val: Optional[str]) -> None: + """Sets content type for bytes data.""" + self._content_type = val + + def pack(self, val: Union[GrpcAny, GrpcMessage]) -> None: + """Serializes protocol buffer message. + + Args: + message (:class:`GrpcMessage`, :class:`GrpcAny`): the protocol buffer message object + + Raises: + ValueError: message is neither GrpcAny nor GrpcMessage. + """ + if isinstance(val, GrpcAny): + self._data = val + elif isinstance(val, GrpcMessage): + self._data = GrpcAny() + self._data.Pack(val) + else: + raise ValueError('invalid data type') + + @property + def status_code(self) -> Optional[int]: + """Gets the response status code attribute.""" + return self._status_code + + @status_code.setter + def status_code(self, val: Optional[int]) -> None: + """Sets the response status code.""" + self._status_code = val + + def unpack(self, message: GrpcMessage) -> None: + """Deserializes the serialized protocol buffer message. + + Args: + message (:class:`GrpcMessage`): the protocol buffer message object + to which the response data is deserialized. + + Raises: + ValueError: message is not protocol buffer message object or message's type is not + matched with the response data type + """ + + if self.content_type is not None and self.content_type.lower() == 'application/x-protobuf': + message.ParseFromString(self.data) + return + + unpack(self.proto, message) + + +class BindingResponse(DaprResponse): + """The response of invoke_binding API. + + This inherits from DaprResponse and has the helpers to handle bytes array data. + + Attributes: + data (bytes): the data in response from the invoke_binding call + binding_metadata (Dict[str, str]): metadata sent as a reponse by the binding + """ + + def __init__( + self, + data: Union[bytes, str], + binding_metadata: Dict[str, str] = {}, + headers: MetadataTuple = (), + ): + """Initializes InvokeBindingReponse from :obj:`runtime_v1.InvokeBindingResponse`. + + Args: + data (bytes): the data in response from the invoke_binding call + binding_metadata (Dict[str, str]): metadata sent as a reponse by the binding + headers (Tuple, optional): the headers from Dapr gRPC response + + Raises: + ValueError: if the response data is not :class:`google.protobuf.any_pb2.Any` + object. + """ + super(BindingResponse, self).__init__(headers) + self.data = data # type: ignore + self._metadata = binding_metadata + + def text(self) -> str: + """Gets content as str.""" + return to_str(self._data) + + def json(self) -> Dict[str, object]: + """Gets content as deserialized JSON dictionary.""" + return json.loads(to_str(self._data)) + + @property + def data(self) -> bytes: + """Gets raw bytes data.""" + return self._data + + @data.setter + def data(self, val: Union[bytes, str]) -> None: + """Sets str or bytes type data to request data.""" + self._data = to_bytes(val) + + @property + def binding_metadata(self) -> Dict[str, str]: + """Gets the metadata in the response.""" + return self._metadata + + +class GetSecretResponse(DaprResponse): + """The response of get_secret API. + + This inherits from DaprResponse + + Attributes: + secret (Dict[str, str]): secret received from response + """ + + def __init__(self, secret: Dict[str, str], headers: MetadataTuple = ()): + """Initializes GetSecretReponse from :obj:`dapr_v1.GetSecretResponse`. + + Args: + secret (Dict[Str, str]): the secret from Dapr response + headers (Tuple, optional): the headers from Dapr gRPC response + """ + super(GetSecretResponse, self).__init__(headers) + self._secret = secret + + @property + def secret(self) -> Dict[str, str]: + """Gets secret as a dict.""" + return self._secret + + +class GetBulkSecretResponse(DaprResponse): + """The response of get_bulk_secret API. + + This inherits from DaprResponse + + Attributes: + secret (Dict[str, Dict[str, str]]): secret received from response + """ + + def __init__(self, secrets: Dict[str, Dict[str, str]], headers: MetadataTuple = ()): + """Initializes GetBulkSecretReponse from :obj:`dapr_v1.GetBulkSecretResponse`. + + Args: + secrets (Dict[Str, Dict[str, str]]): the secret from Dapr response + headers (Tuple, optional): the headers from Dapr gRPC response + """ + super(GetBulkSecretResponse, self).__init__(headers) + self._secrets = secrets + + @property + def secrets(self) -> Dict[str, Dict[str, str]]: + """Gets secrets as a dict.""" + return self._secrets + + +class StateResponse(DaprResponse): + """The response of get_state API. + + This inherits from DaprResponse + + Attributes: + data (bytes): the data in response from the get_state call + etag (str): state's etag. + headers (Tuple, optional): the headers from Dapr gRPC response + """ + + def __init__(self, data: Union[bytes, str], etag: str = '', headers: MetadataTuple = ()): + """Initializes StateResponse from :obj:`runtime_v1.GetStateResponse`. + + Args: + data (bytes): the data in response from the get_state call + etag (str): state's etag. + headers (Tuple, optional): the headers from Dapr gRPC response + + Raises: + ValueError: if the response data is not :class:`google.protobuf.any_pb2.Any` + object. + """ + super(StateResponse, self).__init__(headers) + self.data = data # type: ignore + self._etag = etag + + def text(self) -> str: + """Gets content as str.""" + return to_str(self._data) + + def json(self) -> Dict[str, object]: + """Gets content as deserialized JSON dictionary.""" + return json.loads(to_str(self._data)) + + @property + def etag(self) -> str: + """Gets etag.""" + return self._etag + + @property + def data(self) -> bytes: + """Gets raw bytes data.""" + return self._data + + @data.setter + def data(self, val: Union[bytes, str]) -> None: + """Sets str or bytes type data to request data.""" + self._data = to_bytes(val) + + +class BulkStateItem: + """A state item from bulk_get_state API. + + Attributes: + key (str): state's key. + data (Union[bytes, str]): state's data. + etag (str): state's etag. + error (str): error when state was retrieved + """ + + def __init__(self, key: str, data: Union[bytes, str], etag: str = '', error: str = ''): + """Initializes BulkStateItem item from :obj:`runtime_v1.BulkStateItem`. + + Args: + key (str): state's key. + data (Union[bytes, str]): state's data. + etag (str): state's etag. + error (str): error when state was retrieved + """ + self._key = key + self._data = data # type: ignore + self._etag = etag + self._error = error + + def text(self) -> str: + """Gets content as str.""" + return to_str(self._data) + + def json(self) -> Dict[str, object]: + """Gets content as deserialized JSON dictionary.""" + return json.loads(to_str(self._data)) + + @property + def key(self) -> str: + """Gets key.""" + return self._key + + @property + def data(self) -> Union[bytes, str]: + """Gets raw data.""" + return self._data + + @property + def etag(self) -> str: + """Gets etag.""" + return self._etag + + @property + def error(self) -> str: + """Gets error.""" + return self._error + + +class BulkStatesResponse(DaprResponse): + """The response of bulk_get_state API. + + This inherits from DaprResponse + + Attributes: + data (Union[bytes, str]): state's data. + """ + + def __init__(self, items: Sequence[BulkStateItem], headers: MetadataTuple = ()): + """Initializes BulkStatesResponse from :obj:`runtime_v1.GetBulkStateResponse`. + + Args: + items (Sequence[BulkStatesItem]): the items retrieved. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super(BulkStatesResponse, self).__init__(headers) + self._items = items + + @property + def items(self) -> Sequence[BulkStateItem]: + """Gets the items.""" + return self._items + + +class QueryResponseItem: + """A query response item from state store query API. + + Attributes: + key (str): query reponse item's key. + value (bytes): query reponse item's data. + etag (str): query reponse item's etag. + error (str): error when state was retrieved + """ + + def __init__(self, key: str, value: bytes, etag: str = '', error: str = ''): + """Initializes QueryResponseItem item from :obj:`runtime_v1.QueryStateItem`. + + Args: + key (str): query response item's key. + value (bytes): query response item's data. + etag (str): query response item's etag. + error (str): error when state was retrieved + """ + self._key = key + self._value = value + self._etag = etag + self._error = error + + def text(self) -> str: + """Gets value as str.""" + return to_str(self._value) + + def json(self) -> Dict[str, object]: + """Gets value as deserialized JSON dictionary.""" + return json.loads(to_str(self._value)) + + @property + def key(self) -> str: + """Gets key.""" + return self._key + + @property + def value(self) -> bytes: + """Gets raw value.""" + return self._value + + @property + def etag(self) -> str: + """Gets etag.""" + return self._etag + + @property + def error(self) -> str: + """Gets error.""" + return self._error + + +class QueryResponse(DaprResponse): + """The response of state store query API. + + This inherits from DaprResponse + + Attributes: + results (Sequence[QueryResponseItem]): the query results. + token (str): query response token for pagination. + metadata (Dict[str, str]): query response metadata. + """ + + def __init__( + self, + results: Sequence[QueryResponseItem], + token: str = '', + metadata: Dict[str, str] = dict(), + headers: MetadataTuple = (), + ): + """Initializes QueryResponse from :obj:`runtime_v1.QueryStateResponse`. + + Args: + results (Sequence[QueryResponseItem]): the query results. + token (str): query response token for pagination. + metadata (Dict[str, str]): query response metadata. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super(QueryResponse, self).__init__(headers) + self._metadata = metadata + self._results = results + self._token = token + + @property + def results(self) -> Sequence[QueryResponseItem]: + """Gets the query results.""" + return self._results + + @property + def token(self) -> str: + """Gets the query pagination token.""" + return self._token + + @property + def metadata(self) -> Dict[str, str]: + """Gets the query response metadata.""" + return self._metadata + + +class ConfigurationItem: + """A config item from get_configuration API. + + Attributes: + value (Union[bytes, str]): config's value. + version (str): config's version. + metadata (str): metadata + """ + + def __init__(self, value: str, version: str, metadata: Optional[Dict[str, str]] = dict()): + """Initializes ConfigurationItem item from :obj:`runtime_v1.ConfigurationItem`. + + Args: + value (str): config's value. + version (str): config's version. + metadata (Optional[Dict[str, str]] = dict()): metadata + """ + self._value = value + self._version = version + self._metadata = metadata + + def text(self) -> str: + """Gets content as str.""" + return to_str(self._value) + + def json(self) -> Dict[str, object]: + """Gets content as deserialized JSON dictionary.""" + return json.loads(to_str(self._value)) + + @property + def value(self) -> str: + """Gets value.""" + return self._value + + @property + def version(self) -> str: + """Gets version.""" + return self._version + + @property + def metadata(self) -> Optional[Dict[str, str]]: + """Gets metadata.""" + return self._metadata + + +class ConfigurationResponse(DaprResponse): + """The response of get_configuration API. + + This inherits from DaprResponse + + Attributes: + - items (Mapping[Text, ConfigurationItem]): state's data. + """ + + def __init__( + self, items: Mapping[Text, common_v1.ConfigurationItem], headers: MetadataTuple = () + ): + """Initializes ConfigurationResponse from :obj:`runtime_v1.GetConfigurationResponse`. + + Args: + items (Mapping[str, common_v1.ConfigurationItem]): the items retrieved. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super(ConfigurationResponse, self).__init__(headers) + self._items: Dict[Text, ConfigurationItem] = dict() + k: Text + v: common_v1.ConfigurationItem + for k, v in items.items(): + self._items[k] = ConfigurationItem(v.value, v.version, dict(v.metadata)) + + @property + def items(self) -> Dict[Text, ConfigurationItem]: + """Gets the items.""" + return self._items + + +class ConfigurationWatcher: + def __init__(self): + self.store_name = None + self.keys = None + self.event: threading.Event = threading.Event() + self.id: str = '' + + def watch_configuration( + self, + stub: api_service_v1.DaprStub, + store_name: str, + keys: List[str], + handler: Callable[[Text, ConfigurationResponse], None], + config_metadata: Optional[Dict[str, str]] = dict(), + ): + req = api_v1.SubscribeConfigurationRequest( + store_name=store_name, keys=keys, metadata=config_metadata + ) + thread = threading.Thread(target=self._read_subscribe_config, args=(stub, req, handler)) + thread.daemon = True + thread.start() + self.keys = keys + self.store_name = store_name + check = self.event.wait(timeout=5) + if not check: + print(f'Unable to get configuration id for keys {self.keys}') + return None + return self.id + + def _read_subscribe_config( + self, + stub: api_service_v1.DaprStub, + req: api_v1.SubscribeConfigurationRequest, + handler: Callable[[Text, ConfigurationResponse], None], + ): + try: + responses: List[ + api_v1.SubscribeConfigurationResponse + ] = stub.SubscribeConfigurationAlpha1(req) + isFirst = True + for response in responses: + if isFirst: + self.id = response.id + self.event.set() + isFirst = False + if len(response.items) > 0: + handler(response.id, ConfigurationResponse(response.items)) + except Exception: + print(f'{self.store_name} configuration watcher for keys ' f'{self.keys} stopped.') + pass + + +class TopicEventResponseStatus(Enum): + # success is the default behavior: message is acknowledged and not retried + success = appcallback_v1.TopicEventResponse.TopicEventResponseStatus.SUCCESS + retry = appcallback_v1.TopicEventResponse.TopicEventResponseStatus.RETRY + drop = appcallback_v1.TopicEventResponse.TopicEventResponseStatus.DROP + + +class TopicEventResponse(DaprResponse): + """The response of subscribed topic events. + + This inherits from DaprResponse + + Attributes: + status (Union[str, TopicEventResponseStatus]): status of the response + """ + + def __init__( + self, + status: Union[str, TopicEventResponseStatus], + headers: MetadataTuple = (), + ): + """Initializes a TopicEventResponse. + + Args: + status (TopicEventResponseStatus): The status of the response. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super(TopicEventResponse, self).__init__(headers) + values = [e.name for e in TopicEventResponseStatus] + errormsg = f'`status` must be one of {values} or a TopicEventResponseStatus' + + if isinstance(status, str): + try: + status = TopicEventResponseStatus[status.lower()] + except KeyError as e: + raise KeyError(errormsg) from e + if not isinstance(status, TopicEventResponseStatus): + raise ValueError(errormsg) + self._status = status + + @property + def status(self) -> TopicEventResponseStatus: + """Gets the status.""" + return self._status + + +class UnlockResponseStatus(Enum): + success = api_v1.UnlockResponse.Status.SUCCESS + """The Unlock operation for the referred lock was successful.""" + + lock_does_not_exist = api_v1.UnlockResponse.Status.LOCK_DOES_NOT_EXIST + """'The unlock operation failed: the referred lock does not exist.""" + + lock_belongs_to_others = api_v1.UnlockResponse.Status.LOCK_BELONGS_TO_OTHERS + """The unlock operation failed: the referred lock belongs to another owner.""" + + internal_error = api_v1.UnlockResponse.Status.INTERNAL_ERROR + """An internal error happened while handling the Unlock operation""" + + +class UnlockResponse(DaprResponse): + """The response of an unlock operation. + + This inherits from DaprResponse + + Attributes: + status (UnlockResponseStatus): the status of the unlock operation. + """ + + def __init__( + self, + status: UnlockResponseStatus, + headers: MetadataTuple = (), + ): + """Initializes a UnlockResponse. + + Args: + status (UnlockResponseStatus): The status of the response. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super().__init__(headers) + self._status = status + + @property + def status(self) -> UnlockResponseStatus: + """Gets the status.""" + return self._status + + +class TryLockResponse(contextlib.AbstractContextManager, DaprResponse): + """The response of a try_lock operation. + + This inherits from DaprResponse and AbstractContextManager. + + Attributes: + success (bool): the result of the try_lock operation. + """ + + def __init__( + self, + success: bool, + client: DaprGrpcClient, + store_name: str, + resource_id: str, + lock_owner: str, + headers: MetadataTuple = (), + ): + """Initializes a TryLockResponse. + + Args: + success (bool): the result of the try_lock operation. + client (DaprClient): a reference to the dapr client used for the TryLock request. + store_name (str): the lock store name used in the TryLock request. + resource_id (str): the lock key or identifier used in the TryLock request. + lock_owner (str): the lock owner identifier used in the TryLock request. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super().__init__(headers) + self._success = success + self._client = client + self._store_name = store_name + self._resource_id = resource_id + self._lock_owner = lock_owner + + def __bool__(self) -> bool: + return self._success + + @property + def success(self) -> bool: + """Gets the response success status.""" + return self._success + + def __exit__(self, *exc) -> None: + """'Automatically unlocks the lock if this TryLockResponse was used as + a ContextManager / `with` statement. + + Notice: we are not checking the result of the unlock operation. + If this is something you care about it might be wiser creating + your own ContextManager that logs or otherwise raises exceptions + if unlock doesn't return `UnlockResponseStatus.success`. + """ + if self._success: + self._client.unlock(self._store_name, self._resource_id, self._lock_owner) + # else: there is no point unlocking a lock we did not acquire. + + async def __aexit__(self, *exc) -> None: + """'Automatically unlocks the lock if this TryLockResponse was used as + a ContextManager / `with` statement. + + Notice: we are not checking the result of the unlock operation. + If this is something you care about it might be wiser creating + your own ContextManager that logs or otherwise raises exceptions + if unlock doesn't return `UnlockResponseStatus.success`. + """ + if self._success: + await self._client.unlock( + self._store_name, # type: ignore + self._resource_id, + self._lock_owner, + ) + # else: there is no point unlocking a lock we did not acquire. + + async def __aenter__(self) -> 'TryLockResponse': + """Returns self as the context manager object.""" + return self + + +class GetMetadataResponse(DaprResponse): + """GetMetadataResponse is a message that is returned on GetMetadata rpc call.""" + + def __init__( + self, + application_id: str, + active_actors_count: Dict[str, int], + registered_components: Sequence[RegisteredComponents], + extended_metadata: Dict[str, str], + headers: MetadataTuple = (), + ): + """Initializes GetMetadataResponse. + + Args: + application_id (str): The Application ID. + active_actors_count (Dict[str, int]): mapping from the type of + registered actors to their number of running instances. + registered_components (Sequence[RegisteredComponents]): list of + loaded components metadata. + extended_metadata (Dict[str, str]): mapping of custom (extended) + attributes to their respective values. + headers (Tuple, optional): the headers from Dapr gRPC response. + """ + super().__init__(headers) + self._application_id = application_id + self._active_actors_count = active_actors_count + self._registered_components = registered_components + self._extended_metadata = extended_metadata + + @property + def application_id(self) -> str: + """The Application ID.""" + return self._application_id + + @property + def active_actors_count(self) -> Dict[str, int]: + """Mapping from the type of registered actors to their number of running instances.""" + return self._active_actors_count + + @property + def registered_components(self) -> Sequence[RegisteredComponents]: + """List of loaded components metadata.""" + return self._registered_components + + @property + def extended_metadata(self) -> Dict[str, str]: + """Mapping of custom (extended) attributes to their respective values.""" + return self._extended_metadata + + +class GetWorkflowResponse: + """The response of get_workflow operation.""" + + def __init__( + self, + instance_id: str, + workflow_name: str, + created_at: datetime, + last_updated_at: str, + runtime_status: WorkflowRuntimeStatus, + properties: Dict[str, str] = {}, + ): + """Initializes a GetWorkflowResponse. + + Args: + instance_id (str): the instance ID assocated with this response. + workflow_name (str): the name of the workflow that was started. + created_at (datetime): the time at which the workflow started executing. + last_updated_at (datetime): the time at which the workflow was last updated. + runtime_status (WorkflowRuntimeStatus): the current runtime status of the workflow. + properties (Dict[str, str]): properties sent as a reponse by the workflow. + """ + self.instance_id = instance_id + self.workflow_name = workflow_name + self.created_at = created_at + self.last_updated_at = last_updated_at + self.runtime_status = runtime_status + self.properties = properties + + +class StartWorkflowResponse: + """The response of start_workflow operation.""" + + def __init__( + self, + instance_id: str, + ): + """Initializes a StartWorkflowResponse. + + Args: + instance_id (str): the instance ID assocated with this response. + """ + self.instance_id = instance_id + + +class RegisteredComponents(NamedTuple): + """Describes a loaded Dapr component.""" + + name: str + """Name of the component.""" + + type: str + """Component type.""" + + version: str + """Component version.""" + + capabilities: Sequence[str] + """Supported capabilities for this component type and version.""" + + +class CryptoResponse(DaprResponse, Generic[TCryptoResponse]): + """An iterable of cryptography API responses.""" + + def __init__(self, stream: Generator[TCryptoResponse, None, None]): + """Initialize a CryptoResponse. + + Args: + stream (Generator[TCryptoResponse, None, None]): A stream of cryptography API responses. + """ + self._stream = stream + self._buffer = bytearray() + self._expected_seq = 0 + + def __iter__(self) -> Generator[bytes, None, None]: + """Read the next chunk of data from the stream. + + Yields: + bytes: The payload data of the next chunk from the stream. + + Raises: + ValueError: If the sequence number of the next chunk is incorrect. + """ + for chunk in self._stream: + if chunk.payload.seq != self._expected_seq: + raise ValueError('invalid sequence number in chunk') + self._expected_seq += 1 + yield chunk.payload.data + + def read(self, size: int = -1) -> bytes: + """Read bytes from the stream. + + If size is -1, the entire stream is read and returned as bytes. + Otherwise, up to `size` bytes are read from the stream and returned. + If the stream ends before `size` bytes are available, the remaining + bytes are returned. + + Args: + size (int): The maximum number of bytes to read. If -1 (the default), + read until the end of the stream. + + Returns: + bytes: The bytes read from the stream. + """ + if size == -1: + # Read the entire stream + return b''.join(self) + + # Read the requested number of bytes + data = bytes(self._buffer) + self._buffer.clear() + + for chunk in self: + data += chunk + if len(data) >= size: + break + + # Update the buffer + remaining = data[size:] + self._buffer.extend(remaining) + + # Return the requested number of bytes + return data[:size] + + +class EncryptResponse(CryptoResponse[TCryptoResponse]): + ... + + +class DecryptResponse(CryptoResponse[TCryptoResponse]): + ... diff --git a/dapr/clients/grpc/_state.py b/dapr/clients/grpc/_state.py index 3dc266b22..cff93380f 100644 --- a/dapr/clients/grpc/_state.py +++ b/dapr/clients/grpc/_state.py @@ -1,122 +1,122 @@ -from enum import Enum -from dapr.proto import common_v1 -from typing import Dict, Optional, Union - - -class Consistency(Enum): - """Represents the consistency mode for a Dapr State Api Call""" - - unspecified = common_v1.StateOptions.StateConsistency.CONSISTENCY_UNSPECIFIED # type: ignore - eventual = common_v1.StateOptions.StateConsistency.CONSISTENCY_EVENTUAL # type: ignore - strong = common_v1.StateOptions.StateConsistency.CONSISTENCY_STRONG # type: ignore - - -class Concurrency(Enum): - """Represents the consistency mode for a Dapr State Api Call""" - - unspecified = common_v1.StateOptions.StateConcurrency.CONCURRENCY_UNSPECIFIED # type: ignore - first_write = common_v1.StateOptions.StateConcurrency.CONCURRENCY_FIRST_WRITE # type: ignore - last_write = common_v1.StateOptions.StateConcurrency.CONCURRENCY_LAST_WRITE # type: ignore - - -class StateOptions: - """Represents options for a Dapr State Api Call - Args: - consistency (Consistency, optional): the consistency mode - concurrency (Concurrency, optional): the concurrency mode - retry_policy (RetryPolicy, optional): the policy for retrying the api calls - """ - - def __init__( - self, - consistency: Optional[Consistency] = Consistency.unspecified, - concurrency: Optional[Concurrency] = Concurrency.unspecified, - ): - self.consistency = consistency - self.concurrency = concurrency - - def get_proto(self): - return common_v1.StateOptions( - concurrency=self.concurrency.value, # type: ignore - consistency=self.consistency.value, # type: ignore - ) - - -class StateItem: - """Represents a state for Dapr State Api Call - Args: - key (str): the key for the state - value (Union[bytes, str]): value of the state - etag (str, optional): the etag for the state - options (StateOptions, optional): options for the state - metadata (Dict[str, str], optional): metadata for the state - """ - - def __init__( - self, - key: str, - value: Union[bytes, str], - etag: Optional[str] = None, - options: Optional[StateOptions] = None, - metadata: Optional[Dict[str, str]] = dict(), - ): - """Inits StateItem with the required parameters. - - Args: - key (str): the key for the state - value (Union[bytes, str]): value of the state - etag (str, optional): the etag for the state - options (StateOptions, optional): options for the state - metadata (Dict[str, str], optional): metadata for the state - - Raises: - ValueError: value is not bytes or str - """ - if not isinstance(value, (bytes, str)): - raise ValueError(f'invalid type for data {type(value)}') - - self._key = key - self._value = value - self._etag = etag - self._options = options - self._metadata = metadata - - @property - def key(self): - """Get key from :class:`StateItem`""" - return self._key - - @property - def value(self): - """Get value from :class:`StateItem`""" - return self._value - - @property - def etag(self): - """Get etag from :class:`StateItem`""" - return self._etag - - @etag.setter - def etag(self, val: Optional[str] = None): - """Set etag for instance of :class:`StateItem`""" - self._etag = val - - @property - def metadata(self): - """Get metadata from :class:`StateItem`""" - return self._metadata - - @metadata.setter - def metadata(self, meta: Dict[str, str]): - """Set metadata for instance of :class:`StateItem`""" - self._metadata = meta - - @property - def options(self): - """Get options from :class:`StateItem`""" - return self._options - - @options.setter - def options(self, op: StateOptions): - """Set options for instance of :class:`StateItem`""" - self._options = op +from enum import Enum +from dapr.proto import common_v1 +from typing import Dict, Optional, Union + + +class Consistency(Enum): + """Represents the consistency mode for a Dapr State Api Call""" + + unspecified = common_v1.StateOptions.StateConsistency.CONSISTENCY_UNSPECIFIED # type: ignore + eventual = common_v1.StateOptions.StateConsistency.CONSISTENCY_EVENTUAL # type: ignore + strong = common_v1.StateOptions.StateConsistency.CONSISTENCY_STRONG # type: ignore + + +class Concurrency(Enum): + """Represents the consistency mode for a Dapr State Api Call""" + + unspecified = common_v1.StateOptions.StateConcurrency.CONCURRENCY_UNSPECIFIED # type: ignore + first_write = common_v1.StateOptions.StateConcurrency.CONCURRENCY_FIRST_WRITE # type: ignore + last_write = common_v1.StateOptions.StateConcurrency.CONCURRENCY_LAST_WRITE # type: ignore + + +class StateOptions: + """Represents options for a Dapr State Api Call + Args: + consistency (Consistency, optional): the consistency mode + concurrency (Concurrency, optional): the concurrency mode + retry_policy (RetryPolicy, optional): the policy for retrying the api calls + """ + + def __init__( + self, + consistency: Optional[Consistency] = Consistency.unspecified, + concurrency: Optional[Concurrency] = Concurrency.unspecified, + ): + self.consistency = consistency + self.concurrency = concurrency + + def get_proto(self): + return common_v1.StateOptions( + concurrency=self.concurrency.value, # type: ignore + consistency=self.consistency.value, # type: ignore + ) + + +class StateItem: + """Represents a state for Dapr State Api Call + Args: + key (str): the key for the state + value (Union[bytes, str]): value of the state + etag (str, optional): the etag for the state + options (StateOptions, optional): options for the state + metadata (Dict[str, str], optional): metadata for the state + """ + + def __init__( + self, + key: str, + value: Union[bytes, str], + etag: Optional[str] = None, + options: Optional[StateOptions] = None, + metadata: Optional[Dict[str, str]] = dict(), + ): + """Inits StateItem with the required parameters. + + Args: + key (str): the key for the state + value (Union[bytes, str]): value of the state + etag (str, optional): the etag for the state + options (StateOptions, optional): options for the state + metadata (Dict[str, str], optional): metadata for the state + + Raises: + ValueError: value is not bytes or str + """ + if not isinstance(value, (bytes, str)): + raise ValueError(f'invalid type for data {type(value)}') + + self._key = key + self._value = value + self._etag = etag + self._options = options + self._metadata = metadata + + @property + def key(self): + """Get key from :class:`StateItem`""" + return self._key + + @property + def value(self): + """Get value from :class:`StateItem`""" + return self._value + + @property + def etag(self): + """Get etag from :class:`StateItem`""" + return self._etag + + @etag.setter + def etag(self, val: Optional[str] = None): + """Set etag for instance of :class:`StateItem`""" + self._etag = val + + @property + def metadata(self): + """Get metadata from :class:`StateItem`""" + return self._metadata + + @metadata.setter + def metadata(self, meta: Dict[str, str]): + """Set metadata for instance of :class:`StateItem`""" + self._metadata = meta + + @property + def options(self): + """Get options from :class:`StateItem`""" + return self._options + + @options.setter + def options(self, op: StateOptions): + """Set options for instance of :class:`StateItem`""" + self._options = op diff --git a/dapr/clients/grpc/client.py b/dapr/clients/grpc/client.py index 94793907d..8aa710fdf 100644 --- a/dapr/clients/grpc/client.py +++ b/dapr/clients/grpc/client.py @@ -1,1822 +1,1822 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import threading -import time -import socket -import json -import uuid - -from urllib.parse import urlencode - -from warnings import warn - -from typing import Callable, Dict, Optional, Text, Union, Sequence, List, Any -from typing_extensions import Self -from datetime import datetime -from google.protobuf.message import Message as GrpcMessage -from google.protobuf.empty_pb2 import Empty as GrpcEmpty - -import grpc # type: ignore -from grpc import ( # type: ignore - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, - RpcError, -) - -from dapr.clients.exceptions import DaprInternalError, DaprGrpcError -from dapr.clients.grpc._state import StateOptions, StateItem -from dapr.clients.grpc._helpers import getWorkflowRuntimeStatus -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.clients.grpc.subscription import Subscription, StreamInactiveError -from dapr.clients.grpc.interceptors import DaprClientInterceptor, DaprClientTimeoutInterceptor -from dapr.clients.health import DaprHealth -from dapr.clients.retry import RetryPolicy -from dapr.conf import settings -from dapr.proto import api_v1, api_service_v1, common_v1 -from dapr.proto.runtime.v1.dapr_pb2 import UnsubscribeConfigurationResponse -from dapr.version import __version__ - -from dapr.clients.grpc._helpers import ( - MetadataTuple, - to_bytes, - validateNotNone, - validateNotBlankString, -) -from dapr.conf.helpers import GrpcEndpoint -from dapr.clients.grpc._request import ( - InvokeMethodRequest, - BindingRequest, - TransactionalStateOperation, - EncryptRequestIterator, - DecryptRequestIterator, -) -from dapr.clients.grpc._response import ( - BindingResponse, - DaprResponse, - GetSecretResponse, - GetBulkSecretResponse, - GetMetadataResponse, - InvokeMethodResponse, - UnlockResponseStatus, - StateResponse, - BulkStatesResponse, - BulkStateItem, - ConfigurationResponse, - QueryResponse, - QueryResponseItem, - RegisteredComponents, - ConfigurationWatcher, - TryLockResponse, - UnlockResponse, - GetWorkflowResponse, - StartWorkflowResponse, - EncryptResponse, - DecryptResponse, - TopicEventResponse, -) - - -class DaprGrpcClient: - """The convenient layer implementation of Dapr gRPC APIs. - - This provides the wrappers and helpers to allows developers to use Dapr runtime gRPC API - easily and consistently. - - Examples: - - >>> from dapr.clients import DaprClient - >>> d = DaprClient() - >>> resp = d.invoke_method('callee', 'method', b'data') - - With context manager and custom message size limit: - - >>> from dapr.clients import DaprClient - >>> MAX = 64 * 1024 * 1024 # 64MB - >>> with DaprClient(max_message_length=MAX) as d: - ... resp = d.invoke_method('callee', 'method', b'data') - """ - - def __init__( - self, - address: Optional[str] = None, - interceptors: Optional[ - List[ - Union[ - UnaryUnaryClientInterceptor, - UnaryStreamClientInterceptor, - StreamUnaryClientInterceptor, - StreamStreamClientInterceptor, - ] - ] - ] = None, - max_grpc_message_length: Optional[int] = None, - retry_policy: Optional[RetryPolicy] = None, - ): - """Connects to Dapr Runtime and initializes gRPC client stub. - - Args: - address (str, optional): Dapr Runtime gRPC endpoint address. - interceptors (list of UnaryUnaryClientInterceptor or - UnaryStreamClientInterceptor or - StreamUnaryClientInterceptor or - StreamStreamClientInterceptor, optional): gRPC interceptors. - max_grpc_message_length (int, optional): The maximum grpc send and receive - message length in bytes. - retry_policy (RetryPolicy optional): Specifies retry behaviour - """ - DaprHealth.wait_until_ready() - self.retry_policy = retry_policy or RetryPolicy() - - useragent = f'dapr-sdk-python/{__version__}' - if not max_grpc_message_length: - options = [ - ('grpc.primary_user_agent', useragent), - ] - else: - options = [ - ('grpc.max_send_message_length', max_grpc_message_length), # type: ignore - ('grpc.max_receive_message_length', max_grpc_message_length), # type: ignore - ('grpc.primary_user_agent', useragent), - ] - - if not address: - address = settings.DAPR_GRPC_ENDPOINT or ( - f'{settings.DAPR_RUNTIME_HOST}:' f'{settings.DAPR_GRPC_PORT}' - ) - - try: - self._uri = GrpcEndpoint(address) - except ValueError as error: - raise DaprInternalError(f'{error}') from error - - if self._uri.tls: - self._channel = grpc.secure_channel( # type: ignore - self._uri.endpoint, - self.get_credentials(), - options=options, - ) - else: - self._channel = grpc.insecure_channel( # type: ignore - self._uri.endpoint, - options=options, - ) - - self._channel = grpc.intercept_channel(self._channel, DaprClientTimeoutInterceptor()) # type: ignore - - if settings.DAPR_API_TOKEN: - api_token_interceptor = DaprClientInterceptor( - [ - ('dapr-api-token', settings.DAPR_API_TOKEN), - ] - ) - self._channel = grpc.intercept_channel( # type: ignore - self._channel, api_token_interceptor - ) - if interceptors: - self._channel = grpc.intercept_channel( # type: ignore - self._channel, *interceptors - ) - - self._stub = api_service_v1.DaprStub(self._channel) - - @staticmethod - def get_credentials(): - # This method is used (overwritten) from tests - # to return credentials for self-signed certificates - return grpc.ssl_channel_credentials() # type: ignore - - def close(self): - """Closes Dapr runtime gRPC channel.""" - if hasattr(self, '_channel') and self._channel: - self._channel.close() - - def __del__(self): - self.close() - - def __enter__(self) -> Self: # type: ignore - return self - - def __exit__(self, exc_type, exc_value, traceback) -> None: - self.close() - - @staticmethod - def _get_http_extension( - http_verb: str, http_querystring: Optional[MetadataTuple] = None - ) -> common_v1.HTTPExtension: # type: ignore - verb = common_v1.HTTPExtension.Verb.Value(http_verb) # type: ignore - http_ext = common_v1.HTTPExtension(verb=verb) - if http_querystring is not None and len(http_querystring): - http_ext.querystring = urlencode(http_querystring) - return http_ext - - def invoke_method( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage] = '', - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invokes the target service to call method. - - This can invoke the specified target service to call method with bytes array data or - custom protocol buffer message. If your callee application uses http appcallback, - http_verb and http_querystring must be specified. Otherwise, Dapr runtime will return - error. - - The example calls `callee` service with bytes data, which implements grpc appcallback: - - from dapr.clients import DaprClient - - with DaprClient() as d: - resp = d.invoke_method( - app_id='callee', - method_name='method', - data=b'message', - content_type='text/plain', - ) - - # resp.content includes the content in bytes. - # resp.content_type specifies the content type of resp.content. - # Thus, resp.content can be deserialized properly. - - When sending custom protocol buffer message object, it doesn't requires content_type: - - from dapr.clients import DaprClient - - req_data = dapr_example_v1.CustomRequestMessage(data='custom') - - with DaprClient() as d: - resp = d.invoke_method( - app_id='callee', - method_name='method', - data=req_data, - ) - # Create protocol buffer object - resp_data = dapr_example_v1.CustomResponseMessage() - # Deserialize to resp_data - resp.unpack(resp_data) - - The example calls `callee` service which implements http appcallback: - - from dapr.clients import DaprClient - - with DaprClient() as d: - resp = d.invoke_method( - app_id='callee', - method_name='method', - data=b'message', - content_type='text/plain', - http_verb='POST', - http_querystring=( - ('key1', 'value1') - ), - ) - - # resp.content includes the content in bytes. - # resp.content_type specifies the content type of resp.content. - # Thus, resp.content can be deserialized properly. - - Args: - app_id (str): the callee app id - method (str): the method name which is called - data (bytes or :obj:`google.protobuf.message.Message`, optional): bytes - or Message for data which will be sent to app id - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - http_verb (str, optional): http method verb to call HTTP callee application - http_querystring (tuple, optional): the tuple to represent query string - timeout (int, optional): request timeout in seconds - - Returns: - :class:`InvokeMethodResponse` object returned from callee - """ - warn( - 'invoke_method with protocol gRPC is deprecated. Use gRPC proxying instead.', - DeprecationWarning, - stacklevel=2, - ) - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req_data = InvokeMethodRequest(data, content_type) - http_ext = None - if http_verb: - http_ext = self._get_http_extension(http_verb, http_querystring) - - content_type = '' - if req_data.content_type: - content_type = req_data.content_type - req = api_v1.InvokeServiceRequest( - id=app_id, - message=common_v1.InvokeRequest( - method=method_name, - data=req_data.proto, - content_type=content_type, - http_extension=http_ext, - ), - ) - - response, call = self.retry_policy.run_rpc( - self._stub.InvokeService.with_call, req, metadata=metadata, timeout=timeout - ) - - resp_data = InvokeMethodResponse(response.data, response.content_type) - resp_data.headers = call.initial_metadata() # type: ignore - return resp_data - - def invoke_binding( - self, - binding_name: str, - operation: str, - data: Union[bytes, str] = '', - binding_metadata: Dict[str, str] = {}, - metadata: Optional[MetadataTuple] = None, - ) -> BindingResponse: - """Invokes the output binding with the specified operation. - - The data field takes any JSON serializable value and acts as the - payload to be sent to the output binding. The metadata field is an - array of key/value pairs and allows you to set binding specific metadata - for each call. The operation field tells the Dapr binding which operation - it should perform. - - The example calls output `binding` service with bytes data: - - from dapr.clients import DaprClient - - with DaprClient() as d: - resp = d.invoke_binding( - binding_name = 'kafkaBinding', - operation = 'create', - data = b'message', - ) - # resp.data includes the response data in bytes. - - Args: - binding_name (str): the name of the binding as defined in the components - operation (str): the operation to perform on the binding - data (bytes or str, optional): bytes or str for data which will sent to the binding - binding_metadata (dict, optional): Dapr metadata for output binding - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`InvokeBindingResponse` object returned from binding - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req_data = BindingRequest(data, binding_metadata) - - req = api_v1.InvokeBindingRequest( - name=binding_name, - data=req_data.data, - metadata=req_data.binding_metadata, - operation=operation, - ) - - response, call = self.retry_policy.run_rpc( - self._stub.InvokeBinding.with_call, req, metadata=metadata - ) - return BindingResponse(response.data, dict(response.metadata), call.initial_metadata()) - - def publish_event( - self, - pubsub_name: str, - topic_name: str, - data: Union[bytes, str], - publish_metadata: Dict[str, str] = {}, - metadata: Optional[MetadataTuple] = None, - data_content_type: Optional[str] = None, - ) -> DaprResponse: - """Publish to a given topic. - This publishes an event with bytes array or str data to a specified topic and - specified pubsub component. The str data is encoded into bytes with default - charset of utf-8. Custom metadata can be passed with the metadata field which - will be passed on a gRPC metadata. - - The example publishes a byte array event to a topic: - - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.publish_event( - pubsub_name='pubsub_1', - topic_name='TOPIC_A', - data=b'message', - publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, - ) - # resp.headers includes the gRPC initial metadata. - - Args: - pubsub_name (str): the name of the pubsub component - topic_name (str): the topic name to publish to - data (bytes or str): bytes or str for data - publish_metadata (Dict[str, str], optional): Dapr metadata per Pub/Sub message - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - data_content_type: (str, optional): content type of the data payload - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not isinstance(data, bytes) and not isinstance(data, str): - raise ValueError(f'invalid type for data {type(data)}') - - req_data: bytes - if isinstance(data, bytes): - req_data = data - else: - if isinstance(data, str): - req_data = data.encode('utf-8') - - content_type = '' - if data_content_type: - content_type = data_content_type - req = api_v1.PublishEventRequest( - pubsub_name=pubsub_name, - topic=topic_name, - data=req_data, - data_content_type=content_type, - metadata=publish_metadata, - ) - - try: - # response is google.protobuf.Empty - _, call = self.retry_policy.run_rpc( - self._stub.PublishEvent.with_call, req, metadata=metadata - ) - except RpcError as err: - raise DaprGrpcError(err) from err - - return DaprResponse(call.initial_metadata()) - - def subscribe( - self, - pubsub_name: str, - topic: str, - metadata: Optional[MetadataTuple] = None, - dead_letter_topic: Optional[str] = None, - ) -> Subscription: - """ - Subscribe to a topic with a bidirectional stream - - Args: - pubsub_name (str): The name of the pubsub component. - topic (str): The name of the topic. - metadata (Optional[MetadataTuple]): Additional metadata for the subscription. - dead_letter_topic (Optional[str]): Name of the dead-letter topic. - timeout (Optional[int]): The time in seconds to wait for a message before returning None - If not set, the `next_message` method will block indefinitely - until a message is received. - - Returns: - Subscription: The Subscription object managing the stream. - """ - subscription = Subscription(self._stub, pubsub_name, topic, metadata, dead_letter_topic) - subscription.start() - return subscription - - def subscribe_with_handler( - self, - pubsub_name: str, - topic: str, - handler_fn: Callable[..., TopicEventResponse], - metadata: Optional[MetadataTuple] = None, - dead_letter_topic: Optional[str] = None, - ) -> Callable: - """ - Subscribe to a topic with a bidirectional stream and a message handler function - - Args: - pubsub_name (str): The name of the pubsub component. - topic (str): The name of the topic. - handler_fn (Callable[..., TopicEventResponse]): The function to call when a message is received. - metadata (Optional[MetadataTuple]): Additional metadata for the subscription. - dead_letter_topic (Optional[str]): Name of the dead-letter topic. - timeout (Optional[int]): The time in seconds to wait for a message before returning None - If not set, the `next_message` method will block indefinitely - until a message is received. - """ - subscription = self.subscribe(pubsub_name, topic, metadata, dead_letter_topic) - - def stream_messages(sub): - while True: - try: - message = sub.next_message() - if message: - # Process the message - response = handler_fn(message) - if response: - subscription.respond(message, response.status) - else: - # No message received - continue - except StreamInactiveError: - break - - def close_subscription(): - subscription.close() - - streaming_thread = threading.Thread(target=stream_messages, args=(subscription,)) - streaming_thread.start() - - return close_subscription - - def get_state( - self, - store_name: str, - key: str, - state_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> StateResponse: - """Gets value from a statestore with a key - - The example gets value from a statestore: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.get_state( - store_name='state_store' - key='key_1', - state={"key": "value"}, - state_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to get from - key (str): the key of the key-value pair to be gotten - state_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`StateResponse` gRPC metadata returned from callee - and value obtained from the state store - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req = api_v1.GetStateRequest(store_name=store_name, key=key, metadata=state_metadata) - try: - response, call = self.retry_policy.run_rpc( - self._stub.GetState.with_call, req, metadata=metadata - ) - return StateResponse( - data=response.data, etag=response.etag, headers=call.initial_metadata() - ) - except RpcError as err: - raise DaprGrpcError(err) from err - - def get_bulk_state( - self, - store_name: str, - keys: Sequence[str], - parallelism: int = 1, - states_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> BulkStatesResponse: - """Gets values from a statestore with keys - - The example gets value from a statestore: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.get_bulk_state( - store_name='state_store', - keys=['key_1', key_2], - parallelism=2, - states_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to get from - keys (Sequence[str]): the keys to be retrieved - parallelism (int): number of items to be retrieved in parallel - states_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`BulkStatesResponse` gRPC metadata returned from callee - and value obtained from the state store - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req = api_v1.GetBulkStateRequest( - store_name=store_name, keys=keys, parallelism=parallelism, metadata=states_metadata - ) - - try: - response, call = self.retry_policy.run_rpc( - self._stub.GetBulkState.with_call, req, metadata=metadata - ) - except RpcError as err: - raise DaprGrpcError(err) from err - - items = [] - for item in response.items: - items.append( - BulkStateItem(key=item.key, data=item.data, etag=item.etag, error=item.error) - ) - return BulkStatesResponse(items=items, headers=call.initial_metadata()) - - def query_state( - self, store_name: str, query: str, states_metadata: Optional[Dict[str, str]] = dict() - ) -> QueryResponse: - """Queries a statestore with a query - - For details on supported queries see https://docs.dapr.io/ - - This example queries a statestore: - from dapr.clients import DaprClient - - query = ''' - { - "filter": { - "EQ": { "state": "CA" } - }, - "sort": [ - { - "key": "person.id", - "order": "DESC" - } - ] - } - ''' - - with DaprClient() as d: - resp = d.query_state( - store_name='state_store', - query=query, - states_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to query - query (str): the query to be executed - states_metadata (Dict[str, str], optional): custom metadata for state request - - Returns: - :class:`QueryStateResponse` gRPC metadata returned from callee, - pagination token and results of the query - """ - warn( - 'The State Store Query API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req = api_v1.QueryStateRequest(store_name=store_name, query=query, metadata=states_metadata) - - try: - response, call = self.retry_policy.run_rpc(self._stub.QueryStateAlpha1.with_call, req) - except RpcError as err: - raise DaprGrpcError(err) from err - - results = [] - for item in response.results: - results.append( - QueryResponseItem(key=item.key, value=item.data, etag=item.etag, error=item.error) - ) - - return QueryResponse( - token=response.token, - results=results, - metadata=response.metadata, - headers=call.initial_metadata(), - ) - - def save_state( - self, - store_name: str, - key: str, - value: Union[bytes, str], - etag: Optional[str] = None, - options: Optional[StateOptions] = None, - state_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> DaprResponse: - """Saves key-value pairs to a statestore - - This saves a value to the statestore with a given key and state store name. - Options for request can be passed with the options field and custom - metadata can be passed with metadata field. - - The example saves states to a statestore: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.save_state( - store_name='state_store', - key='key1', - value='value1', - etag='etag', - state_metadata={"metakey": "metavalue"}, - ) - - Args: - store_name (str): the state store name to save to - key (str): the key to be saved - value (bytes or str): the value to be saved - etag (str, optional): the etag to save with - options (StateOptions, optional): custom options - for concurrency and consistency - state_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - Raises: - ValueError: value is not bytes or str - ValueError: store_name is empty - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not isinstance(value, (bytes, str)): - raise ValueError(f'invalid type for data {type(value)}') - - req_value = value - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - if options is None: - state_options = None - else: - state_options = options.get_proto() - - state = common_v1.StateItem( - key=key, - value=to_bytes(req_value), - etag=common_v1.Etag(value=etag) if etag is not None else None, - options=state_options, - metadata=state_metadata, - ) - - req = api_v1.SaveStateRequest(store_name=store_name, states=[state]) - try: - _, call = self.retry_policy.run_rpc( - self._stub.SaveState.with_call, req, metadata=metadata - ) - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprGrpcError(err) from err - - def save_bulk_state( - self, store_name: str, states: List[StateItem], metadata: Optional[MetadataTuple] = None - ) -> DaprResponse: - """Saves state items to a statestore - - This saves a given state item into the statestore specified by store_name. - - The example saves states to a statestore: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.save_bulk_state( - store_name='state_store', - states=[StateItem(key='key1', value='value1'), - StateItem(key='key2', value='value2', etag='etag'),], - ) - - Args: - store_name (str): the state store name to save to - states (List[StateItem]): list of states to save - metadata (tuple, optional): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - Raises: - ValueError: states is empty - ValueError: store_name is empty - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not states or len(states) == 0: - raise ValueError('States to be saved cannot be empty') - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - req_states = [ - common_v1.StateItem( - key=i.key, - value=to_bytes(i.value), - etag=common_v1.Etag(value=i.etag) if i.etag is not None else None, - options=i.options, - metadata=i.metadata, - ) - for i in states - ] - - req = api_v1.SaveStateRequest(store_name=store_name, states=req_states) - - try: - _, call = self.retry_policy.run_rpc( - self._stub.SaveState.with_call, req, metadata=metadata - ) - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprGrpcError(err) from err - - def execute_state_transaction( - self, - store_name: str, - operations: Sequence[TransactionalStateOperation], - transactional_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> DaprResponse: - """Saves or deletes key-value pairs to a statestore as a transaction - - This saves or deletes key-values to the statestore as part of a single transaction, - transaction_metadata is used for the transaction operation, while metadata is used - for the GRPC call. - - The example saves states to a statestore: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.execute_state_transaction( - store_name='state_store', - operations=[ - TransactionalStateOperation(key=key, data=value), - TransactionalStateOperation(key=another_key, data=another_value), - TransactionalStateOperation( - operation_type=TransactionOperationType.delete, - key=key_to_delete), - ], - transactional_metadata={"header1": "value1"}, - ) - - Args: - store_name (str): the state store name to save to - operations (Sequence[TransactionalStateOperation]): the transaction operations - transactional_metadata (Dict[str, str], optional): Dapr metadata for transaction - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token headers ' - 'and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - req_ops = [ - api_v1.TransactionalStateOperation( - operationType=o.operation_type.value, - request=common_v1.StateItem( - key=o.key, - value=to_bytes(o.data), - etag=common_v1.Etag(value=o.etag) if o.etag is not None else None, - ), - ) - for o in operations - ] - - req = api_v1.ExecuteStateTransactionRequest( - storeName=store_name, operations=req_ops, metadata=transactional_metadata - ) - - try: - _, call = self.retry_policy.run_rpc( - self._stub.ExecuteStateTransaction.with_call, req, metadata=metadata - ) - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprGrpcError(err) from err - - def delete_state( - self, - store_name: str, - key: str, - etag: Optional[str] = None, - options: Optional[StateOptions] = None, - state_metadata: Optional[Dict[str, str]] = dict(), - metadata: Optional[MetadataTuple] = None, - ) -> DaprResponse: - """Deletes key-value pairs from a statestore - - This deletes a value from the statestore with a given key and state store name. - Options for request can be passed with the options field and custom - metadata can be passed with metadata field. - - The example deletes states from a statestore: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.delete_state( - store_name='state_store', - key='key1', - etag='etag', - state_metadata={"header1": "value1"}, - ) - - Args: - store_name (str): the state store name to delete from - key (str): the key of the key-value pair to delete - etag (str, optional): the etag to delete with - options (StateOptions, optional): custom options - for concurrency and consistency - state_metadata (Dict[str, str], optional): Dapr metadata for state request - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token ' - 'headers and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('State store name cannot be empty') - - if options is None: - state_options = None - else: - state_options = options.get_proto() - - etag_object = common_v1.Etag(value=etag) if etag is not None else None - req = api_v1.DeleteStateRequest( - store_name=store_name, - key=key, - etag=etag_object, - options=state_options, - metadata=state_metadata, - ) - - try: - _, call = self.retry_policy.run_rpc( - self._stub.DeleteState.with_call, req, metadata=metadata - ) - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprGrpcError(err) from err - - def get_secret( - self, - store_name: str, - key: str, - secret_metadata: Optional[Dict[str, str]] = {}, - metadata: Optional[MetadataTuple] = None, - ) -> GetSecretResponse: - """Get secret with a given key. - - This gets a secret from secret store with a given key and secret store name. - Metadata for request can be passed with the secret_metadata field and custom - metadata can be passed with metadata field. - - - The example gets a secret from secret store: - - from dapr.clients import DaprClient - - with DaprClient() as d: - resp = d.get_secret( - store_name='secretstoreA', - key='keyA', - secret_metadata={'header1', 'value1'} - ) - - # resp.headers includes the gRPC initial metadata. - # resp.trailers includes that gRPC trailing metadata. - - Args: - store_name (str): store name to get secret from - key (str): str for key - secret_metadata (Dict[str, str], Optional): Dapr metadata for secrets request - metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`GetSecretResponse` object with the secret and metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token ' - 'headers and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req = api_v1.GetSecretRequest(store_name=store_name, key=key, metadata=secret_metadata) - - response, call = self.retry_policy.run_rpc( - self._stub.GetSecret.with_call, req, metadata=metadata - ) - - return GetSecretResponse(secret=response.data, headers=call.initial_metadata()) - - def get_bulk_secret( - self, - store_name: str, - secret_metadata: Optional[Dict[str, str]] = {}, - metadata: Optional[MetadataTuple] = None, - ) -> GetBulkSecretResponse: - """Get all granted secrets. - - This gets all granted secrets from secret store. - Metadata for request can be passed with the secret_metadata field. - - - The example gets all secrets from secret store: - - from dapr.clients import DaprClient - - with DaprClient() as d: - resp = d.get_bulk_secret( - store_name='secretstoreA', - secret_metadata={'header1', 'value1'} - ) - - # resp.headers includes the gRPC initial metadata. - # resp.trailers includes that gRPC trailing metadata. - - Args: - store_name (str): store name to get secret from - secret_metadata (Dict[str, Dict[str, str]], Optional): Dapr metadata of secrets request - metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`GetBulkSecretResponse` object with secrets and metadata returned from callee - """ - if metadata is not None: - warn( - 'metadata argument is deprecated. Dapr already intercepts API token ' - 'headers and this is not needed.', - DeprecationWarning, - stacklevel=2, - ) - - req = api_v1.GetBulkSecretRequest(store_name=store_name, metadata=secret_metadata) - - response, call = self.retry_policy.run_rpc( - self._stub.GetBulkSecret.with_call, req, metadata=metadata - ) - - secrets_map = {} - for key in response.data.keys(): - secret_response = response.data[key] - secrets_submap = {} - for subkey in secret_response.secrets.keys(): - secrets_submap[subkey] = secret_response.secrets[subkey] - secrets_map[key] = secrets_submap - - return GetBulkSecretResponse(secrets=secrets_map, headers=call.initial_metadata()) - - def get_configuration( - self, store_name: str, keys: List[str], config_metadata: Optional[Dict[str, str]] = dict() - ) -> ConfigurationResponse: - """Gets value from a config store with a key - - The example gets value from a config store: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.get_configuration( - store_name='state_store' - keys=['key_1'], - config_metadata={"metakey": "metavalue"} - ) - - Args: - store_name (str): the state store name to get from - keys (List[str]): the keys of the key-value pairs to be gotten - config_metadata (Dict[str, str], optional): Dapr metadata for configuration - - Returns: - :class:`ConfigurationResponse` gRPC metadata returned from callee - and value obtained from the config store - """ - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('Config store name cannot be empty to get the configuration') - req = api_v1.GetConfigurationRequest( - store_name=store_name, keys=keys, metadata=config_metadata - ) - response, call = self.retry_policy.run_rpc(self._stub.GetConfiguration.with_call, req) - return ConfigurationResponse(items=response.items, headers=call.initial_metadata()) - - def subscribe_configuration( - self, - store_name: str, - keys: List[str], - handler: Callable[[Text, ConfigurationResponse], None], - config_metadata: Optional[Dict[str, str]] = dict(), - ) -> Text: - """Gets changed value from a config store with a key - - The example gets value from a config store: - from dapr.clients import DaprClient - with DaprClient() as d: - resp = d.subscribe_configuration( - store_name='state_store' - keys=['key_1'], - handler=handler, - config_metadata={"metakey": "metavalue"} - ) - - Args: - store_name (str): the state store name to get from - keys (str array): the keys of the key-value pairs to be gotten - handler(func (key, ConfigurationResponse)): the callback function to be called - config_metadata (Dict[str, str], optional): Dapr metadata for configuration - - Returns: - id (str): subscription id, which can be used to unsubscribe later - """ - - if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: - raise ValueError('Config store name cannot be empty to get the configuration') - - configWatcher = ConfigurationWatcher() - id = configWatcher.watch_configuration( - self._stub, store_name, keys, handler, config_metadata - ) - return id - - def unsubscribe_configuration(self, store_name: str, id: str) -> bool: - """Unsubscribes from configuration changes. - - Args: - store_name (str): the state store name to unsubscribe from - id (str): the subscription id to unsubscribe - - Returns: - bool: True if unsubscribed successfully, False otherwise - """ - req = api_v1.UnsubscribeConfigurationRequest(store_name=store_name, id=id) - response: UnsubscribeConfigurationResponse = self._stub.UnsubscribeConfiguration(req) - return response.ok - - def try_lock( - self, store_name: str, resource_id: str, lock_owner: str, expiry_in_seconds: int - ) -> TryLockResponse: - """Tries to get a lock with an expiry. - - You can use the result of this operation directly on an `if` statement: - - if client.try_lock(store_name, resource_id, first_client_id, expiry_s): - # lock acquired successfully... - - You can also inspect the response's `success` attribute: - - response = client.try_lock(store_name, resource_id, first_client_id, expiry_s) - if response.success: - # lock acquired successfully... - - Finally, you can use this response with a `with` statement, and have the lock - be automatically unlocked after the with-statement scope ends - - with client.try_lock(store_name, resource_id, first_client_id, expiry_s) as lock: - if lock: - # lock acquired successfully... - # Lock automatically unlocked at this point, no need to call client->unlock(...) - - Args: - store_name (str): the lock store name, e.g. `redis`. - resource_id (str): the lock key. e.g. `order_id_111`. - It stands for "which resource I want to protect". - lock_owner (str): indicates the identifier of lock owner. - expiry_in_seconds (int): The length of time (in seconds) for which this lock - will be held and after which it expires. - - Returns: - :class:`TryLockResponse`: With the result of the try-lock operation. - """ - # Warnings and input validation - warn( - 'The Distributed Lock API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - store_name=store_name, resource_id=resource_id, lock_owner=lock_owner - ) - if not expiry_in_seconds or expiry_in_seconds < 1: - raise ValueError('expiry_in_seconds must be a positive number') - # Actual tryLock invocation - req = api_v1.TryLockRequest( - store_name=store_name, - resource_id=resource_id, - lock_owner=lock_owner, - expiry_in_seconds=expiry_in_seconds, - ) - response, call = self.retry_policy.run_rpc(self._stub.TryLockAlpha1.with_call, req) - return TryLockResponse( - success=response.success, - client=self, - store_name=store_name, - resource_id=resource_id, - lock_owner=lock_owner, - headers=call.initial_metadata(), - ) - - def unlock(self, store_name: str, resource_id: str, lock_owner: str) -> UnlockResponse: - """Unlocks a lock. - - Args: - store_name (str): the lock store name, e.g. `redis`. - resource_id (str): the lock key. e.g. `order_id_111`. - It stands for "which resource I want to protect". - lock_owner (str): indicates the identifier of lock owner. - metadata (tuple, optional, DEPRECATED): gRPC custom metadata - - Returns: - :class:`UnlockResponseStatus`: Status of the request, - `UnlockResponseStatus.success` if it was successful of some other - status otherwise. - """ - # Warnings and input validation - warn( - 'The Distributed Lock API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - store_name=store_name, resource_id=resource_id, lock_owner=lock_owner - ) - # Actual unlocking invocation - req = api_v1.UnlockRequest( - store_name=store_name, resource_id=resource_id, lock_owner=lock_owner - ) - response, call = self.retry_policy.run_rpc(self._stub.UnlockAlpha1.with_call, req) - - return UnlockResponse( - status=UnlockResponseStatus(response.status), headers=call.initial_metadata() - ) - - def encrypt(self, data: Union[str, bytes], options: EncryptOptions): - """Encrypt a stream data with given options. - - The encrypt API encrypts a stream data with the given options. - - Example: - from dapr.clients import DaprClient - from dapr.clients.grpc._crypto import EncryptOptions - - with DaprClient() as d: - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = d.encrypt( - data='hello dapr', - options=options, - ) - encrypted_data = resp.read() - - Args: - data (Union[str, bytes]): Data to be encrypted. - options (EncryptOptions): Encryption options. - - Returns: - Readable stream of `api_v1.EncryptResponse`. - - Raises: - ValueError: If component_name, key_name, or key_wrap_algorithm is empty. - """ - # Warnings and input validation - warn( - 'The Encrypt API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - component_name=options.component_name, - key_name=options.key_name, - key_wrap_algorithm=options.key_wrap_algorithm, - ) - - req_iterator = EncryptRequestIterator(data, options) - resp_stream = self._stub.EncryptAlpha1(req_iterator) - return EncryptResponse(resp_stream) - - def decrypt(self, data: Union[str, bytes], options: DecryptOptions): - """Decrypt a stream data with given options. - - The decrypt API decrypts a stream data with the given options. - - Example: - from dapr.clients import DaprClient - from dapr.clients.grpc._crypto import DecryptOptions - - with DaprClient() as d: - options = DecryptOptions( - component_name='crypto_component', - key_name='crypto_key', - ) - resp = d.decrypt( - data='hello dapr', - options=options, - ) - decrypted_data = resp.read() - - Args: - data (Union[str, bytes]): Data to be decrypted. - options (DecryptOptions): Decryption options. - - Returns: - Readable stream of `api_v1.DecryptResponse`. - - Raises: - ValueError: If component_name is empty. - """ - # Warnings and input validation - warn( - 'The Decrypt API is an Alpha version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - component_name=options.component_name, - ) - - req_iterator = DecryptRequestIterator(data, options) - resp_stream = self._stub.DecryptAlpha1(req_iterator) - return DecryptResponse(resp_stream) - - def start_workflow( - self, - workflow_component: str, - workflow_name: str, - input: Optional[Union[Any, bytes]] = None, - instance_id: Optional[str] = None, - workflow_options: Optional[Dict[str, str]] = dict(), - send_raw_bytes: bool = False, - ) -> StartWorkflowResponse: - """Starts a workflow. - - Args: - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - workflow_name (str): the name of the workflow that will be executed. - input (Optional[Union[Any, bytes]]): the input that the workflow will receive. - The input value will be serialized to JSON - by default. Use the send_raw_bytes param - to send unencoded binary input. - instance_id (Optional[str]): the name of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_options (Optional[Dict[str, str]]): the key-value options - that the workflow will receive. - send_raw_bytes (bool) if true, no serialization will be performed on the input - bytes - - Returns: - :class:`StartWorkflowResponse`: Instance ID associated with the started workflow - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(workflow_component=workflow_component, workflow_name=workflow_name) - - if instance_id is None: - instance_id = str(uuid.uuid4()) - - if isinstance(input, bytes) and send_raw_bytes: - encoded_data = input - else: - try: - encoded_data = json.dumps(input).encode('utf-8') if input is not None else bytes([]) - except TypeError: - raise DaprInternalError('start_workflow: input data must be JSON serializable') - except ValueError as e: - raise DaprInternalError(f'start_workflow JSON serialization error: {e}') - - # Actual start workflow invocation - req = api_v1.StartWorkflowRequest( - instance_id=instance_id, - workflow_component=workflow_component, - workflow_name=workflow_name, - options=workflow_options, - input=encoded_data, - ) - - try: - response = self._stub.StartWorkflowBeta1(req) - return StartWorkflowResponse(instance_id=response.instance_id) - except RpcError as err: - raise DaprInternalError(err.details()) - - def get_workflow(self, instance_id: str, workflow_component: str) -> GetWorkflowResponse: - """Gets information on a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`GetWorkflowResponse`: Instance ID associated with the started workflow - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual get workflow invocation - req = api_v1.GetWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - resp = self.retry_policy.run_rpc(self._stub.GetWorkflowBeta1, req) - if resp.created_at is None: - resp.created_at = datetime.now() - if resp.last_updated_at is None: - resp.last_updated_at = datetime.now() - return GetWorkflowResponse( - instance_id=instance_id, - workflow_name=resp.workflow_name, - created_at=resp.created_at, - last_updated_at=resp.last_updated_at, - runtime_status=getWorkflowRuntimeStatus(resp.runtime_status), - properties=resp.properties, - ) - except RpcError as err: - raise DaprInternalError(err.details()) - - def terminate_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Terminates a workflow. - - Args: - instance_id (str): the ID of the workflow instance, e.g. - `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual terminate workflow invocation - req = api_v1.TerminateWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self.retry_policy.run_rpc(self._stub.TerminateWorkflowBeta1.with_call, req) - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprInternalError(err.details()) - - def raise_workflow_event( - self, - instance_id: str, - workflow_component: str, - event_name: str, - event_data: Optional[Union[Any, bytes]] = None, - send_raw_bytes: bool = False, - ) -> DaprResponse: - """Raises an event on a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - event_data (Optional[Union[Any, bytes]]): the input that the workflow will receive. - The input value will be serialized to JSON - by default. Use the send_raw_bytes param - to send unencoded binary input. - event_data (Optional[Union[Any, bytes]]): the input to the event. - send_raw_bytes (bool) if true, no serialization will be performed on the input - bytes - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString( - instance_id=instance_id, workflow_component=workflow_component, event_name=event_name - ) - - if isinstance(event_data, bytes) and send_raw_bytes: - encoded_data = event_data - else: - if event_data is not None: - try: - encoded_data = ( - json.dumps(event_data).encode('utf-8') - if event_data is not None - else bytes([]) - ) - except TypeError: - raise DaprInternalError( - 'raise_workflow_event:\ - event_data must be JSON serializable' - ) - except ValueError as e: - raise DaprInternalError(f'raise_workflow_event JSON serialization error: {e}') - encoded_data = json.dumps(event_data).encode('utf-8') - else: - encoded_data = bytes([]) - - # Actual workflow raise event invocation - req = api_v1.RaiseEventWorkflowRequest( - instance_id=instance_id, - workflow_component=workflow_component, - event_name=event_name, - event_data=encoded_data, - ) - - try: - _, call = self.retry_policy.run_rpc(self._stub.RaiseEventWorkflowBeta1.with_call, req) - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprInternalError(err.details()) - - def pause_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Pause a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual pause workflow invocation - req = api_v1.PauseWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self.retry_policy.run_rpc(self._stub.PauseWorkflowBeta1.with_call, req) - - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprInternalError(err.details()) - - def resume_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Resumes a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual resume workflow invocation - req = api_v1.ResumeWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - _, call = self.retry_policy.run_rpc(self._stub.ResumeWorkflowBeta1.with_call, req) - - return DaprResponse(headers=call.initial_metadata()) - except RpcError as err: - raise DaprInternalError(err.details()) - - def purge_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: - """Purges a workflow. - - Args: - instance_id (str): the ID of the workflow instance, - e.g. `order_processing_workflow-103784`. - workflow_component (str): the name of the workflow component - that will run the workflow. e.g. `dapr`. - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - # Warnings and input validation - warn( - 'The Workflow API is a Beta version and is subject to change.', - UserWarning, - stacklevel=2, - ) - validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) - # Actual purge workflow invocation - req = api_v1.PurgeWorkflowRequest( - instance_id=instance_id, workflow_component=workflow_component - ) - - try: - response, call = self.retry_policy.run_rpc(self._stub.PurgeWorkflowBeta1.with_call, req) - - return DaprResponse(headers=call.initial_metadata()) - - except RpcError as err: - raise DaprInternalError(err.details()) - - def wait(self, timeout_s: float): - """Waits for sidecar to be available within the timeout. - - It checks if sidecar socket is available within the given timeout. - - The example gets a secret from secret store: - - from dapr.clients import DaprClient - - with DaprClient() as d: - d.wait(1) # waits for 1 second. - # Sidecar is available after this. - - Args: - timeout_s (float): timeout in seconds - """ - warn( - 'The wait method is deprecated. A health check is now done automatically on client ' - 'initialization.', - DeprecationWarning, - stacklevel=2, - ) - start = time.time() - while True: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(timeout_s) - try: - s.connect((self._uri.hostname, self._uri.port_as_int)) - return - except Exception as e: - remaining = (start + timeout_s) - time.time() - if remaining < 0: - raise e - time.sleep(min(1, remaining)) - - # --- - def get_metadata(self) -> GetMetadataResponse: - """Returns information about the sidecar allowing for runtime - discoverability. - - The metadata API returns a list of the components loaded, - the activated actors (if present) and attributes with information - attached. - - Each loaded component provides its name, type and version and also - information about supported features in the form of component - capabilities. - """ - try: - _resp, call = self.retry_policy.run_rpc(self._stub.GetMetadata.with_call, GrpcEmpty()) - except RpcError as err: - raise DaprGrpcError(err) from err - - response: api_v1.GetMetadataResponse = _resp # type alias - # Convert to more pythonic formats - active_actors_count = { - type_count.type: type_count.count for type_count in response.active_actors_count - } - registered_components = [ - RegisteredComponents( - name=i.name, type=i.type, version=i.version, capabilities=i.capabilities - ) - for i in response.registered_components - ] - extended_metadata = dict(response.extended_metadata.items()) - - return GetMetadataResponse( - application_id=response.id, - active_actors_count=active_actors_count, - registered_components=registered_components, - extended_metadata=extended_metadata, - headers=call.initial_metadata(), - ) - - def set_metadata(self, attributeName: str, attributeValue: str) -> DaprResponse: - """Adds a custom (extended) metadata attribute to the Dapr sidecar - information stored by the Metadata endpoint. - - The metadata API allows you to store additional attribute information - in the format of key-value pairs. These are ephemeral in-memory and - are not persisted if a sidecar is reloaded. This information should - be added at the time of a sidecar creation, for example, after the - application has started. - - Args: - attributeName (str): Custom attribute name. This is they key name - in the key-value pair. - attributeValue (str): Custom attribute value we want to store. - """ - # input validation - validateNotBlankString(attributeName=attributeName) - # Type-checking should catch this but better safe at run-time than sorry. - validateNotNone(attributeValue=attributeValue) - # Actual invocation - req = api_v1.SetMetadataRequest(key=attributeName, value=attributeValue) - _, call = self.retry_policy.run_rpc(self._stub.SetMetadata.with_call, req) - - return DaprResponse(call.initial_metadata()) - - def shutdown(self) -> DaprResponse: - """Shutdown the sidecar. - - This will ask the sidecar to gracefully shutdown. - - The example shutdown the sidecar: - - from dapr.clients import DaprClient - - with DaprClient() as d: - resp = d.shutdown() - - Returns: - :class:`DaprResponse` gRPC metadata returned from callee - """ - - _, call = self.retry_policy.run_rpc(self._stub.Shutdown.with_call, GrpcEmpty()) - - return DaprResponse(call.initial_metadata()) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import threading +import time +import socket +import json +import uuid + +from urllib.parse import urlencode + +from warnings import warn + +from typing import Callable, Dict, Optional, Text, Union, Sequence, List, Any +from typing_extensions import Self +from datetime import datetime +from google.protobuf.message import Message as GrpcMessage +from google.protobuf.empty_pb2 import Empty as GrpcEmpty + +import grpc # type: ignore +from grpc import ( # type: ignore + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, + RpcError, +) + +from dapr.clients.exceptions import DaprInternalError, DaprGrpcError +from dapr.clients.grpc._state import StateOptions, StateItem +from dapr.clients.grpc._helpers import getWorkflowRuntimeStatus +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.clients.grpc.subscription import Subscription, StreamInactiveError +from dapr.clients.grpc.interceptors import DaprClientInterceptor, DaprClientTimeoutInterceptor +from dapr.clients.health import DaprHealth +from dapr.clients.retry import RetryPolicy +from dapr.conf import settings +from dapr.proto import api_v1, api_service_v1, common_v1 +from dapr.proto.runtime.v1.dapr_pb2 import UnsubscribeConfigurationResponse +from dapr.version import __version__ + +from dapr.clients.grpc._helpers import ( + MetadataTuple, + to_bytes, + validateNotNone, + validateNotBlankString, +) +from dapr.conf.helpers import GrpcEndpoint +from dapr.clients.grpc._request import ( + InvokeMethodRequest, + BindingRequest, + TransactionalStateOperation, + EncryptRequestIterator, + DecryptRequestIterator, +) +from dapr.clients.grpc._response import ( + BindingResponse, + DaprResponse, + GetSecretResponse, + GetBulkSecretResponse, + GetMetadataResponse, + InvokeMethodResponse, + UnlockResponseStatus, + StateResponse, + BulkStatesResponse, + BulkStateItem, + ConfigurationResponse, + QueryResponse, + QueryResponseItem, + RegisteredComponents, + ConfigurationWatcher, + TryLockResponse, + UnlockResponse, + GetWorkflowResponse, + StartWorkflowResponse, + EncryptResponse, + DecryptResponse, + TopicEventResponse, +) + + +class DaprGrpcClient: + """The convenient layer implementation of Dapr gRPC APIs. + + This provides the wrappers and helpers to allows developers to use Dapr runtime gRPC API + easily and consistently. + + Examples: + + >>> from dapr.clients import DaprClient + >>> d = DaprClient() + >>> resp = d.invoke_method('callee', 'method', b'data') + + With context manager and custom message size limit: + + >>> from dapr.clients import DaprClient + >>> MAX = 64 * 1024 * 1024 # 64MB + >>> with DaprClient(max_message_length=MAX) as d: + ... resp = d.invoke_method('callee', 'method', b'data') + """ + + def __init__( + self, + address: Optional[str] = None, + interceptors: Optional[ + List[ + Union[ + UnaryUnaryClientInterceptor, + UnaryStreamClientInterceptor, + StreamUnaryClientInterceptor, + StreamStreamClientInterceptor, + ] + ] + ] = None, + max_grpc_message_length: Optional[int] = None, + retry_policy: Optional[RetryPolicy] = None, + ): + """Connects to Dapr Runtime and initializes gRPC client stub. + + Args: + address (str, optional): Dapr Runtime gRPC endpoint address. + interceptors (list of UnaryUnaryClientInterceptor or + UnaryStreamClientInterceptor or + StreamUnaryClientInterceptor or + StreamStreamClientInterceptor, optional): gRPC interceptors. + max_grpc_message_length (int, optional): The maximum grpc send and receive + message length in bytes. + retry_policy (RetryPolicy optional): Specifies retry behaviour + """ + DaprHealth.wait_until_ready() + self.retry_policy = retry_policy or RetryPolicy() + + useragent = f'dapr-sdk-python/{__version__}' + if not max_grpc_message_length: + options = [ + ('grpc.primary_user_agent', useragent), + ] + else: + options = [ + ('grpc.max_send_message_length', max_grpc_message_length), # type: ignore + ('grpc.max_receive_message_length', max_grpc_message_length), # type: ignore + ('grpc.primary_user_agent', useragent), + ] + + if not address: + address = settings.DAPR_GRPC_ENDPOINT or ( + f'{settings.DAPR_RUNTIME_HOST}:' f'{settings.DAPR_GRPC_PORT}' + ) + + try: + self._uri = GrpcEndpoint(address) + except ValueError as error: + raise DaprInternalError(f'{error}') from error + + if self._uri.tls: + self._channel = grpc.secure_channel( # type: ignore + self._uri.endpoint, + self.get_credentials(), + options=options, + ) + else: + self._channel = grpc.insecure_channel( # type: ignore + self._uri.endpoint, + options=options, + ) + + self._channel = grpc.intercept_channel(self._channel, DaprClientTimeoutInterceptor()) # type: ignore + + if settings.DAPR_API_TOKEN: + api_token_interceptor = DaprClientInterceptor( + [ + ('dapr-api-token', settings.DAPR_API_TOKEN), + ] + ) + self._channel = grpc.intercept_channel( # type: ignore + self._channel, api_token_interceptor + ) + if interceptors: + self._channel = grpc.intercept_channel( # type: ignore + self._channel, *interceptors + ) + + self._stub = api_service_v1.DaprStub(self._channel) + + @staticmethod + def get_credentials(): + # This method is used (overwritten) from tests + # to return credentials for self-signed certificates + return grpc.ssl_channel_credentials() # type: ignore + + def close(self): + """Closes Dapr runtime gRPC channel.""" + if hasattr(self, '_channel') and self._channel: + self._channel.close() + + def __del__(self): + self.close() + + def __enter__(self) -> Self: # type: ignore + return self + + def __exit__(self, exc_type, exc_value, traceback) -> None: + self.close() + + @staticmethod + def _get_http_extension( + http_verb: str, http_querystring: Optional[MetadataTuple] = None + ) -> common_v1.HTTPExtension: # type: ignore + verb = common_v1.HTTPExtension.Verb.Value(http_verb) # type: ignore + http_ext = common_v1.HTTPExtension(verb=verb) + if http_querystring is not None and len(http_querystring): + http_ext.querystring = urlencode(http_querystring) + return http_ext + + def invoke_method( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage] = '', + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invokes the target service to call method. + + This can invoke the specified target service to call method with bytes array data or + custom protocol buffer message. If your callee application uses http appcallback, + http_verb and http_querystring must be specified. Otherwise, Dapr runtime will return + error. + + The example calls `callee` service with bytes data, which implements grpc appcallback: + + from dapr.clients import DaprClient + + with DaprClient() as d: + resp = d.invoke_method( + app_id='callee', + method_name='method', + data=b'message', + content_type='text/plain', + ) + + # resp.content includes the content in bytes. + # resp.content_type specifies the content type of resp.content. + # Thus, resp.content can be deserialized properly. + + When sending custom protocol buffer message object, it doesn't requires content_type: + + from dapr.clients import DaprClient + + req_data = dapr_example_v1.CustomRequestMessage(data='custom') + + with DaprClient() as d: + resp = d.invoke_method( + app_id='callee', + method_name='method', + data=req_data, + ) + # Create protocol buffer object + resp_data = dapr_example_v1.CustomResponseMessage() + # Deserialize to resp_data + resp.unpack(resp_data) + + The example calls `callee` service which implements http appcallback: + + from dapr.clients import DaprClient + + with DaprClient() as d: + resp = d.invoke_method( + app_id='callee', + method_name='method', + data=b'message', + content_type='text/plain', + http_verb='POST', + http_querystring=( + ('key1', 'value1') + ), + ) + + # resp.content includes the content in bytes. + # resp.content_type specifies the content type of resp.content. + # Thus, resp.content can be deserialized properly. + + Args: + app_id (str): the callee app id + method (str): the method name which is called + data (bytes or :obj:`google.protobuf.message.Message`, optional): bytes + or Message for data which will be sent to app id + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + http_verb (str, optional): http method verb to call HTTP callee application + http_querystring (tuple, optional): the tuple to represent query string + timeout (int, optional): request timeout in seconds + + Returns: + :class:`InvokeMethodResponse` object returned from callee + """ + warn( + 'invoke_method with protocol gRPC is deprecated. Use gRPC proxying instead.', + DeprecationWarning, + stacklevel=2, + ) + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req_data = InvokeMethodRequest(data, content_type) + http_ext = None + if http_verb: + http_ext = self._get_http_extension(http_verb, http_querystring) + + content_type = '' + if req_data.content_type: + content_type = req_data.content_type + req = api_v1.InvokeServiceRequest( + id=app_id, + message=common_v1.InvokeRequest( + method=method_name, + data=req_data.proto, + content_type=content_type, + http_extension=http_ext, + ), + ) + + response, call = self.retry_policy.run_rpc( + self._stub.InvokeService.with_call, req, metadata=metadata, timeout=timeout + ) + + resp_data = InvokeMethodResponse(response.data, response.content_type) + resp_data.headers = call.initial_metadata() # type: ignore + return resp_data + + def invoke_binding( + self, + binding_name: str, + operation: str, + data: Union[bytes, str] = '', + binding_metadata: Dict[str, str] = {}, + metadata: Optional[MetadataTuple] = None, + ) -> BindingResponse: + """Invokes the output binding with the specified operation. + + The data field takes any JSON serializable value and acts as the + payload to be sent to the output binding. The metadata field is an + array of key/value pairs and allows you to set binding specific metadata + for each call. The operation field tells the Dapr binding which operation + it should perform. + + The example calls output `binding` service with bytes data: + + from dapr.clients import DaprClient + + with DaprClient() as d: + resp = d.invoke_binding( + binding_name = 'kafkaBinding', + operation = 'create', + data = b'message', + ) + # resp.data includes the response data in bytes. + + Args: + binding_name (str): the name of the binding as defined in the components + operation (str): the operation to perform on the binding + data (bytes or str, optional): bytes or str for data which will sent to the binding + binding_metadata (dict, optional): Dapr metadata for output binding + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`InvokeBindingResponse` object returned from binding + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req_data = BindingRequest(data, binding_metadata) + + req = api_v1.InvokeBindingRequest( + name=binding_name, + data=req_data.data, + metadata=req_data.binding_metadata, + operation=operation, + ) + + response, call = self.retry_policy.run_rpc( + self._stub.InvokeBinding.with_call, req, metadata=metadata + ) + return BindingResponse(response.data, dict(response.metadata), call.initial_metadata()) + + def publish_event( + self, + pubsub_name: str, + topic_name: str, + data: Union[bytes, str], + publish_metadata: Dict[str, str] = {}, + metadata: Optional[MetadataTuple] = None, + data_content_type: Optional[str] = None, + ) -> DaprResponse: + """Publish to a given topic. + This publishes an event with bytes array or str data to a specified topic and + specified pubsub component. The str data is encoded into bytes with default + charset of utf-8. Custom metadata can be passed with the metadata field which + will be passed on a gRPC metadata. + + The example publishes a byte array event to a topic: + + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.publish_event( + pubsub_name='pubsub_1', + topic_name='TOPIC_A', + data=b'message', + publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, + ) + # resp.headers includes the gRPC initial metadata. + + Args: + pubsub_name (str): the name of the pubsub component + topic_name (str): the topic name to publish to + data (bytes or str): bytes or str for data + publish_metadata (Dict[str, str], optional): Dapr metadata per Pub/Sub message + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + data_content_type: (str, optional): content type of the data payload + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not isinstance(data, bytes) and not isinstance(data, str): + raise ValueError(f'invalid type for data {type(data)}') + + req_data: bytes + if isinstance(data, bytes): + req_data = data + else: + if isinstance(data, str): + req_data = data.encode('utf-8') + + content_type = '' + if data_content_type: + content_type = data_content_type + req = api_v1.PublishEventRequest( + pubsub_name=pubsub_name, + topic=topic_name, + data=req_data, + data_content_type=content_type, + metadata=publish_metadata, + ) + + try: + # response is google.protobuf.Empty + _, call = self.retry_policy.run_rpc( + self._stub.PublishEvent.with_call, req, metadata=metadata + ) + except RpcError as err: + raise DaprGrpcError(err) from err + + return DaprResponse(call.initial_metadata()) + + def subscribe( + self, + pubsub_name: str, + topic: str, + metadata: Optional[MetadataTuple] = None, + dead_letter_topic: Optional[str] = None, + ) -> Subscription: + """ + Subscribe to a topic with a bidirectional stream + + Args: + pubsub_name (str): The name of the pubsub component. + topic (str): The name of the topic. + metadata (Optional[MetadataTuple]): Additional metadata for the subscription. + dead_letter_topic (Optional[str]): Name of the dead-letter topic. + timeout (Optional[int]): The time in seconds to wait for a message before returning None + If not set, the `next_message` method will block indefinitely + until a message is received. + + Returns: + Subscription: The Subscription object managing the stream. + """ + subscription = Subscription(self._stub, pubsub_name, topic, metadata, dead_letter_topic) + subscription.start() + return subscription + + def subscribe_with_handler( + self, + pubsub_name: str, + topic: str, + handler_fn: Callable[..., TopicEventResponse], + metadata: Optional[MetadataTuple] = None, + dead_letter_topic: Optional[str] = None, + ) -> Callable: + """ + Subscribe to a topic with a bidirectional stream and a message handler function + + Args: + pubsub_name (str): The name of the pubsub component. + topic (str): The name of the topic. + handler_fn (Callable[..., TopicEventResponse]): The function to call when a message is received. + metadata (Optional[MetadataTuple]): Additional metadata for the subscription. + dead_letter_topic (Optional[str]): Name of the dead-letter topic. + timeout (Optional[int]): The time in seconds to wait for a message before returning None + If not set, the `next_message` method will block indefinitely + until a message is received. + """ + subscription = self.subscribe(pubsub_name, topic, metadata, dead_letter_topic) + + def stream_messages(sub): + while True: + try: + message = sub.next_message() + if message: + # Process the message + response = handler_fn(message) + if response: + subscription.respond(message, response.status) + else: + # No message received + continue + except StreamInactiveError: + break + + def close_subscription(): + subscription.close() + + streaming_thread = threading.Thread(target=stream_messages, args=(subscription,)) + streaming_thread.start() + + return close_subscription + + def get_state( + self, + store_name: str, + key: str, + state_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> StateResponse: + """Gets value from a statestore with a key + + The example gets value from a statestore: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.get_state( + store_name='state_store' + key='key_1', + state={"key": "value"}, + state_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to get from + key (str): the key of the key-value pair to be gotten + state_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`StateResponse` gRPC metadata returned from callee + and value obtained from the state store + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req = api_v1.GetStateRequest(store_name=store_name, key=key, metadata=state_metadata) + try: + response, call = self.retry_policy.run_rpc( + self._stub.GetState.with_call, req, metadata=metadata + ) + return StateResponse( + data=response.data, etag=response.etag, headers=call.initial_metadata() + ) + except RpcError as err: + raise DaprGrpcError(err) from err + + def get_bulk_state( + self, + store_name: str, + keys: Sequence[str], + parallelism: int = 1, + states_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> BulkStatesResponse: + """Gets values from a statestore with keys + + The example gets value from a statestore: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.get_bulk_state( + store_name='state_store', + keys=['key_1', key_2], + parallelism=2, + states_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to get from + keys (Sequence[str]): the keys to be retrieved + parallelism (int): number of items to be retrieved in parallel + states_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`BulkStatesResponse` gRPC metadata returned from callee + and value obtained from the state store + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req = api_v1.GetBulkStateRequest( + store_name=store_name, keys=keys, parallelism=parallelism, metadata=states_metadata + ) + + try: + response, call = self.retry_policy.run_rpc( + self._stub.GetBulkState.with_call, req, metadata=metadata + ) + except RpcError as err: + raise DaprGrpcError(err) from err + + items = [] + for item in response.items: + items.append( + BulkStateItem(key=item.key, data=item.data, etag=item.etag, error=item.error) + ) + return BulkStatesResponse(items=items, headers=call.initial_metadata()) + + def query_state( + self, store_name: str, query: str, states_metadata: Optional[Dict[str, str]] = dict() + ) -> QueryResponse: + """Queries a statestore with a query + + For details on supported queries see https://docs.dapr.io/ + + This example queries a statestore: + from dapr.clients import DaprClient + + query = ''' + { + "filter": { + "EQ": { "state": "CA" } + }, + "sort": [ + { + "key": "person.id", + "order": "DESC" + } + ] + } + ''' + + with DaprClient() as d: + resp = d.query_state( + store_name='state_store', + query=query, + states_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to query + query (str): the query to be executed + states_metadata (Dict[str, str], optional): custom metadata for state request + + Returns: + :class:`QueryStateResponse` gRPC metadata returned from callee, + pagination token and results of the query + """ + warn( + 'The State Store Query API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req = api_v1.QueryStateRequest(store_name=store_name, query=query, metadata=states_metadata) + + try: + response, call = self.retry_policy.run_rpc(self._stub.QueryStateAlpha1.with_call, req) + except RpcError as err: + raise DaprGrpcError(err) from err + + results = [] + for item in response.results: + results.append( + QueryResponseItem(key=item.key, value=item.data, etag=item.etag, error=item.error) + ) + + return QueryResponse( + token=response.token, + results=results, + metadata=response.metadata, + headers=call.initial_metadata(), + ) + + def save_state( + self, + store_name: str, + key: str, + value: Union[bytes, str], + etag: Optional[str] = None, + options: Optional[StateOptions] = None, + state_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> DaprResponse: + """Saves key-value pairs to a statestore + + This saves a value to the statestore with a given key and state store name. + Options for request can be passed with the options field and custom + metadata can be passed with metadata field. + + The example saves states to a statestore: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.save_state( + store_name='state_store', + key='key1', + value='value1', + etag='etag', + state_metadata={"metakey": "metavalue"}, + ) + + Args: + store_name (str): the state store name to save to + key (str): the key to be saved + value (bytes or str): the value to be saved + etag (str, optional): the etag to save with + options (StateOptions, optional): custom options + for concurrency and consistency + state_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + Raises: + ValueError: value is not bytes or str + ValueError: store_name is empty + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not isinstance(value, (bytes, str)): + raise ValueError(f'invalid type for data {type(value)}') + + req_value = value + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + if options is None: + state_options = None + else: + state_options = options.get_proto() + + state = common_v1.StateItem( + key=key, + value=to_bytes(req_value), + etag=common_v1.Etag(value=etag) if etag is not None else None, + options=state_options, + metadata=state_metadata, + ) + + req = api_v1.SaveStateRequest(store_name=store_name, states=[state]) + try: + _, call = self.retry_policy.run_rpc( + self._stub.SaveState.with_call, req, metadata=metadata + ) + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprGrpcError(err) from err + + def save_bulk_state( + self, store_name: str, states: List[StateItem], metadata: Optional[MetadataTuple] = None + ) -> DaprResponse: + """Saves state items to a statestore + + This saves a given state item into the statestore specified by store_name. + + The example saves states to a statestore: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.save_bulk_state( + store_name='state_store', + states=[StateItem(key='key1', value='value1'), + StateItem(key='key2', value='value2', etag='etag'),], + ) + + Args: + store_name (str): the state store name to save to + states (List[StateItem]): list of states to save + metadata (tuple, optional): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + Raises: + ValueError: states is empty + ValueError: store_name is empty + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not states or len(states) == 0: + raise ValueError('States to be saved cannot be empty') + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + req_states = [ + common_v1.StateItem( + key=i.key, + value=to_bytes(i.value), + etag=common_v1.Etag(value=i.etag) if i.etag is not None else None, + options=i.options, + metadata=i.metadata, + ) + for i in states + ] + + req = api_v1.SaveStateRequest(store_name=store_name, states=req_states) + + try: + _, call = self.retry_policy.run_rpc( + self._stub.SaveState.with_call, req, metadata=metadata + ) + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprGrpcError(err) from err + + def execute_state_transaction( + self, + store_name: str, + operations: Sequence[TransactionalStateOperation], + transactional_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> DaprResponse: + """Saves or deletes key-value pairs to a statestore as a transaction + + This saves or deletes key-values to the statestore as part of a single transaction, + transaction_metadata is used for the transaction operation, while metadata is used + for the GRPC call. + + The example saves states to a statestore: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.execute_state_transaction( + store_name='state_store', + operations=[ + TransactionalStateOperation(key=key, data=value), + TransactionalStateOperation(key=another_key, data=another_value), + TransactionalStateOperation( + operation_type=TransactionOperationType.delete, + key=key_to_delete), + ], + transactional_metadata={"header1": "value1"}, + ) + + Args: + store_name (str): the state store name to save to + operations (Sequence[TransactionalStateOperation]): the transaction operations + transactional_metadata (Dict[str, str], optional): Dapr metadata for transaction + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token headers ' + 'and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + req_ops = [ + api_v1.TransactionalStateOperation( + operationType=o.operation_type.value, + request=common_v1.StateItem( + key=o.key, + value=to_bytes(o.data), + etag=common_v1.Etag(value=o.etag) if o.etag is not None else None, + ), + ) + for o in operations + ] + + req = api_v1.ExecuteStateTransactionRequest( + storeName=store_name, operations=req_ops, metadata=transactional_metadata + ) + + try: + _, call = self.retry_policy.run_rpc( + self._stub.ExecuteStateTransaction.with_call, req, metadata=metadata + ) + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprGrpcError(err) from err + + def delete_state( + self, + store_name: str, + key: str, + etag: Optional[str] = None, + options: Optional[StateOptions] = None, + state_metadata: Optional[Dict[str, str]] = dict(), + metadata: Optional[MetadataTuple] = None, + ) -> DaprResponse: + """Deletes key-value pairs from a statestore + + This deletes a value from the statestore with a given key and state store name. + Options for request can be passed with the options field and custom + metadata can be passed with metadata field. + + The example deletes states from a statestore: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.delete_state( + store_name='state_store', + key='key1', + etag='etag', + state_metadata={"header1": "value1"}, + ) + + Args: + store_name (str): the state store name to delete from + key (str): the key of the key-value pair to delete + etag (str, optional): the etag to delete with + options (StateOptions, optional): custom options + for concurrency and consistency + state_metadata (Dict[str, str], optional): Dapr metadata for state request + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token ' + 'headers and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('State store name cannot be empty') + + if options is None: + state_options = None + else: + state_options = options.get_proto() + + etag_object = common_v1.Etag(value=etag) if etag is not None else None + req = api_v1.DeleteStateRequest( + store_name=store_name, + key=key, + etag=etag_object, + options=state_options, + metadata=state_metadata, + ) + + try: + _, call = self.retry_policy.run_rpc( + self._stub.DeleteState.with_call, req, metadata=metadata + ) + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprGrpcError(err) from err + + def get_secret( + self, + store_name: str, + key: str, + secret_metadata: Optional[Dict[str, str]] = {}, + metadata: Optional[MetadataTuple] = None, + ) -> GetSecretResponse: + """Get secret with a given key. + + This gets a secret from secret store with a given key and secret store name. + Metadata for request can be passed with the secret_metadata field and custom + metadata can be passed with metadata field. + + + The example gets a secret from secret store: + + from dapr.clients import DaprClient + + with DaprClient() as d: + resp = d.get_secret( + store_name='secretstoreA', + key='keyA', + secret_metadata={'header1', 'value1'} + ) + + # resp.headers includes the gRPC initial metadata. + # resp.trailers includes that gRPC trailing metadata. + + Args: + store_name (str): store name to get secret from + key (str): str for key + secret_metadata (Dict[str, str], Optional): Dapr metadata for secrets request + metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`GetSecretResponse` object with the secret and metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token ' + 'headers and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req = api_v1.GetSecretRequest(store_name=store_name, key=key, metadata=secret_metadata) + + response, call = self.retry_policy.run_rpc( + self._stub.GetSecret.with_call, req, metadata=metadata + ) + + return GetSecretResponse(secret=response.data, headers=call.initial_metadata()) + + def get_bulk_secret( + self, + store_name: str, + secret_metadata: Optional[Dict[str, str]] = {}, + metadata: Optional[MetadataTuple] = None, + ) -> GetBulkSecretResponse: + """Get all granted secrets. + + This gets all granted secrets from secret store. + Metadata for request can be passed with the secret_metadata field. + + + The example gets all secrets from secret store: + + from dapr.clients import DaprClient + + with DaprClient() as d: + resp = d.get_bulk_secret( + store_name='secretstoreA', + secret_metadata={'header1', 'value1'} + ) + + # resp.headers includes the gRPC initial metadata. + # resp.trailers includes that gRPC trailing metadata. + + Args: + store_name (str): store name to get secret from + secret_metadata (Dict[str, Dict[str, str]], Optional): Dapr metadata of secrets request + metadata (MetadataTuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`GetBulkSecretResponse` object with secrets and metadata returned from callee + """ + if metadata is not None: + warn( + 'metadata argument is deprecated. Dapr already intercepts API token ' + 'headers and this is not needed.', + DeprecationWarning, + stacklevel=2, + ) + + req = api_v1.GetBulkSecretRequest(store_name=store_name, metadata=secret_metadata) + + response, call = self.retry_policy.run_rpc( + self._stub.GetBulkSecret.with_call, req, metadata=metadata + ) + + secrets_map = {} + for key in response.data.keys(): + secret_response = response.data[key] + secrets_submap = {} + for subkey in secret_response.secrets.keys(): + secrets_submap[subkey] = secret_response.secrets[subkey] + secrets_map[key] = secrets_submap + + return GetBulkSecretResponse(secrets=secrets_map, headers=call.initial_metadata()) + + def get_configuration( + self, store_name: str, keys: List[str], config_metadata: Optional[Dict[str, str]] = dict() + ) -> ConfigurationResponse: + """Gets value from a config store with a key + + The example gets value from a config store: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.get_configuration( + store_name='state_store' + keys=['key_1'], + config_metadata={"metakey": "metavalue"} + ) + + Args: + store_name (str): the state store name to get from + keys (List[str]): the keys of the key-value pairs to be gotten + config_metadata (Dict[str, str], optional): Dapr metadata for configuration + + Returns: + :class:`ConfigurationResponse` gRPC metadata returned from callee + and value obtained from the config store + """ + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('Config store name cannot be empty to get the configuration') + req = api_v1.GetConfigurationRequest( + store_name=store_name, keys=keys, metadata=config_metadata + ) + response, call = self.retry_policy.run_rpc(self._stub.GetConfiguration.with_call, req) + return ConfigurationResponse(items=response.items, headers=call.initial_metadata()) + + def subscribe_configuration( + self, + store_name: str, + keys: List[str], + handler: Callable[[Text, ConfigurationResponse], None], + config_metadata: Optional[Dict[str, str]] = dict(), + ) -> Text: + """Gets changed value from a config store with a key + + The example gets value from a config store: + from dapr.clients import DaprClient + with DaprClient() as d: + resp = d.subscribe_configuration( + store_name='state_store' + keys=['key_1'], + handler=handler, + config_metadata={"metakey": "metavalue"} + ) + + Args: + store_name (str): the state store name to get from + keys (str array): the keys of the key-value pairs to be gotten + handler(func (key, ConfigurationResponse)): the callback function to be called + config_metadata (Dict[str, str], optional): Dapr metadata for configuration + + Returns: + id (str): subscription id, which can be used to unsubscribe later + """ + + if not store_name or len(store_name) == 0 or len(store_name.strip()) == 0: + raise ValueError('Config store name cannot be empty to get the configuration') + + configWatcher = ConfigurationWatcher() + id = configWatcher.watch_configuration( + self._stub, store_name, keys, handler, config_metadata + ) + return id + + def unsubscribe_configuration(self, store_name: str, id: str) -> bool: + """Unsubscribes from configuration changes. + + Args: + store_name (str): the state store name to unsubscribe from + id (str): the subscription id to unsubscribe + + Returns: + bool: True if unsubscribed successfully, False otherwise + """ + req = api_v1.UnsubscribeConfigurationRequest(store_name=store_name, id=id) + response: UnsubscribeConfigurationResponse = self._stub.UnsubscribeConfiguration(req) + return response.ok + + def try_lock( + self, store_name: str, resource_id: str, lock_owner: str, expiry_in_seconds: int + ) -> TryLockResponse: + """Tries to get a lock with an expiry. + + You can use the result of this operation directly on an `if` statement: + + if client.try_lock(store_name, resource_id, first_client_id, expiry_s): + # lock acquired successfully... + + You can also inspect the response's `success` attribute: + + response = client.try_lock(store_name, resource_id, first_client_id, expiry_s) + if response.success: + # lock acquired successfully... + + Finally, you can use this response with a `with` statement, and have the lock + be automatically unlocked after the with-statement scope ends + + with client.try_lock(store_name, resource_id, first_client_id, expiry_s) as lock: + if lock: + # lock acquired successfully... + # Lock automatically unlocked at this point, no need to call client->unlock(...) + + Args: + store_name (str): the lock store name, e.g. `redis`. + resource_id (str): the lock key. e.g. `order_id_111`. + It stands for "which resource I want to protect". + lock_owner (str): indicates the identifier of lock owner. + expiry_in_seconds (int): The length of time (in seconds) for which this lock + will be held and after which it expires. + + Returns: + :class:`TryLockResponse`: With the result of the try-lock operation. + """ + # Warnings and input validation + warn( + 'The Distributed Lock API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + store_name=store_name, resource_id=resource_id, lock_owner=lock_owner + ) + if not expiry_in_seconds or expiry_in_seconds < 1: + raise ValueError('expiry_in_seconds must be a positive number') + # Actual tryLock invocation + req = api_v1.TryLockRequest( + store_name=store_name, + resource_id=resource_id, + lock_owner=lock_owner, + expiry_in_seconds=expiry_in_seconds, + ) + response, call = self.retry_policy.run_rpc(self._stub.TryLockAlpha1.with_call, req) + return TryLockResponse( + success=response.success, + client=self, + store_name=store_name, + resource_id=resource_id, + lock_owner=lock_owner, + headers=call.initial_metadata(), + ) + + def unlock(self, store_name: str, resource_id: str, lock_owner: str) -> UnlockResponse: + """Unlocks a lock. + + Args: + store_name (str): the lock store name, e.g. `redis`. + resource_id (str): the lock key. e.g. `order_id_111`. + It stands for "which resource I want to protect". + lock_owner (str): indicates the identifier of lock owner. + metadata (tuple, optional, DEPRECATED): gRPC custom metadata + + Returns: + :class:`UnlockResponseStatus`: Status of the request, + `UnlockResponseStatus.success` if it was successful of some other + status otherwise. + """ + # Warnings and input validation + warn( + 'The Distributed Lock API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + store_name=store_name, resource_id=resource_id, lock_owner=lock_owner + ) + # Actual unlocking invocation + req = api_v1.UnlockRequest( + store_name=store_name, resource_id=resource_id, lock_owner=lock_owner + ) + response, call = self.retry_policy.run_rpc(self._stub.UnlockAlpha1.with_call, req) + + return UnlockResponse( + status=UnlockResponseStatus(response.status), headers=call.initial_metadata() + ) + + def encrypt(self, data: Union[str, bytes], options: EncryptOptions): + """Encrypt a stream data with given options. + + The encrypt API encrypts a stream data with the given options. + + Example: + from dapr.clients import DaprClient + from dapr.clients.grpc._crypto import EncryptOptions + + with DaprClient() as d: + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = d.encrypt( + data='hello dapr', + options=options, + ) + encrypted_data = resp.read() + + Args: + data (Union[str, bytes]): Data to be encrypted. + options (EncryptOptions): Encryption options. + + Returns: + Readable stream of `api_v1.EncryptResponse`. + + Raises: + ValueError: If component_name, key_name, or key_wrap_algorithm is empty. + """ + # Warnings and input validation + warn( + 'The Encrypt API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + component_name=options.component_name, + key_name=options.key_name, + key_wrap_algorithm=options.key_wrap_algorithm, + ) + + req_iterator = EncryptRequestIterator(data, options) + resp_stream = self._stub.EncryptAlpha1(req_iterator) + return EncryptResponse(resp_stream) + + def decrypt(self, data: Union[str, bytes], options: DecryptOptions): + """Decrypt a stream data with given options. + + The decrypt API decrypts a stream data with the given options. + + Example: + from dapr.clients import DaprClient + from dapr.clients.grpc._crypto import DecryptOptions + + with DaprClient() as d: + options = DecryptOptions( + component_name='crypto_component', + key_name='crypto_key', + ) + resp = d.decrypt( + data='hello dapr', + options=options, + ) + decrypted_data = resp.read() + + Args: + data (Union[str, bytes]): Data to be decrypted. + options (DecryptOptions): Decryption options. + + Returns: + Readable stream of `api_v1.DecryptResponse`. + + Raises: + ValueError: If component_name is empty. + """ + # Warnings and input validation + warn( + 'The Decrypt API is an Alpha version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + component_name=options.component_name, + ) + + req_iterator = DecryptRequestIterator(data, options) + resp_stream = self._stub.DecryptAlpha1(req_iterator) + return DecryptResponse(resp_stream) + + def start_workflow( + self, + workflow_component: str, + workflow_name: str, + input: Optional[Union[Any, bytes]] = None, + instance_id: Optional[str] = None, + workflow_options: Optional[Dict[str, str]] = dict(), + send_raw_bytes: bool = False, + ) -> StartWorkflowResponse: + """Starts a workflow. + + Args: + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + workflow_name (str): the name of the workflow that will be executed. + input (Optional[Union[Any, bytes]]): the input that the workflow will receive. + The input value will be serialized to JSON + by default. Use the send_raw_bytes param + to send unencoded binary input. + instance_id (Optional[str]): the name of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_options (Optional[Dict[str, str]]): the key-value options + that the workflow will receive. + send_raw_bytes (bool) if true, no serialization will be performed on the input + bytes + + Returns: + :class:`StartWorkflowResponse`: Instance ID associated with the started workflow + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(workflow_component=workflow_component, workflow_name=workflow_name) + + if instance_id is None: + instance_id = str(uuid.uuid4()) + + if isinstance(input, bytes) and send_raw_bytes: + encoded_data = input + else: + try: + encoded_data = json.dumps(input).encode('utf-8') if input is not None else bytes([]) + except TypeError: + raise DaprInternalError('start_workflow: input data must be JSON serializable') + except ValueError as e: + raise DaprInternalError(f'start_workflow JSON serialization error: {e}') + + # Actual start workflow invocation + req = api_v1.StartWorkflowRequest( + instance_id=instance_id, + workflow_component=workflow_component, + workflow_name=workflow_name, + options=workflow_options, + input=encoded_data, + ) + + try: + response = self._stub.StartWorkflowBeta1(req) + return StartWorkflowResponse(instance_id=response.instance_id) + except RpcError as err: + raise DaprInternalError(err.details()) + + def get_workflow(self, instance_id: str, workflow_component: str) -> GetWorkflowResponse: + """Gets information on a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`GetWorkflowResponse`: Instance ID associated with the started workflow + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual get workflow invocation + req = api_v1.GetWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + resp = self.retry_policy.run_rpc(self._stub.GetWorkflowBeta1, req) + if resp.created_at is None: + resp.created_at = datetime.now() + if resp.last_updated_at is None: + resp.last_updated_at = datetime.now() + return GetWorkflowResponse( + instance_id=instance_id, + workflow_name=resp.workflow_name, + created_at=resp.created_at, + last_updated_at=resp.last_updated_at, + runtime_status=getWorkflowRuntimeStatus(resp.runtime_status), + properties=resp.properties, + ) + except RpcError as err: + raise DaprInternalError(err.details()) + + def terminate_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Terminates a workflow. + + Args: + instance_id (str): the ID of the workflow instance, e.g. + `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual terminate workflow invocation + req = api_v1.TerminateWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self.retry_policy.run_rpc(self._stub.TerminateWorkflowBeta1.with_call, req) + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprInternalError(err.details()) + + def raise_workflow_event( + self, + instance_id: str, + workflow_component: str, + event_name: str, + event_data: Optional[Union[Any, bytes]] = None, + send_raw_bytes: bool = False, + ) -> DaprResponse: + """Raises an event on a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + event_data (Optional[Union[Any, bytes]]): the input that the workflow will receive. + The input value will be serialized to JSON + by default. Use the send_raw_bytes param + to send unencoded binary input. + event_data (Optional[Union[Any, bytes]]): the input to the event. + send_raw_bytes (bool) if true, no serialization will be performed on the input + bytes + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString( + instance_id=instance_id, workflow_component=workflow_component, event_name=event_name + ) + + if isinstance(event_data, bytes) and send_raw_bytes: + encoded_data = event_data + else: + if event_data is not None: + try: + encoded_data = ( + json.dumps(event_data).encode('utf-8') + if event_data is not None + else bytes([]) + ) + except TypeError: + raise DaprInternalError( + 'raise_workflow_event:\ + event_data must be JSON serializable' + ) + except ValueError as e: + raise DaprInternalError(f'raise_workflow_event JSON serialization error: {e}') + encoded_data = json.dumps(event_data).encode('utf-8') + else: + encoded_data = bytes([]) + + # Actual workflow raise event invocation + req = api_v1.RaiseEventWorkflowRequest( + instance_id=instance_id, + workflow_component=workflow_component, + event_name=event_name, + event_data=encoded_data, + ) + + try: + _, call = self.retry_policy.run_rpc(self._stub.RaiseEventWorkflowBeta1.with_call, req) + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprInternalError(err.details()) + + def pause_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Pause a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual pause workflow invocation + req = api_v1.PauseWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self.retry_policy.run_rpc(self._stub.PauseWorkflowBeta1.with_call, req) + + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprInternalError(err.details()) + + def resume_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Resumes a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual resume workflow invocation + req = api_v1.ResumeWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + _, call = self.retry_policy.run_rpc(self._stub.ResumeWorkflowBeta1.with_call, req) + + return DaprResponse(headers=call.initial_metadata()) + except RpcError as err: + raise DaprInternalError(err.details()) + + def purge_workflow(self, instance_id: str, workflow_component: str) -> DaprResponse: + """Purges a workflow. + + Args: + instance_id (str): the ID of the workflow instance, + e.g. `order_processing_workflow-103784`. + workflow_component (str): the name of the workflow component + that will run the workflow. e.g. `dapr`. + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + # Warnings and input validation + warn( + 'The Workflow API is a Beta version and is subject to change.', + UserWarning, + stacklevel=2, + ) + validateNotBlankString(instance_id=instance_id, workflow_component=workflow_component) + # Actual purge workflow invocation + req = api_v1.PurgeWorkflowRequest( + instance_id=instance_id, workflow_component=workflow_component + ) + + try: + response, call = self.retry_policy.run_rpc(self._stub.PurgeWorkflowBeta1.with_call, req) + + return DaprResponse(headers=call.initial_metadata()) + + except RpcError as err: + raise DaprInternalError(err.details()) + + def wait(self, timeout_s: float): + """Waits for sidecar to be available within the timeout. + + It checks if sidecar socket is available within the given timeout. + + The example gets a secret from secret store: + + from dapr.clients import DaprClient + + with DaprClient() as d: + d.wait(1) # waits for 1 second. + # Sidecar is available after this. + + Args: + timeout_s (float): timeout in seconds + """ + warn( + 'The wait method is deprecated. A health check is now done automatically on client ' + 'initialization.', + DeprecationWarning, + stacklevel=2, + ) + start = time.time() + while True: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(timeout_s) + try: + s.connect((self._uri.hostname, self._uri.port_as_int)) + return + except Exception as e: + remaining = (start + timeout_s) - time.time() + if remaining < 0: + raise e + time.sleep(min(1, remaining)) + + # --- + def get_metadata(self) -> GetMetadataResponse: + """Returns information about the sidecar allowing for runtime + discoverability. + + The metadata API returns a list of the components loaded, + the activated actors (if present) and attributes with information + attached. + + Each loaded component provides its name, type and version and also + information about supported features in the form of component + capabilities. + """ + try: + _resp, call = self.retry_policy.run_rpc(self._stub.GetMetadata.with_call, GrpcEmpty()) + except RpcError as err: + raise DaprGrpcError(err) from err + + response: api_v1.GetMetadataResponse = _resp # type alias + # Convert to more pythonic formats + active_actors_count = { + type_count.type: type_count.count for type_count in response.active_actors_count + } + registered_components = [ + RegisteredComponents( + name=i.name, type=i.type, version=i.version, capabilities=i.capabilities + ) + for i in response.registered_components + ] + extended_metadata = dict(response.extended_metadata.items()) + + return GetMetadataResponse( + application_id=response.id, + active_actors_count=active_actors_count, + registered_components=registered_components, + extended_metadata=extended_metadata, + headers=call.initial_metadata(), + ) + + def set_metadata(self, attributeName: str, attributeValue: str) -> DaprResponse: + """Adds a custom (extended) metadata attribute to the Dapr sidecar + information stored by the Metadata endpoint. + + The metadata API allows you to store additional attribute information + in the format of key-value pairs. These are ephemeral in-memory and + are not persisted if a sidecar is reloaded. This information should + be added at the time of a sidecar creation, for example, after the + application has started. + + Args: + attributeName (str): Custom attribute name. This is they key name + in the key-value pair. + attributeValue (str): Custom attribute value we want to store. + """ + # input validation + validateNotBlankString(attributeName=attributeName) + # Type-checking should catch this but better safe at run-time than sorry. + validateNotNone(attributeValue=attributeValue) + # Actual invocation + req = api_v1.SetMetadataRequest(key=attributeName, value=attributeValue) + _, call = self.retry_policy.run_rpc(self._stub.SetMetadata.with_call, req) + + return DaprResponse(call.initial_metadata()) + + def shutdown(self) -> DaprResponse: + """Shutdown the sidecar. + + This will ask the sidecar to gracefully shutdown. + + The example shutdown the sidecar: + + from dapr.clients import DaprClient + + with DaprClient() as d: + resp = d.shutdown() + + Returns: + :class:`DaprResponse` gRPC metadata returned from callee + """ + + _, call = self.retry_policy.run_rpc(self._stub.Shutdown.with_call, GrpcEmpty()) + + return DaprResponse(call.initial_metadata()) diff --git a/dapr/clients/grpc/interceptors.py b/dapr/clients/grpc/interceptors.py index 15bde1857..f04d9898d 100644 --- a/dapr/clients/grpc/interceptors.py +++ b/dapr/clients/grpc/interceptors.py @@ -1,127 +1,127 @@ -from collections import namedtuple -from typing import List, Tuple - -from grpc import UnaryUnaryClientInterceptor, ClientCallDetails, StreamStreamClientInterceptor # type: ignore - -from dapr.conf import settings - - -class _ClientCallDetails( - namedtuple( - '_ClientCallDetails', - ['method', 'timeout', 'metadata', 'credentials', 'wait_for_ready', 'compression'], - ), - ClientCallDetails, -): - """This is an implementation of the ClientCallDetails interface needed for interceptors. - This class takes six named values and inherits the ClientCallDetails from grpc package. - This class encloses the values that describe a RPC to be invoked. - """ - - pass - - -class DaprClientTimeoutInterceptor(UnaryUnaryClientInterceptor): - def intercept_unary_unary(self, continuation, client_call_details, request): - # If a specific timeout is not set, create a new ClientCallDetails with the default timeout - if client_call_details.timeout is None: - new_client_call_details = _ClientCallDetails( - client_call_details.method, - settings.DAPR_API_TIMEOUT_SECONDS, - client_call_details.metadata, - client_call_details.credentials, - client_call_details.wait_for_ready, - client_call_details.compression, - ) - return continuation(new_client_call_details, request) - - return continuation(client_call_details, request) - - -class DaprClientInterceptor(UnaryUnaryClientInterceptor, StreamStreamClientInterceptor): - """The class implements a UnaryUnaryClientInterceptor from grpc to add an interceptor to add - additional headers to all calls as needed. - - Examples: - - interceptor = HeaderInterceptor([('header', 'value', )]) - intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) - - With multiple header values: - - interceptor = HeaderInterceptor([('header1', 'value1', ), ('header2', 'value2', )]) - intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) - """ - - def __init__(self, metadata: List[Tuple[str, str]]): - """Initializes the metadata field for the class. - - Args: - metadata list[tuple[str, str]]: list of tuple of (key, value) strings - representing header values - """ - - self._metadata = metadata - - def _intercept_call(self, client_call_details: ClientCallDetails) -> ClientCallDetails: - """Internal intercept_call implementation which adds metadata to grpc metadata in the RPC - call details. - - Args: - client_call_details :class: `ClientCallDetails`: object that describes a RPC - to be invoked - - Returns: - :class: `ClientCallDetails` modified call details - """ - - metadata = [] - if client_call_details.metadata is not None: - metadata = list(client_call_details.metadata) - metadata.extend(self._metadata) - - new_call_details = _ClientCallDetails( - client_call_details.method, - client_call_details.timeout, - metadata, - client_call_details.credentials, - client_call_details.wait_for_ready, - client_call_details.compression, - ) - return new_call_details - - def intercept_unary_unary(self, continuation, client_call_details, request): - """This method intercepts a unary-unary gRPC call. It is the implementation of the - abstract method defined in UnaryUnaryClientInterceptor defined in grpc. It's invoked - automatically by grpc based on the order in which interceptors are added to the channel. - - Args: - continuation: a callable to be invoked to continue with the RPC or next interceptor - client_call_details: a ClientCallDetails object describing the outgoing RPC - request: the request value for the RPC - - Returns: - A response object after invoking the continuation callable - """ - new_call_details = self._intercept_call(client_call_details) - # Call continuation - response = continuation(new_call_details, request) - return response - - def intercept_stream_stream(self, continuation, client_call_details, request_iterator): - """This method intercepts a stream-stream gRPC call. It is the implementation of the - abstract method defined in StreamStreamClientInterceptor defined in grpc. It's invoked - automatically by grpc based on the order in which interceptors are added to the channel. - - Args: - continuation: a callable to be invoked to continue with the RPC or next interceptor - client_call_details: a ClientCallDetails object describing the outgoing RPC - request_iterator: the request value for the RPC - - Returns: - A response object after invoking the continuation callable - """ - new_call_details = self._intercept_call(client_call_details) - # Call continuation - response = continuation(new_call_details, request_iterator) - return response +from collections import namedtuple +from typing import List, Tuple + +from grpc import UnaryUnaryClientInterceptor, ClientCallDetails, StreamStreamClientInterceptor # type: ignore + +from dapr.conf import settings + + +class _ClientCallDetails( + namedtuple( + '_ClientCallDetails', + ['method', 'timeout', 'metadata', 'credentials', 'wait_for_ready', 'compression'], + ), + ClientCallDetails, +): + """This is an implementation of the ClientCallDetails interface needed for interceptors. + This class takes six named values and inherits the ClientCallDetails from grpc package. + This class encloses the values that describe a RPC to be invoked. + """ + + pass + + +class DaprClientTimeoutInterceptor(UnaryUnaryClientInterceptor): + def intercept_unary_unary(self, continuation, client_call_details, request): + # If a specific timeout is not set, create a new ClientCallDetails with the default timeout + if client_call_details.timeout is None: + new_client_call_details = _ClientCallDetails( + client_call_details.method, + settings.DAPR_API_TIMEOUT_SECONDS, + client_call_details.metadata, + client_call_details.credentials, + client_call_details.wait_for_ready, + client_call_details.compression, + ) + return continuation(new_client_call_details, request) + + return continuation(client_call_details, request) + + +class DaprClientInterceptor(UnaryUnaryClientInterceptor, StreamStreamClientInterceptor): + """The class implements a UnaryUnaryClientInterceptor from grpc to add an interceptor to add + additional headers to all calls as needed. + + Examples: + + interceptor = HeaderInterceptor([('header', 'value', )]) + intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) + + With multiple header values: + + interceptor = HeaderInterceptor([('header1', 'value1', ), ('header2', 'value2', )]) + intercepted_channel = grpc.intercept_channel(grpc_channel, interceptor) + """ + + def __init__(self, metadata: List[Tuple[str, str]]): + """Initializes the metadata field for the class. + + Args: + metadata list[tuple[str, str]]: list of tuple of (key, value) strings + representing header values + """ + + self._metadata = metadata + + def _intercept_call(self, client_call_details: ClientCallDetails) -> ClientCallDetails: + """Internal intercept_call implementation which adds metadata to grpc metadata in the RPC + call details. + + Args: + client_call_details :class: `ClientCallDetails`: object that describes a RPC + to be invoked + + Returns: + :class: `ClientCallDetails` modified call details + """ + + metadata = [] + if client_call_details.metadata is not None: + metadata = list(client_call_details.metadata) + metadata.extend(self._metadata) + + new_call_details = _ClientCallDetails( + client_call_details.method, + client_call_details.timeout, + metadata, + client_call_details.credentials, + client_call_details.wait_for_ready, + client_call_details.compression, + ) + return new_call_details + + def intercept_unary_unary(self, continuation, client_call_details, request): + """This method intercepts a unary-unary gRPC call. It is the implementation of the + abstract method defined in UnaryUnaryClientInterceptor defined in grpc. It's invoked + automatically by grpc based on the order in which interceptors are added to the channel. + + Args: + continuation: a callable to be invoked to continue with the RPC or next interceptor + client_call_details: a ClientCallDetails object describing the outgoing RPC + request: the request value for the RPC + + Returns: + A response object after invoking the continuation callable + """ + new_call_details = self._intercept_call(client_call_details) + # Call continuation + response = continuation(new_call_details, request) + return response + + def intercept_stream_stream(self, continuation, client_call_details, request_iterator): + """This method intercepts a stream-stream gRPC call. It is the implementation of the + abstract method defined in StreamStreamClientInterceptor defined in grpc. It's invoked + automatically by grpc based on the order in which interceptors are added to the channel. + + Args: + continuation: a callable to be invoked to continue with the RPC or next interceptor + client_call_details: a ClientCallDetails object describing the outgoing RPC + request_iterator: the request value for the RPC + + Returns: + A response object after invoking the continuation callable + """ + new_call_details = self._intercept_call(client_call_details) + # Call continuation + response = continuation(new_call_details, request_iterator) + return response diff --git a/dapr/clients/grpc/subscription.py b/dapr/clients/grpc/subscription.py index d67bed9d5..fd482de53 100644 --- a/dapr/clients/grpc/subscription.py +++ b/dapr/clients/grpc/subscription.py @@ -1,145 +1,145 @@ -from grpc import RpcError, StatusCode, Call # type: ignore - -from dapr.clients.grpc._response import TopicEventResponse -from dapr.clients.health import DaprHealth -from dapr.common.pubsub.subscription import ( - StreamInactiveError, - SubscriptionMessage, - StreamCancelledError, -) -from dapr.proto import api_v1, appcallback_v1 -import queue -import threading -from typing import Optional - - -class Subscription: - def __init__(self, stub, pubsub_name, topic, metadata=None, dead_letter_topic=None): - self._stub = stub - self._pubsub_name = pubsub_name - self._topic = topic - self._metadata = metadata or {} - self._dead_letter_topic = dead_letter_topic or '' - self._stream: Optional[Call] = None - self._response_thread: Optional[threading.Thread] = None - self._send_queue: queue.Queue = queue.Queue() - self._stream_active: bool = False - self._stream_lock = threading.Lock() # Protects _stream_active - - def start(self): - def outgoing_request_iterator(): - """ - Generator function to create the request iterator for the stream. - This sends the initial request to establish the stream. - """ - try: - # Send InitialRequest needed to establish the stream - initial_request = api_v1.SubscribeTopicEventsRequestAlpha1( - initial_request=api_v1.SubscribeTopicEventsRequestInitialAlpha1( - pubsub_name=self._pubsub_name, - topic=self._topic, - metadata=self._metadata or {}, - dead_letter_topic=self._dead_letter_topic or '', - ) - ) - yield initial_request - - # Start sending back acknowledgement messages from the send queue - while self._is_stream_active(): - try: - # Wait for responses/acknowledgements to send from the send queue. - response = self._send_queue.get() - yield response - except queue.Empty: - continue - except Exception as e: - raise Exception(f'Error while writing to stream: {e}') - - # Create the bidirectional stream - self._stream = self._stub.SubscribeTopicEventsAlpha1(outgoing_request_iterator()) - self._set_stream_active() - try: - next(self._stream) # discard the initial message - except Exception as e: - raise Exception(f'Error while initializing stream: {e}') - - def reconnect_stream(self): - self.close() - DaprHealth.wait_until_ready() - print('Attempting to reconnect...') - self.start() - - def next_message(self): - """ - Get the next message from the receive queue. - @return: The next message from the queue, - or None if no message is received within the timeout. - """ - if not self._is_stream_active() or self._stream is None: - raise StreamInactiveError('Stream is not active') - - try: - # Read the next message from the stream directly - message = next(self._stream) - return SubscriptionMessage(message.event_message) - except RpcError as e: - # If Dapr can't be reached, wait until it's ready and reconnect the stream - if e.code() == StatusCode.UNAVAILABLE: - print( - f'gRPC error while reading from stream: {e.details()}, Status Code: {e.code()}' - ) - self.reconnect_stream() - elif e.code() == StatusCode.CANCELLED: - raise StreamCancelledError('Stream has been cancelled') - else: - raise Exception( - f'gRPC error while reading from subscription stream: {e.details()} ' - f'Status Code: {e.code()}' - ) - except Exception as e: - raise Exception(f'Error while fetching message: {e}') - - def respond(self, message, status): - try: - status = appcallback_v1.TopicEventResponse(status=status.value) - response = api_v1.SubscribeTopicEventsRequestProcessedAlpha1( - id=message.id(), status=status - ) - msg = api_v1.SubscribeTopicEventsRequestAlpha1(event_processed=response) - if not self._is_stream_active(): - raise StreamInactiveError('Stream is not active') - self._send_queue.put(msg) - except Exception as e: - print(f"Can't send message on inactive stream: {e}") - - def respond_success(self, message): - self.respond(message, TopicEventResponse('success').status) - - def respond_retry(self, message): - self.respond(message, TopicEventResponse('retry').status) - - def respond_drop(self, message): - self.respond(message, TopicEventResponse('drop').status) - - def _set_stream_active(self): - with self._stream_lock: - self._stream_active = True - - def _set_stream_inactive(self): - with self._stream_lock: - self._stream_active = False - - def _is_stream_active(self): - with self._stream_lock: - return self._stream_active - - def close(self): - if self._stream: - try: - self._stream.cancel() - self._set_stream_inactive() - except RpcError as e: - if e.code() != StatusCode.CANCELLED: - raise Exception(f'Error while closing stream: {e}') - except Exception as e: - raise Exception(f'Error while closing stream: {e}') +from grpc import RpcError, StatusCode, Call # type: ignore + +from dapr.clients.grpc._response import TopicEventResponse +from dapr.clients.health import DaprHealth +from dapr.common.pubsub.subscription import ( + StreamInactiveError, + SubscriptionMessage, + StreamCancelledError, +) +from dapr.proto import api_v1, appcallback_v1 +import queue +import threading +from typing import Optional + + +class Subscription: + def __init__(self, stub, pubsub_name, topic, metadata=None, dead_letter_topic=None): + self._stub = stub + self._pubsub_name = pubsub_name + self._topic = topic + self._metadata = metadata or {} + self._dead_letter_topic = dead_letter_topic or '' + self._stream: Optional[Call] = None + self._response_thread: Optional[threading.Thread] = None + self._send_queue: queue.Queue = queue.Queue() + self._stream_active: bool = False + self._stream_lock = threading.Lock() # Protects _stream_active + + def start(self): + def outgoing_request_iterator(): + """ + Generator function to create the request iterator for the stream. + This sends the initial request to establish the stream. + """ + try: + # Send InitialRequest needed to establish the stream + initial_request = api_v1.SubscribeTopicEventsRequestAlpha1( + initial_request=api_v1.SubscribeTopicEventsRequestInitialAlpha1( + pubsub_name=self._pubsub_name, + topic=self._topic, + metadata=self._metadata or {}, + dead_letter_topic=self._dead_letter_topic or '', + ) + ) + yield initial_request + + # Start sending back acknowledgement messages from the send queue + while self._is_stream_active(): + try: + # Wait for responses/acknowledgements to send from the send queue. + response = self._send_queue.get() + yield response + except queue.Empty: + continue + except Exception as e: + raise Exception(f'Error while writing to stream: {e}') + + # Create the bidirectional stream + self._stream = self._stub.SubscribeTopicEventsAlpha1(outgoing_request_iterator()) + self._set_stream_active() + try: + next(self._stream) # discard the initial message + except Exception as e: + raise Exception(f'Error while initializing stream: {e}') + + def reconnect_stream(self): + self.close() + DaprHealth.wait_until_ready() + print('Attempting to reconnect...') + self.start() + + def next_message(self): + """ + Get the next message from the receive queue. + @return: The next message from the queue, + or None if no message is received within the timeout. + """ + if not self._is_stream_active() or self._stream is None: + raise StreamInactiveError('Stream is not active') + + try: + # Read the next message from the stream directly + message = next(self._stream) + return SubscriptionMessage(message.event_message) + except RpcError as e: + # If Dapr can't be reached, wait until it's ready and reconnect the stream + if e.code() == StatusCode.UNAVAILABLE: + print( + f'gRPC error while reading from stream: {e.details()}, Status Code: {e.code()}' + ) + self.reconnect_stream() + elif e.code() == StatusCode.CANCELLED: + raise StreamCancelledError('Stream has been cancelled') + else: + raise Exception( + f'gRPC error while reading from subscription stream: {e.details()} ' + f'Status Code: {e.code()}' + ) + except Exception as e: + raise Exception(f'Error while fetching message: {e}') + + def respond(self, message, status): + try: + status = appcallback_v1.TopicEventResponse(status=status.value) + response = api_v1.SubscribeTopicEventsRequestProcessedAlpha1( + id=message.id(), status=status + ) + msg = api_v1.SubscribeTopicEventsRequestAlpha1(event_processed=response) + if not self._is_stream_active(): + raise StreamInactiveError('Stream is not active') + self._send_queue.put(msg) + except Exception as e: + print(f"Can't send message on inactive stream: {e}") + + def respond_success(self, message): + self.respond(message, TopicEventResponse('success').status) + + def respond_retry(self, message): + self.respond(message, TopicEventResponse('retry').status) + + def respond_drop(self, message): + self.respond(message, TopicEventResponse('drop').status) + + def _set_stream_active(self): + with self._stream_lock: + self._stream_active = True + + def _set_stream_inactive(self): + with self._stream_lock: + self._stream_active = False + + def _is_stream_active(self): + with self._stream_lock: + return self._stream_active + + def close(self): + if self._stream: + try: + self._stream.cancel() + self._set_stream_inactive() + except RpcError as e: + if e.code() != StatusCode.CANCELLED: + raise Exception(f'Error while closing stream: {e}') + except Exception as e: + raise Exception(f'Error while closing stream: {e}') diff --git a/dapr/clients/health.py b/dapr/clients/health.py index e3daec79d..3e716858f 100644 --- a/dapr/clients/health.py +++ b/dapr/clients/health.py @@ -1,54 +1,54 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2024 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import urllib.request -import urllib.error -import time - -from dapr.clients.http.conf import DAPR_API_TOKEN_HEADER, USER_AGENT_HEADER, DAPR_USER_AGENT -from dapr.clients.http.helpers import get_api_url -from dapr.conf import settings - - -class DaprHealth: - @staticmethod - def wait_until_ready(): - health_url = f'{get_api_url()}/healthz/outbound' - headers = {USER_AGENT_HEADER: DAPR_USER_AGENT} - if settings.DAPR_API_TOKEN is not None: - headers[DAPR_API_TOKEN_HEADER] = settings.DAPR_API_TOKEN - timeout = float(settings.DAPR_HEALTH_TIMEOUT) - - start = time.time() - while True: - try: - req = urllib.request.Request(health_url, headers=headers) - with urllib.request.urlopen(req, context=DaprHealth.get_ssl_context()) as response: - if 200 <= response.status < 300: - break - except urllib.error.URLError as e: - print(f'Health check on {health_url} failed: {e.reason}') - except Exception as e: - print(f'Unexpected error during health check: {e}') - - remaining = (start + timeout) - time.time() - if remaining <= 0: - raise TimeoutError(f'Dapr health check timed out, after {timeout}.') - time.sleep(min(1, remaining)) - - @staticmethod - def get_ssl_context(): - # This method is used (overwritten) from tests - # to return context for self-signed certificates - return None +# -*- coding: utf-8 -*- + +""" +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import urllib.request +import urllib.error +import time + +from dapr.clients.http.conf import DAPR_API_TOKEN_HEADER, USER_AGENT_HEADER, DAPR_USER_AGENT +from dapr.clients.http.helpers import get_api_url +from dapr.conf import settings + + +class DaprHealth: + @staticmethod + def wait_until_ready(): + health_url = f'{get_api_url()}/healthz/outbound' + headers = {USER_AGENT_HEADER: DAPR_USER_AGENT} + if settings.DAPR_API_TOKEN is not None: + headers[DAPR_API_TOKEN_HEADER] = settings.DAPR_API_TOKEN + timeout = float(settings.DAPR_HEALTH_TIMEOUT) + + start = time.time() + while True: + try: + req = urllib.request.Request(health_url, headers=headers) + with urllib.request.urlopen(req, context=DaprHealth.get_ssl_context()) as response: + if 200 <= response.status < 300: + break + except urllib.error.URLError as e: + print(f'Health check on {health_url} failed: {e.reason}') + except Exception as e: + print(f'Unexpected error during health check: {e}') + + remaining = (start + timeout) - time.time() + if remaining <= 0: + raise TimeoutError(f'Dapr health check timed out, after {timeout}.') + time.sleep(min(1, remaining)) + + @staticmethod + def get_ssl_context(): + # This method is used (overwritten) from tests + # to return context for self-signed certificates + return None diff --git a/dapr/clients/http/__init__.py b/dapr/clients/http/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/clients/http/__init__.py +++ b/dapr/clients/http/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/clients/http/client.py b/dapr/clients/http/client.py index 6f2a8e3d9..d763d9b08 100644 --- a/dapr/clients/http/client.py +++ b/dapr/clients/http/client.py @@ -1,130 +1,130 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import aiohttp - -from typing import Callable, Mapping, Dict, Optional, Union, Tuple, TYPE_CHECKING - -from dapr.clients.health import DaprHealth -from dapr.clients.http.conf import ( - DAPR_API_TOKEN_HEADER, - USER_AGENT_HEADER, - DAPR_USER_AGENT, - CONTENT_TYPE_HEADER, -) -from dapr.clients.retry import RetryPolicy - -if TYPE_CHECKING: - from dapr.serializers import Serializer - -from dapr.conf import settings -from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE -from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_DOES_NOT_EXIST, ERROR_CODE_UNKNOWN - - -class DaprHttpClient: - """A Dapr Http API client""" - - def __init__( - self, - message_serializer: 'Serializer', - timeout: Optional[int] = 60, - headers_callback: Optional[Callable[[], Dict[str, str]]] = None, - retry_policy: Optional[RetryPolicy] = None, - ): - """Invokes Dapr over HTTP. - - Args: - message_serializer (Serializer): Dapr serializer. - timeout (int, optional): Timeout in seconds, defaults to 60. - headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. - """ - DaprHealth.wait_until_ready() - - self._timeout = aiohttp.ClientTimeout(total=timeout) - self._serializer = message_serializer - self._headers_callback = headers_callback - self.retry_policy = retry_policy or RetryPolicy() - - async def send_bytes( - self, - method: str, - url: str, - data: Optional[bytes], - headers: Dict[str, Union[bytes, str]] = {}, - query_params: Optional[Mapping] = None, - timeout: Optional[int] = None, - ) -> Tuple[bytes, aiohttp.ClientResponse]: - headers_map = headers - if not headers_map.get(CONTENT_TYPE_HEADER): - headers_map[CONTENT_TYPE_HEADER] = DEFAULT_JSON_CONTENT_TYPE - - if settings.DAPR_API_TOKEN is not None: - headers_map[DAPR_API_TOKEN_HEADER] = settings.DAPR_API_TOKEN - - if self._headers_callback is not None: - trace_headers = self._headers_callback() - headers_map.update(trace_headers) - - headers_map[USER_AGENT_HEADER] = DAPR_USER_AGENT - - r = None - client_timeout = aiohttp.ClientTimeout(total=timeout) if timeout else self._timeout - sslcontext = self.get_ssl_context() - - async with aiohttp.ClientSession() as session: - req = { - 'method': method, - 'url': url, - 'data': data, - 'headers': headers_map, - 'sslcontext': sslcontext, - 'params': query_params, - 'timeout': client_timeout, - } - r = await self.retry_policy.make_http_call(session, req) - - if 200 <= r.status < 300: - return await r.read(), r - - raise (await self.convert_to_error(r)) - - async def convert_to_error(self, response: aiohttp.ClientResponse) -> DaprInternalError: - error_info = None - try: - error_body = await response.read() - if (error_body is None or len(error_body) == 0) and response.status == 404: - return DaprInternalError('Not Found', ERROR_CODE_DOES_NOT_EXIST) - error_info = self._serializer.deserialize(error_body) - except Exception: - return DaprInternalError( - f'Unknown Dapr Error. HTTP status code: {response.status}', - raw_response_bytes=error_body, - ) - - if error_info and isinstance(error_info, dict): - message = error_info.get('message') - error_code = error_info.get('errorCode') or ERROR_CODE_UNKNOWN - return DaprInternalError(message, error_code, raw_response_bytes=error_body) - - return DaprInternalError( - f'Unknown Dapr Error. HTTP status code: {response.status}', - raw_response_bytes=error_body, - ) - - def get_ssl_context(self): - # This method is used (overwritten) from tests - # to return context for self-signed certificates - return False +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import aiohttp + +from typing import Callable, Mapping, Dict, Optional, Union, Tuple, TYPE_CHECKING + +from dapr.clients.health import DaprHealth +from dapr.clients.http.conf import ( + DAPR_API_TOKEN_HEADER, + USER_AGENT_HEADER, + DAPR_USER_AGENT, + CONTENT_TYPE_HEADER, +) +from dapr.clients.retry import RetryPolicy + +if TYPE_CHECKING: + from dapr.serializers import Serializer + +from dapr.conf import settings +from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE +from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_DOES_NOT_EXIST, ERROR_CODE_UNKNOWN + + +class DaprHttpClient: + """A Dapr Http API client""" + + def __init__( + self, + message_serializer: 'Serializer', + timeout: Optional[int] = 60, + headers_callback: Optional[Callable[[], Dict[str, str]]] = None, + retry_policy: Optional[RetryPolicy] = None, + ): + """Invokes Dapr over HTTP. + + Args: + message_serializer (Serializer): Dapr serializer. + timeout (int, optional): Timeout in seconds, defaults to 60. + headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. + """ + DaprHealth.wait_until_ready() + + self._timeout = aiohttp.ClientTimeout(total=timeout) + self._serializer = message_serializer + self._headers_callback = headers_callback + self.retry_policy = retry_policy or RetryPolicy() + + async def send_bytes( + self, + method: str, + url: str, + data: Optional[bytes], + headers: Dict[str, Union[bytes, str]] = {}, + query_params: Optional[Mapping] = None, + timeout: Optional[int] = None, + ) -> Tuple[bytes, aiohttp.ClientResponse]: + headers_map = headers + if not headers_map.get(CONTENT_TYPE_HEADER): + headers_map[CONTENT_TYPE_HEADER] = DEFAULT_JSON_CONTENT_TYPE + + if settings.DAPR_API_TOKEN is not None: + headers_map[DAPR_API_TOKEN_HEADER] = settings.DAPR_API_TOKEN + + if self._headers_callback is not None: + trace_headers = self._headers_callback() + headers_map.update(trace_headers) + + headers_map[USER_AGENT_HEADER] = DAPR_USER_AGENT + + r = None + client_timeout = aiohttp.ClientTimeout(total=timeout) if timeout else self._timeout + sslcontext = self.get_ssl_context() + + async with aiohttp.ClientSession() as session: + req = { + 'method': method, + 'url': url, + 'data': data, + 'headers': headers_map, + 'sslcontext': sslcontext, + 'params': query_params, + 'timeout': client_timeout, + } + r = await self.retry_policy.make_http_call(session, req) + + if 200 <= r.status < 300: + return await r.read(), r + + raise (await self.convert_to_error(r)) + + async def convert_to_error(self, response: aiohttp.ClientResponse) -> DaprInternalError: + error_info = None + try: + error_body = await response.read() + if (error_body is None or len(error_body) == 0) and response.status == 404: + return DaprInternalError('Not Found', ERROR_CODE_DOES_NOT_EXIST) + error_info = self._serializer.deserialize(error_body) + except Exception: + return DaprInternalError( + f'Unknown Dapr Error. HTTP status code: {response.status}', + raw_response_bytes=error_body, + ) + + if error_info and isinstance(error_info, dict): + message = error_info.get('message') + error_code = error_info.get('errorCode') or ERROR_CODE_UNKNOWN + return DaprInternalError(message, error_code, raw_response_bytes=error_body) + + return DaprInternalError( + f'Unknown Dapr Error. HTTP status code: {response.status}', + raw_response_bytes=error_body, + ) + + def get_ssl_context(self): + # This method is used (overwritten) from tests + # to return context for self-signed certificates + return False diff --git a/dapr/clients/http/conf.py b/dapr/clients/http/conf.py index 2dce2834e..9c7866cb1 100644 --- a/dapr/clients/http/conf.py +++ b/dapr/clients/http/conf.py @@ -1,21 +1,21 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.version import __version__ - -CONTENT_TYPE_HEADER = 'content-type' -DAPR_API_TOKEN_HEADER = 'dapr-api-token' -USER_AGENT_HEADER = 'User-Agent' -DAPR_USER_AGENT = f'dapr-sdk-python/{__version__}' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.version import __version__ + +CONTENT_TYPE_HEADER = 'content-type' +DAPR_API_TOKEN_HEADER = 'dapr-api-token' +USER_AGENT_HEADER = 'User-Agent' +DAPR_USER_AGENT = f'dapr-sdk-python/{__version__}' diff --git a/dapr/clients/http/dapr_actor_http_client.py b/dapr/clients/http/dapr_actor_http_client.py index 186fdbc1c..331367391 100644 --- a/dapr/clients/http/dapr_actor_http_client.py +++ b/dapr/clients/http/dapr_actor_http_client.py @@ -1,153 +1,153 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Callable, Dict, Optional, Union, TYPE_CHECKING - -from dapr.clients.http.helpers import get_api_url - -if TYPE_CHECKING: - from dapr.serializers import Serializer - -from dapr.clients.http.client import DaprHttpClient -from dapr.clients.base import DaprActorClientBase -from dapr.clients.retry import RetryPolicy - -DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id' - - -class DaprActorHttpClient(DaprActorClientBase): - """A Dapr Actor http client implementing :class:`DaprActorClientBase`""" - - def __init__( - self, - message_serializer: 'Serializer', - timeout: int = 60, - headers_callback: Optional[Callable[[], Dict[str, str]]] = None, - retry_policy: Optional[RetryPolicy] = None, - ): - """Invokes Dapr Actors over HTTP. - - Args: - message_serializer (Serializer): Dapr serializer. - timeout (int, optional): Timeout in seconds, defaults to 60. - headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. - retry_policy (RetryPolicy optional): Specifies retry behaviour - """ - self._client = DaprHttpClient(message_serializer, timeout, headers_callback, retry_policy) - - async def invoke_method( - self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None - ) -> bytes: - """Invoke method defined in :class:`Actor` remotely. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - method (str): Method name defined in :class:`Actor`. - bytes data (bytes): data which will be passed to the target actor. - - Returns: - bytes: the response from the actor. - """ - url = f'{self._get_base_url(actor_type, actor_id)}/method/{method}' - - # import to avoid circular dependency - from dapr.actor.runtime.reentrancy_context import reentrancy_ctx - - reentrancy_id = reentrancy_ctx.get() - headers: Dict[str, Union[bytes, str]] = ( - {DAPR_REENTRANCY_ID_HEADER: reentrancy_id} if reentrancy_id else {} - ) - - body, _ = await self._client.send_bytes(method='POST', url=url, data=data, headers=headers) - - return body - - async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: - """Save state transactionally. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - data (bytes): Json-serialized the transactional state operations. - """ - url = f'{self._get_base_url(actor_type, actor_id)}/state' - await self._client.send_bytes(method='PUT', url=url, data=data) - - async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: - """Get state value for name key. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - name (str): The name of state. - - Returns: - bytes: the value of the state. - """ - url = f'{self._get_base_url(actor_type, actor_id)}/state/{name}' - body, _ = await self._client.send_bytes(method='GET', url=url, data=None) - return body - - async def register_reminder( - self, actor_type: str, actor_id: str, name: str, data: bytes - ) -> None: - """Register actor reminder. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - name (str): The name of reminder - data (bytes): Reminder request json body. - """ - url = f'{self._get_base_url(actor_type, actor_id)}/reminders/{name}' - await self._client.send_bytes(method='PUT', url=url, data=data) - - async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: - """Unregister actor reminder. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - name (str): the name of reminder. - """ - url = f'{self._get_base_url(actor_type, actor_id)}/reminders/{name}' - await self._client.send_bytes(method='DELETE', url=url, data=None) - - async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: - """Register actor timer. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - name (str): The name of reminder. - data (bytes): Timer request json body. - """ - url = f'{self._get_base_url(actor_type, actor_id)}/timers/{name}' - await self._client.send_bytes(method='PUT', url=url, data=data) - - async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: - """Unregister actor timer. - - Args: - actor_type (str): Actor type. - actor_id (str): Id of Actor type. - name (str): The name of timer - """ - url = f'{self._get_base_url(actor_type, actor_id)}/timers/{name}' - await self._client.send_bytes(method='DELETE', url=url, data=None) - - def _get_base_url(self, actor_type: str, actor_id: str) -> str: - return '{}/actors/{}/{}'.format(get_api_url(), actor_type, actor_id) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Callable, Dict, Optional, Union, TYPE_CHECKING + +from dapr.clients.http.helpers import get_api_url + +if TYPE_CHECKING: + from dapr.serializers import Serializer + +from dapr.clients.http.client import DaprHttpClient +from dapr.clients.base import DaprActorClientBase +from dapr.clients.retry import RetryPolicy + +DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id' + + +class DaprActorHttpClient(DaprActorClientBase): + """A Dapr Actor http client implementing :class:`DaprActorClientBase`""" + + def __init__( + self, + message_serializer: 'Serializer', + timeout: int = 60, + headers_callback: Optional[Callable[[], Dict[str, str]]] = None, + retry_policy: Optional[RetryPolicy] = None, + ): + """Invokes Dapr Actors over HTTP. + + Args: + message_serializer (Serializer): Dapr serializer. + timeout (int, optional): Timeout in seconds, defaults to 60. + headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. + retry_policy (RetryPolicy optional): Specifies retry behaviour + """ + self._client = DaprHttpClient(message_serializer, timeout, headers_callback, retry_policy) + + async def invoke_method( + self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None + ) -> bytes: + """Invoke method defined in :class:`Actor` remotely. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + method (str): Method name defined in :class:`Actor`. + bytes data (bytes): data which will be passed to the target actor. + + Returns: + bytes: the response from the actor. + """ + url = f'{self._get_base_url(actor_type, actor_id)}/method/{method}' + + # import to avoid circular dependency + from dapr.actor.runtime.reentrancy_context import reentrancy_ctx + + reentrancy_id = reentrancy_ctx.get() + headers: Dict[str, Union[bytes, str]] = ( + {DAPR_REENTRANCY_ID_HEADER: reentrancy_id} if reentrancy_id else {} + ) + + body, _ = await self._client.send_bytes(method='POST', url=url, data=data, headers=headers) + + return body + + async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: + """Save state transactionally. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + data (bytes): Json-serialized the transactional state operations. + """ + url = f'{self._get_base_url(actor_type, actor_id)}/state' + await self._client.send_bytes(method='PUT', url=url, data=data) + + async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: + """Get state value for name key. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + name (str): The name of state. + + Returns: + bytes: the value of the state. + """ + url = f'{self._get_base_url(actor_type, actor_id)}/state/{name}' + body, _ = await self._client.send_bytes(method='GET', url=url, data=None) + return body + + async def register_reminder( + self, actor_type: str, actor_id: str, name: str, data: bytes + ) -> None: + """Register actor reminder. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + name (str): The name of reminder + data (bytes): Reminder request json body. + """ + url = f'{self._get_base_url(actor_type, actor_id)}/reminders/{name}' + await self._client.send_bytes(method='PUT', url=url, data=data) + + async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: + """Unregister actor reminder. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + name (str): the name of reminder. + """ + url = f'{self._get_base_url(actor_type, actor_id)}/reminders/{name}' + await self._client.send_bytes(method='DELETE', url=url, data=None) + + async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: + """Register actor timer. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + name (str): The name of reminder. + data (bytes): Timer request json body. + """ + url = f'{self._get_base_url(actor_type, actor_id)}/timers/{name}' + await self._client.send_bytes(method='PUT', url=url, data=data) + + async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: + """Unregister actor timer. + + Args: + actor_type (str): Actor type. + actor_id (str): Id of Actor type. + name (str): The name of timer + """ + url = f'{self._get_base_url(actor_type, actor_id)}/timers/{name}' + await self._client.send_bytes(method='DELETE', url=url, data=None) + + def _get_base_url(self, actor_type: str, actor_id: str) -> str: + return '{}/actors/{}/{}'.format(get_api_url(), actor_type, actor_id) diff --git a/dapr/clients/http/dapr_invocation_http_client.py b/dapr/clients/http/dapr_invocation_http_client.py index df4e6d222..825f0b4c6 100644 --- a/dapr/clients/http/dapr_invocation_http_client.py +++ b/dapr/clients/http/dapr_invocation_http_client.py @@ -1,167 +1,167 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import asyncio - -from typing import Callable, Dict, Optional, Union -from multidict import MultiDict - -from dapr.clients.http.client import DaprHttpClient -from dapr.clients.grpc._helpers import MetadataTuple, GrpcMessage -from dapr.clients.grpc._response import InvokeMethodResponse -from dapr.clients.http.conf import CONTENT_TYPE_HEADER -from dapr.clients.http.helpers import get_api_url -from dapr.clients.retry import RetryPolicy -from dapr.serializers import DefaultJSONSerializer -from dapr.version import __version__ - -USER_AGENT_HEADER = 'User-Agent' -DAPR_USER_AGENT = f'dapr-python-sdk/{__version__}' - - -class DaprInvocationHttpClient: - """Service Invocation HTTP Client""" - - def __init__( - self, - timeout: int = 60, - headers_callback: Optional[Callable[[], Dict[str, str]]] = None, - retry_policy: Optional[RetryPolicy] = None, - ): - """Invokes Dapr's API for method invocation over HTTP. - - Args: - timeout (int, optional): Timeout in seconds, defaults to 60. - headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. - retry_policy (RetryPolicy optional): Specifies retry behaviour - """ - self._client = DaprHttpClient( - DefaultJSONSerializer(), timeout, headers_callback, retry_policy=retry_policy - ) - - async def invoke_method_async( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage], - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invoke a service method over HTTP (async). - - Args: - app_id (str): Application Id. - method_name (str): Method to be invoked. - data (bytes or str or GrpcMessage, optional): Data for requet's body. - content_type (str, optional): Content type header. - metadata (MetadataTuple, optional): Additional headers. - http_verb (str, optional): HTTP verb for the request. - http_querystring (MetadataTuple, optional): Query parameters. - timeout (int, optional): request timeout in seconds. - - Returns: - InvokeMethodResponse: the response from the method invocation. - """ - - verb = 'GET' - if http_verb is not None: - verb = http_verb - - headers = {} - - if metadata is not None: - for key, value in metadata: - headers[key] = value - query_params: MultiDict = MultiDict() - if http_querystring is not None: - for key, value in http_querystring: - query_params.add(key, value) - - if content_type is not None: - headers[CONTENT_TYPE_HEADER] = content_type - - headers[USER_AGENT_HEADER] = DAPR_USER_AGENT - - url = f'{get_api_url()}/invoke/{app_id}/method/{method_name}' - - if isinstance(data, GrpcMessage): - body = data.SerializeToString() - elif isinstance(data, str): - body = data.encode('utf-8') - else: - body = data - - async def make_request() -> InvokeMethodResponse: - resp_body, r = await self._client.send_bytes( - method=verb, - headers=headers, - url=url, - data=body, - query_params=query_params, - timeout=timeout, - ) - - respHeaders: MetadataTuple = tuple(r.headers.items()) - - resp_data = InvokeMethodResponse( - data=resp_body, - content_type=r.content_type, - headers=respHeaders, - status_code=r.status, - ) - return resp_data - - return await make_request() - - def invoke_method( - self, - app_id: str, - method_name: str, - data: Union[bytes, str, GrpcMessage], - content_type: Optional[str] = None, - metadata: Optional[MetadataTuple] = None, - http_verb: Optional[str] = None, - http_querystring: Optional[MetadataTuple] = None, - timeout: Optional[int] = None, - ) -> InvokeMethodResponse: - """Invoke a service method over HTTP (async). - - Args: - app_id (str): Application Id. - method_name (str): Method to be invoked. - data (bytes or str or GrpcMessage, optional): Data for request's body. - content_type (str, optional): Content type header. - metadata (MetadataTuple, optional): Additional headers. - http_verb (str, optional): HTTP verb for the request. - http_querystring (MetadataTuple, optional): Query parameters. - timeout (int, optional): request timeout in seconds. - - Returns: - InvokeMethodResponse: the response from the method invocation. - """ - - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - awaitable = self.invoke_method_async( - app_id, method_name, data, content_type, metadata, http_verb, http_querystring, timeout - ) - return loop.run_until_complete(awaitable) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import asyncio + +from typing import Callable, Dict, Optional, Union +from multidict import MultiDict + +from dapr.clients.http.client import DaprHttpClient +from dapr.clients.grpc._helpers import MetadataTuple, GrpcMessage +from dapr.clients.grpc._response import InvokeMethodResponse +from dapr.clients.http.conf import CONTENT_TYPE_HEADER +from dapr.clients.http.helpers import get_api_url +from dapr.clients.retry import RetryPolicy +from dapr.serializers import DefaultJSONSerializer +from dapr.version import __version__ + +USER_AGENT_HEADER = 'User-Agent' +DAPR_USER_AGENT = f'dapr-python-sdk/{__version__}' + + +class DaprInvocationHttpClient: + """Service Invocation HTTP Client""" + + def __init__( + self, + timeout: int = 60, + headers_callback: Optional[Callable[[], Dict[str, str]]] = None, + retry_policy: Optional[RetryPolicy] = None, + ): + """Invokes Dapr's API for method invocation over HTTP. + + Args: + timeout (int, optional): Timeout in seconds, defaults to 60. + headers_callback (lambda: Dict[str, str]], optional): Generates header for each request. + retry_policy (RetryPolicy optional): Specifies retry behaviour + """ + self._client = DaprHttpClient( + DefaultJSONSerializer(), timeout, headers_callback, retry_policy=retry_policy + ) + + async def invoke_method_async( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage], + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invoke a service method over HTTP (async). + + Args: + app_id (str): Application Id. + method_name (str): Method to be invoked. + data (bytes or str or GrpcMessage, optional): Data for requet's body. + content_type (str, optional): Content type header. + metadata (MetadataTuple, optional): Additional headers. + http_verb (str, optional): HTTP verb for the request. + http_querystring (MetadataTuple, optional): Query parameters. + timeout (int, optional): request timeout in seconds. + + Returns: + InvokeMethodResponse: the response from the method invocation. + """ + + verb = 'GET' + if http_verb is not None: + verb = http_verb + + headers = {} + + if metadata is not None: + for key, value in metadata: + headers[key] = value + query_params: MultiDict = MultiDict() + if http_querystring is not None: + for key, value in http_querystring: + query_params.add(key, value) + + if content_type is not None: + headers[CONTENT_TYPE_HEADER] = content_type + + headers[USER_AGENT_HEADER] = DAPR_USER_AGENT + + url = f'{get_api_url()}/invoke/{app_id}/method/{method_name}' + + if isinstance(data, GrpcMessage): + body = data.SerializeToString() + elif isinstance(data, str): + body = data.encode('utf-8') + else: + body = data + + async def make_request() -> InvokeMethodResponse: + resp_body, r = await self._client.send_bytes( + method=verb, + headers=headers, + url=url, + data=body, + query_params=query_params, + timeout=timeout, + ) + + respHeaders: MetadataTuple = tuple(r.headers.items()) + + resp_data = InvokeMethodResponse( + data=resp_body, + content_type=r.content_type, + headers=respHeaders, + status_code=r.status, + ) + return resp_data + + return await make_request() + + def invoke_method( + self, + app_id: str, + method_name: str, + data: Union[bytes, str, GrpcMessage], + content_type: Optional[str] = None, + metadata: Optional[MetadataTuple] = None, + http_verb: Optional[str] = None, + http_querystring: Optional[MetadataTuple] = None, + timeout: Optional[int] = None, + ) -> InvokeMethodResponse: + """Invoke a service method over HTTP (async). + + Args: + app_id (str): Application Id. + method_name (str): Method to be invoked. + data (bytes or str or GrpcMessage, optional): Data for request's body. + content_type (str, optional): Content type header. + metadata (MetadataTuple, optional): Additional headers. + http_verb (str, optional): HTTP verb for the request. + http_querystring (MetadataTuple, optional): Query parameters. + timeout (int, optional): request timeout in seconds. + + Returns: + InvokeMethodResponse: the response from the method invocation. + """ + + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + awaitable = self.invoke_method_async( + app_id, method_name, data, content_type, metadata, http_verb, http_querystring, timeout + ) + return loop.run_until_complete(awaitable) diff --git a/dapr/clients/http/helpers.py b/dapr/clients/http/helpers.py index 00d7a250d..6c0be438a 100644 --- a/dapr/clients/http/helpers.py +++ b/dapr/clients/http/helpers.py @@ -1,25 +1,25 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.conf import settings - - -def get_api_url() -> str: - if settings.DAPR_HTTP_ENDPOINT: - return '{}/{}'.format(settings.DAPR_HTTP_ENDPOINT, settings.DAPR_API_VERSION) - - return 'http://{}:{}/{}'.format( - settings.DAPR_RUNTIME_HOST, settings.DAPR_HTTP_PORT, settings.DAPR_API_VERSION - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.conf import settings + + +def get_api_url() -> str: + if settings.DAPR_HTTP_ENDPOINT: + return '{}/{}'.format(settings.DAPR_HTTP_ENDPOINT, settings.DAPR_API_VERSION) + + return 'http://{}:{}/{}'.format( + settings.DAPR_RUNTIME_HOST, settings.DAPR_HTTP_PORT, settings.DAPR_API_VERSION + ) diff --git a/dapr/clients/retry.py b/dapr/clients/retry.py index 171c96fbd..2b15a6bfa 100644 --- a/dapr/clients/retry.py +++ b/dapr/clients/retry.py @@ -1,165 +1,165 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2024 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import asyncio -from typing import Optional, List, Callable - -from grpc import RpcError, StatusCode # type: ignore -import time - -from dapr.conf import settings - - -class RetryPolicy: - """RetryPolicy holds the retry policy configuration for a gRPC client. - - Args: - max_attempts (int): The maximum number of retry attempts. - initial_backoff (int): The initial backoff duration. - max_backoff (int): The maximum backoff duration. - backoff_multiplier (float): The backoff multiplier. - retryable_http_status_codes (List[int]): The list of http retryable status codes - retryable_grpc_status_codes (List[StatusCode]): The list of retryable grpc status codes - """ - - def __init__( - self, - max_attempts: Optional[int] = settings.DAPR_API_MAX_RETRIES, - initial_backoff: int = 1, - max_backoff: int = 20, - backoff_multiplier: float = 1.5, - retryable_http_status_codes: List[int] = [408, 429, 500, 502, 503, 504], - retryable_grpc_status_codes: List[StatusCode] = [ - StatusCode.UNAVAILABLE, - StatusCode.DEADLINE_EXCEEDED, - ], - ): - if max_attempts < -1: # type: ignore - raise ValueError('max_attempts must be greater than or equal to -1') - self.max_attempts = max_attempts - - if initial_backoff < 1: - raise ValueError('initial_backoff must be greater than or equal to 1') - self.initial_backoff = initial_backoff - - if max_backoff < 1: - raise ValueError('max_backoff must be greater than or equal to 1') - self.max_backoff = max_backoff - - if backoff_multiplier < 1: - raise ValueError('backoff_multiplier must be greater than or equal to 1') - self.backoff_multiplier = backoff_multiplier - - if len(retryable_http_status_codes) == 0: - raise ValueError("retryable_http_status_codes can't be empty") - self.retryable_http_status_codes = retryable_http_status_codes - - if len(retryable_grpc_status_codes) == 0: - raise ValueError("retryable_http_status_codes can't be empty") - self.retryable_grpc_status_codes = retryable_grpc_status_codes - - def run_rpc(self, func=Callable, *args, **kwargs): - # If max_retries is 0, we don't retry - if self.max_attempts == 0: - return func(*args, **kwargs) - - attempt = 0 - while self.max_attempts == -1 or attempt < self.max_attempts: # type: ignore - try: - print(f'Trying RPC call, attempt {attempt + 1}') - return func(*args, **kwargs) - except RpcError as err: - if err.code() not in self.retryable_grpc_status_codes: - raise - if self.max_attempts != -1 and attempt == self.max_attempts - 1: # type: ignore - raise - sleep_time = min( - self.max_backoff, - self.initial_backoff * (self.backoff_multiplier**attempt), - ) - print(f'Sleeping for {sleep_time} seconds before retrying RPC call') - time.sleep(sleep_time) - attempt += 1 - raise Exception(f'RPC call failed after {attempt} retries') - - async def run_rpc_async(self, func: Callable, *args, **kwargs): - # If max_retries is 0, we don't retry - if self.max_attempts == 0: - call = func(*args, **kwargs) - result = await call - return result, call - - attempt = 0 - while self.max_attempts == -1 or attempt < self.max_attempts: # type: ignore - try: - print(f'Trying RPC call, attempt {attempt + 1}') - call = func(*args, **kwargs) - result = await call - return result, call - except RpcError as err: - if err.code() not in self.retryable_grpc_status_codes: - raise - if self.max_attempts != -1 and attempt == self.max_attempts - 1: # type: ignore - raise - sleep_time = min( - self.max_backoff, - self.initial_backoff * (self.backoff_multiplier**attempt), - ) - print(f'Sleeping for {sleep_time} seconds before retrying RPC call') - await asyncio.sleep(sleep_time) - attempt += 1 - raise Exception(f'RPC call failed after {attempt} retries') - - async def make_http_call(self, session, req): - # If max_retries is 0, we don't retry - if self.max_attempts == 0: - return await session.request( - method=req['method'], - url=req['url'], - data=req['data'], - headers=req['headers'], - ssl=req['sslcontext'], - params=req['params'], - timeout=req['timeout'], - ) - - attempt = 0 - while self.max_attempts == -1 or attempt < self.max_attempts: # type: ignore - print(f'Request attempt {attempt + 1}') - r = await session.request( - method=req['method'], - url=req['url'], - data=req['data'], - headers=req['headers'], - ssl=req['sslcontext'], - params=req['params'], - timeout=req['timeout'], - ) - - if r.status not in self.retryable_http_status_codes: - return r - - if ( - self.max_attempts != -1 and attempt == self.max_attempts - 1 # type: ignore - ): # type: ignore - return r - - sleep_time = min( - self.max_backoff, - self.initial_backoff * (self.backoff_multiplier**attempt), - ) - - print(f'Sleeping for {sleep_time} seconds before retrying call') - await asyncio.sleep(sleep_time) - attempt += 1 +# -*- coding: utf-8 -*- + +""" +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import asyncio +from typing import Optional, List, Callable + +from grpc import RpcError, StatusCode # type: ignore +import time + +from dapr.conf import settings + + +class RetryPolicy: + """RetryPolicy holds the retry policy configuration for a gRPC client. + + Args: + max_attempts (int): The maximum number of retry attempts. + initial_backoff (int): The initial backoff duration. + max_backoff (int): The maximum backoff duration. + backoff_multiplier (float): The backoff multiplier. + retryable_http_status_codes (List[int]): The list of http retryable status codes + retryable_grpc_status_codes (List[StatusCode]): The list of retryable grpc status codes + """ + + def __init__( + self, + max_attempts: Optional[int] = settings.DAPR_API_MAX_RETRIES, + initial_backoff: int = 1, + max_backoff: int = 20, + backoff_multiplier: float = 1.5, + retryable_http_status_codes: List[int] = [408, 429, 500, 502, 503, 504], + retryable_grpc_status_codes: List[StatusCode] = [ + StatusCode.UNAVAILABLE, + StatusCode.DEADLINE_EXCEEDED, + ], + ): + if max_attempts < -1: # type: ignore + raise ValueError('max_attempts must be greater than or equal to -1') + self.max_attempts = max_attempts + + if initial_backoff < 1: + raise ValueError('initial_backoff must be greater than or equal to 1') + self.initial_backoff = initial_backoff + + if max_backoff < 1: + raise ValueError('max_backoff must be greater than or equal to 1') + self.max_backoff = max_backoff + + if backoff_multiplier < 1: + raise ValueError('backoff_multiplier must be greater than or equal to 1') + self.backoff_multiplier = backoff_multiplier + + if len(retryable_http_status_codes) == 0: + raise ValueError("retryable_http_status_codes can't be empty") + self.retryable_http_status_codes = retryable_http_status_codes + + if len(retryable_grpc_status_codes) == 0: + raise ValueError("retryable_http_status_codes can't be empty") + self.retryable_grpc_status_codes = retryable_grpc_status_codes + + def run_rpc(self, func=Callable, *args, **kwargs): + # If max_retries is 0, we don't retry + if self.max_attempts == 0: + return func(*args, **kwargs) + + attempt = 0 + while self.max_attempts == -1 or attempt < self.max_attempts: # type: ignore + try: + print(f'Trying RPC call, attempt {attempt + 1}') + return func(*args, **kwargs) + except RpcError as err: + if err.code() not in self.retryable_grpc_status_codes: + raise + if self.max_attempts != -1 and attempt == self.max_attempts - 1: # type: ignore + raise + sleep_time = min( + self.max_backoff, + self.initial_backoff * (self.backoff_multiplier**attempt), + ) + print(f'Sleeping for {sleep_time} seconds before retrying RPC call') + time.sleep(sleep_time) + attempt += 1 + raise Exception(f'RPC call failed after {attempt} retries') + + async def run_rpc_async(self, func: Callable, *args, **kwargs): + # If max_retries is 0, we don't retry + if self.max_attempts == 0: + call = func(*args, **kwargs) + result = await call + return result, call + + attempt = 0 + while self.max_attempts == -1 or attempt < self.max_attempts: # type: ignore + try: + print(f'Trying RPC call, attempt {attempt + 1}') + call = func(*args, **kwargs) + result = await call + return result, call + except RpcError as err: + if err.code() not in self.retryable_grpc_status_codes: + raise + if self.max_attempts != -1 and attempt == self.max_attempts - 1: # type: ignore + raise + sleep_time = min( + self.max_backoff, + self.initial_backoff * (self.backoff_multiplier**attempt), + ) + print(f'Sleeping for {sleep_time} seconds before retrying RPC call') + await asyncio.sleep(sleep_time) + attempt += 1 + raise Exception(f'RPC call failed after {attempt} retries') + + async def make_http_call(self, session, req): + # If max_retries is 0, we don't retry + if self.max_attempts == 0: + return await session.request( + method=req['method'], + url=req['url'], + data=req['data'], + headers=req['headers'], + ssl=req['sslcontext'], + params=req['params'], + timeout=req['timeout'], + ) + + attempt = 0 + while self.max_attempts == -1 or attempt < self.max_attempts: # type: ignore + print(f'Request attempt {attempt + 1}') + r = await session.request( + method=req['method'], + url=req['url'], + data=req['data'], + headers=req['headers'], + ssl=req['sslcontext'], + params=req['params'], + timeout=req['timeout'], + ) + + if r.status not in self.retryable_http_status_codes: + return r + + if ( + self.max_attempts != -1 and attempt == self.max_attempts - 1 # type: ignore + ): # type: ignore + return r + + sleep_time = min( + self.max_backoff, + self.initial_backoff * (self.backoff_multiplier**attempt), + ) + + print(f'Sleeping for {sleep_time} seconds before retrying call') + await asyncio.sleep(sleep_time) + attempt += 1 diff --git a/dapr/common/pubsub/subscription.py b/dapr/common/pubsub/subscription.py index 6f68e180d..08d74a776 100644 --- a/dapr/common/pubsub/subscription.py +++ b/dapr/common/pubsub/subscription.py @@ -1,92 +1,92 @@ -import json -from google.protobuf.json_format import MessageToDict -from dapr.proto.runtime.v1.appcallback_pb2 import TopicEventRequest -from typing import Optional, Union - - -class SubscriptionMessage: - def __init__(self, msg: TopicEventRequest): - self._id: str = msg.id - self._source: str = msg.source - self._type: str = msg.type - self._spec_version: str = msg.spec_version - self._data_content_type: str = msg.data_content_type - self._topic: str = msg.topic - self._pubsub_name: str = msg.pubsub_name - self._raw_data: bytes = msg.data - self._data: Optional[Union[dict, str]] = None - - try: - self._extensions = MessageToDict(msg.extensions) - except Exception as e: - self._extensions = {} - print(f'Error parsing extensions: {e}') - - # Parse the content based on its media type - if self._raw_data and len(self._raw_data) > 0: - self._parse_data_content() - - def id(self): - return self._id - - def source(self): - return self._source - - def type(self): - return self._type - - def spec_version(self): - return self._spec_version - - def data_content_type(self): - return self._data_content_type - - def topic(self): - return self._topic - - def pubsub_name(self): - return self._pubsub_name - - def raw_data(self): - return self._raw_data - - def extensions(self): - return self._extensions - - def data(self): - return self._data - - def _parse_data_content(self): - try: - if self._data_content_type == 'application/json': - try: - self._data = json.loads(self._raw_data) - except json.JSONDecodeError: - print(f'Error parsing json message data from topic {self._topic}') - pass # If JSON parsing fails, keep `data` as None - elif self._data_content_type == 'text/plain': - # Assume UTF-8 encoding - try: - self._data = self._raw_data.decode('utf-8') - except UnicodeDecodeError: - print(f'Error decoding message data from topic {self._topic} as UTF-8') - elif self._data_content_type.startswith( - 'application/' - ) and self._data_content_type.endswith('+json'): - # Handle custom JSON-based media types (e.g., application/vnd.api+json) - try: - self._data = json.loads(self._raw_data) - except json.JSONDecodeError: - print(f'Error parsing json message data from topic {self._topic}') - pass # If JSON parsing fails, keep `data` as None - except Exception as e: - # Log or handle any unexpected exceptions - print(f'Error parsing media type: {e}') - - -class StreamInactiveError(Exception): - pass - - -class StreamCancelledError(Exception): - pass +import json +from google.protobuf.json_format import MessageToDict +from dapr.proto.runtime.v1.appcallback_pb2 import TopicEventRequest +from typing import Optional, Union + + +class SubscriptionMessage: + def __init__(self, msg: TopicEventRequest): + self._id: str = msg.id + self._source: str = msg.source + self._type: str = msg.type + self._spec_version: str = msg.spec_version + self._data_content_type: str = msg.data_content_type + self._topic: str = msg.topic + self._pubsub_name: str = msg.pubsub_name + self._raw_data: bytes = msg.data + self._data: Optional[Union[dict, str]] = None + + try: + self._extensions = MessageToDict(msg.extensions) + except Exception as e: + self._extensions = {} + print(f'Error parsing extensions: {e}') + + # Parse the content based on its media type + if self._raw_data and len(self._raw_data) > 0: + self._parse_data_content() + + def id(self): + return self._id + + def source(self): + return self._source + + def type(self): + return self._type + + def spec_version(self): + return self._spec_version + + def data_content_type(self): + return self._data_content_type + + def topic(self): + return self._topic + + def pubsub_name(self): + return self._pubsub_name + + def raw_data(self): + return self._raw_data + + def extensions(self): + return self._extensions + + def data(self): + return self._data + + def _parse_data_content(self): + try: + if self._data_content_type == 'application/json': + try: + self._data = json.loads(self._raw_data) + except json.JSONDecodeError: + print(f'Error parsing json message data from topic {self._topic}') + pass # If JSON parsing fails, keep `data` as None + elif self._data_content_type == 'text/plain': + # Assume UTF-8 encoding + try: + self._data = self._raw_data.decode('utf-8') + except UnicodeDecodeError: + print(f'Error decoding message data from topic {self._topic} as UTF-8') + elif self._data_content_type.startswith( + 'application/' + ) and self._data_content_type.endswith('+json'): + # Handle custom JSON-based media types (e.g., application/vnd.api+json) + try: + self._data = json.loads(self._raw_data) + except json.JSONDecodeError: + print(f'Error parsing json message data from topic {self._topic}') + pass # If JSON parsing fails, keep `data` as None + except Exception as e: + # Log or handle any unexpected exceptions + print(f'Error parsing media type: {e}') + + +class StreamInactiveError(Exception): + pass + + +class StreamCancelledError(Exception): + pass diff --git a/dapr/conf/__init__.py b/dapr/conf/__init__.py index 7fbe5f2f7..629ecf004 100644 --- a/dapr/conf/__init__.py +++ b/dapr/conf/__init__.py @@ -1,40 +1,40 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os - -from dapr.conf import global_settings - - -class Settings: - def __init__(self): - for setting in dir(global_settings): - default_value = getattr(global_settings, setting) - env_variable = os.environ.get(setting) - if env_variable: - val = ( - type(default_value)(env_variable) if default_value is not None else env_variable - ) - setattr(self, setting, val) - else: - setattr(self, setting, default_value) - - def __getattr__(self, name): - if name not in dir(global_settings): - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") - return getattr(self, name) - - -settings = Settings() +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from dapr.conf import global_settings + + +class Settings: + def __init__(self): + for setting in dir(global_settings): + default_value = getattr(global_settings, setting) + env_variable = os.environ.get(setting) + if env_variable: + val = ( + type(default_value)(env_variable) if default_value is not None else env_variable + ) + setattr(self, setting, val) + else: + setattr(self, setting, default_value) + + def __getattr__(self, name): + if name not in dir(global_settings): + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") + return getattr(self, name) + + +settings = Settings() diff --git a/dapr/conf/global_settings.py b/dapr/conf/global_settings.py index 43bb51f6f..be06012c5 100644 --- a/dapr/conf/global_settings.py +++ b/dapr/conf/global_settings.py @@ -1,35 +1,35 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -# Default environment settings that environment variables overrides - -HTTP_APP_PORT = 3000 -GRPC_APP_PORT = 3010 - -DAPR_API_TOKEN = None -DAPR_HTTP_ENDPOINT = None -DAPR_GRPC_ENDPOINT = None -DAPR_RUNTIME_HOST = '127.0.0.1' -DAPR_HTTP_PORT = 3500 -DAPR_GRPC_PORT = 50001 -DAPR_API_VERSION = 'v1.0' -DAPR_HEALTH_TIMEOUT = 60 # seconds - -DAPR_API_MAX_RETRIES = 0 -DAPR_API_TIMEOUT_SECONDS = 60 - -DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' - -DAPR_HTTP_TIMEOUT_SECONDS = 60 +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +# Default environment settings that environment variables overrides + +HTTP_APP_PORT = 3000 +GRPC_APP_PORT = 3010 + +DAPR_API_TOKEN = None +DAPR_HTTP_ENDPOINT = None +DAPR_GRPC_ENDPOINT = None +DAPR_RUNTIME_HOST = '127.0.0.1' +DAPR_HTTP_PORT = 3500 +DAPR_GRPC_PORT = 50001 +DAPR_API_VERSION = 'v1.0' +DAPR_HEALTH_TIMEOUT = 60 # seconds + +DAPR_API_MAX_RETRIES = 0 +DAPR_API_TIMEOUT_SECONDS = 60 + +DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' + +DAPR_HTTP_TIMEOUT_SECONDS = 60 diff --git a/dapr/conf/helpers.py b/dapr/conf/helpers.py index ab1e494b2..609ff881e 100644 --- a/dapr/conf/helpers.py +++ b/dapr/conf/helpers.py @@ -1,191 +1,191 @@ -from warnings import warn -from urllib.parse import urlparse, parse_qs, ParseResult - - -class URIParseConfig: - DEFAULT_SCHEME = 'dns' - DEFAULT_HOSTNAME = 'localhost' - DEFAULT_PORT = 443 - DEFAULT_AUTHORITY = '' - ACCEPTED_SCHEMES = ['dns', 'unix', 'unix-abstract', 'vsock', 'http', 'https'] - - -class GrpcEndpoint: - _scheme: str - _hostname: str - _port: int - _tls: bool - _authority: str - _url: str - _parsed_url: ParseResult # from urllib.parse - _endpoint: str - - def __init__(self, url: str): - self._authority = URIParseConfig.DEFAULT_AUTHORITY - self._url = url - - self._parsed_url = urlparse(self._preprocess_uri(url)) - self._validate_path_and_query() - - self._set_tls() - self._set_hostname() - self._set_scheme() - self._set_port() - self._set_endpoint() - - def _set_scheme(self): - if len(self._parsed_url.scheme) == 0: - self._scheme = URIParseConfig.DEFAULT_SCHEME - return - - if self._parsed_url.scheme in ['http', 'https']: - self._scheme = URIParseConfig.DEFAULT_SCHEME - warn( - 'http and https schemes are deprecated for grpc, use myhost?tls=false or myhost?tls=true instead' - ) - return - - if self._parsed_url.scheme not in URIParseConfig.ACCEPTED_SCHEMES: - raise ValueError(f"invalid scheme '{self._parsed_url.scheme}' in URL '{self._url}'") - - self._scheme = self._parsed_url.scheme - - @property - def scheme(self) -> str: - return self._scheme - - def _set_hostname(self): - if self._parsed_url.hostname is None: - self._hostname = URIParseConfig.DEFAULT_HOSTNAME - return - - if self._parsed_url.hostname.count(':') == 7: - # IPv6 address - self._hostname = f'[{self._parsed_url.hostname}]' - return - - self._hostname = self._parsed_url.hostname - - @property - def hostname(self) -> str: - return self._hostname - - def _set_port(self): - if self._parsed_url.scheme in ['unix', 'unix-abstract']: - self._port = 0 - return - - if self._parsed_url.port is None: - self._port = URIParseConfig.DEFAULT_PORT - return - - self._port = self._parsed_url.port - - @property - def port(self) -> str: - if self._port == 0: - return '' - - return str(self._port) - - @property - def port_as_int(self) -> int: - return self._port - - def _set_endpoint(self): - port = '' if not self._port else f':{self.port}' - - if self._scheme == 'unix': - separator = '://' if self._url.startswith('unix://') else ':' - self._endpoint = f'{self._scheme}{separator}{self._hostname}' - return - - if self._scheme == 'vsock': - self._endpoint = f'{self._scheme}:{self._hostname}:{self.port}' - return - - if self._scheme == 'unix-abstract': - self._endpoint = f'{self._scheme}:{self._hostname}{port}' - return - - if self._scheme == 'dns': - authority = f'//{self._authority}/' if self._authority else '' - self._endpoint = f'{self._scheme}:{authority}{self._hostname}{port}' - return - - self._endpoint = f'{self._scheme}:{self._hostname}{port}' - - @property - def endpoint(self) -> str: - return self._endpoint - - # Prepares the uri string in a specific format for parsing by the urlparse function - def _preprocess_uri(self, url: str) -> str: - url_list = url.split(':') - if len(url_list) == 3 and '://' not in url: - # A URI like dns:mydomain:5000 or vsock:mycid:5000 was used - url = url.replace(':', '://', 1) - elif ( - len(url_list) >= 2 - and '://' not in url - and url_list[0] in URIParseConfig.ACCEPTED_SCHEMES - ): - # A URI like dns:mydomain or dns:[2001:db8:1f70::999:de8:7648:6e8]:mydomain was used - # Possibly a URI like dns:[2001:db8:1f70::999:de8:7648:6e8]:mydomain was used - url = url.replace(':', '://', 1) - else: - url_list = url.split('://') - if len(url_list) == 1: - # If a scheme was not explicitly specified in the URL - # we need to add a default scheme, - # because of how urlparse works - url = f'{URIParseConfig.DEFAULT_SCHEME}://{url}' - else: - # If a scheme was explicitly specified in the URL - # we need to make sure it is a valid scheme - scheme = url_list[0] - if scheme not in URIParseConfig.ACCEPTED_SCHEMES: - raise ValueError(f"invalid scheme '{scheme}' in URL '{url}'") - - # We should do a special check if the scheme is dns, and it uses - # an authority in the format of dns:[//authority/]host[:port] - if scheme.lower() == 'dns': - # A URI like dns://authority/mydomain was used - url_list = url.split('/') - if len(url_list) < 4: - raise ValueError(f"invalid dns authority '{url_list[2]}' in URL '{url}'") - self._authority = url_list[2] - url = f'dns://{url_list[3]}' - return url - - def _set_tls(self): - query_dict = parse_qs(self._parsed_url.query) - tls_str = query_dict.get('tls', [''])[0] - tls = tls_str.lower() == 'true' - if self._parsed_url.scheme == 'https': - tls = True - - self._tls = tls - - @property - def tls(self) -> bool: - return self._tls - - def _validate_path_and_query(self) -> None: - if self._parsed_url.path: - raise ValueError( - f'paths are not supported for gRPC endpoints:' f" '{self._parsed_url.path}'" - ) - if self._parsed_url.query: - query_dict = parse_qs(self._parsed_url.query) - if 'tls' in query_dict and self._parsed_url.scheme in ['http', 'https']: - raise ValueError( - f'the tls query parameter is not supported for http(s) endpoints: ' - f"'{self._parsed_url.query}'" - ) - query_dict.pop('tls', None) - if query_dict: - raise ValueError( - f'query parameters are not supported for gRPC endpoints:' - f" '{self._parsed_url.query}'" - ) +from warnings import warn +from urllib.parse import urlparse, parse_qs, ParseResult + + +class URIParseConfig: + DEFAULT_SCHEME = 'dns' + DEFAULT_HOSTNAME = 'localhost' + DEFAULT_PORT = 443 + DEFAULT_AUTHORITY = '' + ACCEPTED_SCHEMES = ['dns', 'unix', 'unix-abstract', 'vsock', 'http', 'https'] + + +class GrpcEndpoint: + _scheme: str + _hostname: str + _port: int + _tls: bool + _authority: str + _url: str + _parsed_url: ParseResult # from urllib.parse + _endpoint: str + + def __init__(self, url: str): + self._authority = URIParseConfig.DEFAULT_AUTHORITY + self._url = url + + self._parsed_url = urlparse(self._preprocess_uri(url)) + self._validate_path_and_query() + + self._set_tls() + self._set_hostname() + self._set_scheme() + self._set_port() + self._set_endpoint() + + def _set_scheme(self): + if len(self._parsed_url.scheme) == 0: + self._scheme = URIParseConfig.DEFAULT_SCHEME + return + + if self._parsed_url.scheme in ['http', 'https']: + self._scheme = URIParseConfig.DEFAULT_SCHEME + warn( + 'http and https schemes are deprecated for grpc, use myhost?tls=false or myhost?tls=true instead' + ) + return + + if self._parsed_url.scheme not in URIParseConfig.ACCEPTED_SCHEMES: + raise ValueError(f"invalid scheme '{self._parsed_url.scheme}' in URL '{self._url}'") + + self._scheme = self._parsed_url.scheme + + @property + def scheme(self) -> str: + return self._scheme + + def _set_hostname(self): + if self._parsed_url.hostname is None: + self._hostname = URIParseConfig.DEFAULT_HOSTNAME + return + + if self._parsed_url.hostname.count(':') == 7: + # IPv6 address + self._hostname = f'[{self._parsed_url.hostname}]' + return + + self._hostname = self._parsed_url.hostname + + @property + def hostname(self) -> str: + return self._hostname + + def _set_port(self): + if self._parsed_url.scheme in ['unix', 'unix-abstract']: + self._port = 0 + return + + if self._parsed_url.port is None: + self._port = URIParseConfig.DEFAULT_PORT + return + + self._port = self._parsed_url.port + + @property + def port(self) -> str: + if self._port == 0: + return '' + + return str(self._port) + + @property + def port_as_int(self) -> int: + return self._port + + def _set_endpoint(self): + port = '' if not self._port else f':{self.port}' + + if self._scheme == 'unix': + separator = '://' if self._url.startswith('unix://') else ':' + self._endpoint = f'{self._scheme}{separator}{self._hostname}' + return + + if self._scheme == 'vsock': + self._endpoint = f'{self._scheme}:{self._hostname}:{self.port}' + return + + if self._scheme == 'unix-abstract': + self._endpoint = f'{self._scheme}:{self._hostname}{port}' + return + + if self._scheme == 'dns': + authority = f'//{self._authority}/' if self._authority else '' + self._endpoint = f'{self._scheme}:{authority}{self._hostname}{port}' + return + + self._endpoint = f'{self._scheme}:{self._hostname}{port}' + + @property + def endpoint(self) -> str: + return self._endpoint + + # Prepares the uri string in a specific format for parsing by the urlparse function + def _preprocess_uri(self, url: str) -> str: + url_list = url.split(':') + if len(url_list) == 3 and '://' not in url: + # A URI like dns:mydomain:5000 or vsock:mycid:5000 was used + url = url.replace(':', '://', 1) + elif ( + len(url_list) >= 2 + and '://' not in url + and url_list[0] in URIParseConfig.ACCEPTED_SCHEMES + ): + # A URI like dns:mydomain or dns:[2001:db8:1f70::999:de8:7648:6e8]:mydomain was used + # Possibly a URI like dns:[2001:db8:1f70::999:de8:7648:6e8]:mydomain was used + url = url.replace(':', '://', 1) + else: + url_list = url.split('://') + if len(url_list) == 1: + # If a scheme was not explicitly specified in the URL + # we need to add a default scheme, + # because of how urlparse works + url = f'{URIParseConfig.DEFAULT_SCHEME}://{url}' + else: + # If a scheme was explicitly specified in the URL + # we need to make sure it is a valid scheme + scheme = url_list[0] + if scheme not in URIParseConfig.ACCEPTED_SCHEMES: + raise ValueError(f"invalid scheme '{scheme}' in URL '{url}'") + + # We should do a special check if the scheme is dns, and it uses + # an authority in the format of dns:[//authority/]host[:port] + if scheme.lower() == 'dns': + # A URI like dns://authority/mydomain was used + url_list = url.split('/') + if len(url_list) < 4: + raise ValueError(f"invalid dns authority '{url_list[2]}' in URL '{url}'") + self._authority = url_list[2] + url = f'dns://{url_list[3]}' + return url + + def _set_tls(self): + query_dict = parse_qs(self._parsed_url.query) + tls_str = query_dict.get('tls', [''])[0] + tls = tls_str.lower() == 'true' + if self._parsed_url.scheme == 'https': + tls = True + + self._tls = tls + + @property + def tls(self) -> bool: + return self._tls + + def _validate_path_and_query(self) -> None: + if self._parsed_url.path: + raise ValueError( + f'paths are not supported for gRPC endpoints:' f" '{self._parsed_url.path}'" + ) + if self._parsed_url.query: + query_dict = parse_qs(self._parsed_url.query) + if 'tls' in query_dict and self._parsed_url.scheme in ['http', 'https']: + raise ValueError( + f'the tls query parameter is not supported for http(s) endpoints: ' + f"'{self._parsed_url.query}'" + ) + query_dict.pop('tls', None) + if query_dict: + raise ValueError( + f'query parameters are not supported for gRPC endpoints:' + f" '{self._parsed_url.query}'" + ) diff --git a/dapr/proto/README.md b/dapr/proto/README.md index 8ce199958..bf6106c25 100644 --- a/dapr/proto/README.md +++ b/dapr/proto/README.md @@ -1,19 +1,19 @@ -## Generating gRPC interface and Protobuf - -As a good practice create a python virtual environment: - -```sh -python3 -m venv -source /bin/activate -``` - -### Linux and MacOS - -Run the following commands: - -```sh -pip3 install -r dev-requirements.txt -./tools/regen_grpcclient.sh -``` - -> Note: To use the newly generated protobuf stubs and gRPC interface replace `daprd` with `edge` version of `daprd` built from master branch. Refer [this](https://github.com/dapr/dapr/blob/master/docs/development/developing-dapr.md#build-the-dapr-binaries) for instructions on how to build `daprd` from master. +## Generating gRPC interface and Protobuf + +As a good practice create a python virtual environment: + +```sh +python3 -m venv +source /bin/activate +``` + +### Linux and MacOS + +Run the following commands: + +```sh +pip3 install -r dev-requirements.txt +./tools/regen_grpcclient.sh +``` + +> Note: To use the newly generated protobuf stubs and gRPC interface replace `daprd` with `edge` version of `daprd` built from master branch. Refer [this](https://github.com/dapr/dapr/blob/master/docs/development/developing-dapr.md#build-the-dapr-binaries) for instructions on how to build `daprd` from master. diff --git a/dapr/proto/__init__.py b/dapr/proto/__init__.py index 3a918a66d..820a01e53 100644 --- a/dapr/proto/__init__.py +++ b/dapr/proto/__init__.py @@ -1,46 +1,46 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os -import grpc - -from contextlib import contextmanager -from typing import Optional -from dapr.conf import settings - -from dapr.proto.common.v1 import common_pb2 as common_v1 -from dapr.proto.runtime.v1 import dapr_pb2 as api_v1 -from dapr.proto.runtime.v1 import dapr_pb2_grpc as api_service_v1 -from dapr.proto.runtime.v1 import appcallback_pb2 as appcallback_v1 -from dapr.proto.runtime.v1 import appcallback_pb2_grpc as appcallback_service_v1 - - -@contextmanager -def connect_dapr(port: Optional[int] = -1): - if port == -1: - port = settings.DAPR_GRPC_PORT - channel = grpc.insecure_channel(f"127.0.0.1:{port}") - stub = api_service_v1.DaprStub(channel) - yield stub - channel.close() - - -__all__ = [ - 'connect_dapr', - 'common_v1', - 'api_v1', - 'appcallback_v1', - 'appcallback_service_v1', -] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os +import grpc + +from contextlib import contextmanager +from typing import Optional +from dapr.conf import settings + +from dapr.proto.common.v1 import common_pb2 as common_v1 +from dapr.proto.runtime.v1 import dapr_pb2 as api_v1 +from dapr.proto.runtime.v1 import dapr_pb2_grpc as api_service_v1 +from dapr.proto.runtime.v1 import appcallback_pb2 as appcallback_v1 +from dapr.proto.runtime.v1 import appcallback_pb2_grpc as appcallback_service_v1 + + +@contextmanager +def connect_dapr(port: Optional[int] = -1): + if port == -1: + port = settings.DAPR_GRPC_PORT + channel = grpc.insecure_channel(f"127.0.0.1:{port}") + stub = api_service_v1.DaprStub(channel) + yield stub + channel.close() + + +__all__ = [ + 'connect_dapr', + 'common_v1', + 'api_v1', + 'appcallback_v1', + 'appcallback_service_v1', +] diff --git a/dapr/proto/common/__init__.py b/dapr/proto/common/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/proto/common/__init__.py +++ b/dapr/proto/common/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/proto/common/v1/__init__.py b/dapr/proto/common/v1/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/proto/common/v1/__init__.py +++ b/dapr/proto/common/v1/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/proto/common/v1/common_pb2.py b/dapr/proto/common/v1/common_pb2.py index a1bf25663..cdd51313e 100644 --- a/dapr/proto/common/v1/common_pb2.py +++ b/dapr/proto/common/v1/common_pb2.py @@ -1,56 +1,56 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: dapr/proto/common/v1/common.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!dapr/proto/common/v1/common.proto\x12\x14\x64\x61pr.proto.common.v1\x1a\x19google/protobuf/any.proto\"\xd0\x01\n\rHTTPExtension\x12\x36\n\x04verb\x18\x01 \x01(\x0e\x32(.dapr.proto.common.v1.HTTPExtension.Verb\x12\x13\n\x0bquerystring\x18\x02 \x01(\t\"r\n\x04Verb\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03GET\x10\x01\x12\x08\n\x04HEAD\x10\x02\x12\x08\n\x04POST\x10\x03\x12\x07\n\x03PUT\x10\x04\x12\n\n\x06\x44\x45LETE\x10\x05\x12\x0b\n\x07\x43ONNECT\x10\x06\x12\x0b\n\x07OPTIONS\x10\x07\x12\t\n\x05TRACE\x10\x08\x12\t\n\x05PATCH\x10\t\"\x96\x01\n\rInvokeRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\"\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12;\n\x0ehttp_extension\x18\x04 \x01(\x0b\x32#.dapr.proto.common.v1.HTTPExtension\"J\n\x0eInvokeResponse\x12\"\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\"*\n\rStreamPayload\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0b\n\x03seq\x18\x02 \x01(\x04\"\xf8\x01\n\tStateItem\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12(\n\x04\x65tag\x18\x03 \x01(\x0b\x32\x1a.dapr.proto.common.v1.Etag\x12?\n\x08metadata\x18\x04 \x03(\x0b\x32-.dapr.proto.common.v1.StateItem.MetadataEntry\x12\x33\n\x07options\x18\x05 \x01(\x0b\x32\".dapr.proto.common.v1.StateOptions\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x15\n\x04\x45tag\x12\r\n\x05value\x18\x01 \x01(\t\"\xef\x02\n\x0cStateOptions\x12H\n\x0b\x63oncurrency\x18\x01 \x01(\x0e\x32\x33.dapr.proto.common.v1.StateOptions.StateConcurrency\x12H\n\x0b\x63onsistency\x18\x02 \x01(\x0e\x32\x33.dapr.proto.common.v1.StateOptions.StateConsistency\"h\n\x10StateConcurrency\x12\x1b\n\x17\x43ONCURRENCY_UNSPECIFIED\x10\x00\x12\x1b\n\x17\x43ONCURRENCY_FIRST_WRITE\x10\x01\x12\x1a\n\x16\x43ONCURRENCY_LAST_WRITE\x10\x02\"a\n\x10StateConsistency\x12\x1b\n\x17\x43ONSISTENCY_UNSPECIFIED\x10\x00\x12\x18\n\x14\x43ONSISTENCY_EVENTUAL\x10\x01\x12\x16\n\x12\x43ONSISTENCY_STRONG\x10\x02\"\xad\x01\n\x11\x43onfigurationItem\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12G\n\x08metadata\x18\x03 \x03(\x0b\x32\x35.dapr.proto.common.v1.ConfigurationItem.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42i\n\nio.dapr.v1B\x0c\x43ommonProtosZ/github.com/dapr/dapr/pkg/proto/common/v1;common\xaa\x02\x1b\x44\x61pr.Client.Autogen.Grpc.v1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dapr.proto.common.v1.common_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\nio.dapr.v1B\014CommonProtosZ/github.com/dapr/dapr/pkg/proto/common/v1;common\252\002\033Dapr.Client.Autogen.Grpc.v1' - _STATEITEM_METADATAENTRY._options = None - _STATEITEM_METADATAENTRY._serialized_options = b'8\001' - _CONFIGURATIONITEM_METADATAENTRY._options = None - _CONFIGURATIONITEM_METADATAENTRY._serialized_options = b'8\001' - _globals['_HTTPEXTENSION']._serialized_start=87 - _globals['_HTTPEXTENSION']._serialized_end=295 - _globals['_HTTPEXTENSION_VERB']._serialized_start=181 - _globals['_HTTPEXTENSION_VERB']._serialized_end=295 - _globals['_INVOKEREQUEST']._serialized_start=298 - _globals['_INVOKEREQUEST']._serialized_end=448 - _globals['_INVOKERESPONSE']._serialized_start=450 - _globals['_INVOKERESPONSE']._serialized_end=524 - _globals['_STREAMPAYLOAD']._serialized_start=526 - _globals['_STREAMPAYLOAD']._serialized_end=568 - _globals['_STATEITEM']._serialized_start=571 - _globals['_STATEITEM']._serialized_end=819 - _globals['_STATEITEM_METADATAENTRY']._serialized_start=772 - _globals['_STATEITEM_METADATAENTRY']._serialized_end=819 - _globals['_ETAG']._serialized_start=821 - _globals['_ETAG']._serialized_end=842 - _globals['_STATEOPTIONS']._serialized_start=845 - _globals['_STATEOPTIONS']._serialized_end=1212 - _globals['_STATEOPTIONS_STATECONCURRENCY']._serialized_start=1009 - _globals['_STATEOPTIONS_STATECONCURRENCY']._serialized_end=1113 - _globals['_STATEOPTIONS_STATECONSISTENCY']._serialized_start=1115 - _globals['_STATEOPTIONS_STATECONSISTENCY']._serialized_end=1212 - _globals['_CONFIGURATIONITEM']._serialized_start=1215 - _globals['_CONFIGURATIONITEM']._serialized_end=1388 - _globals['_CONFIGURATIONITEM_METADATAENTRY']._serialized_start=772 - _globals['_CONFIGURATIONITEM_METADATAENTRY']._serialized_end=819 -# @@protoc_insertion_point(module_scope) +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: dapr/proto/common/v1/common.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!dapr/proto/common/v1/common.proto\x12\x14\x64\x61pr.proto.common.v1\x1a\x19google/protobuf/any.proto\"\xd0\x01\n\rHTTPExtension\x12\x36\n\x04verb\x18\x01 \x01(\x0e\x32(.dapr.proto.common.v1.HTTPExtension.Verb\x12\x13\n\x0bquerystring\x18\x02 \x01(\t\"r\n\x04Verb\x12\x08\n\x04NONE\x10\x00\x12\x07\n\x03GET\x10\x01\x12\x08\n\x04HEAD\x10\x02\x12\x08\n\x04POST\x10\x03\x12\x07\n\x03PUT\x10\x04\x12\n\n\x06\x44\x45LETE\x10\x05\x12\x0b\n\x07\x43ONNECT\x10\x06\x12\x0b\n\x07OPTIONS\x10\x07\x12\t\n\x05TRACE\x10\x08\x12\t\n\x05PATCH\x10\t\"\x96\x01\n\rInvokeRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\"\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12;\n\x0ehttp_extension\x18\x04 \x01(\x0b\x32#.dapr.proto.common.v1.HTTPExtension\"J\n\x0eInvokeResponse\x12\"\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\"*\n\rStreamPayload\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0b\n\x03seq\x18\x02 \x01(\x04\"\xf8\x01\n\tStateItem\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c\x12(\n\x04\x65tag\x18\x03 \x01(\x0b\x32\x1a.dapr.proto.common.v1.Etag\x12?\n\x08metadata\x18\x04 \x03(\x0b\x32-.dapr.proto.common.v1.StateItem.MetadataEntry\x12\x33\n\x07options\x18\x05 \x01(\x0b\x32\".dapr.proto.common.v1.StateOptions\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x15\n\x04\x45tag\x12\r\n\x05value\x18\x01 \x01(\t\"\xef\x02\n\x0cStateOptions\x12H\n\x0b\x63oncurrency\x18\x01 \x01(\x0e\x32\x33.dapr.proto.common.v1.StateOptions.StateConcurrency\x12H\n\x0b\x63onsistency\x18\x02 \x01(\x0e\x32\x33.dapr.proto.common.v1.StateOptions.StateConsistency\"h\n\x10StateConcurrency\x12\x1b\n\x17\x43ONCURRENCY_UNSPECIFIED\x10\x00\x12\x1b\n\x17\x43ONCURRENCY_FIRST_WRITE\x10\x01\x12\x1a\n\x16\x43ONCURRENCY_LAST_WRITE\x10\x02\"a\n\x10StateConsistency\x12\x1b\n\x17\x43ONSISTENCY_UNSPECIFIED\x10\x00\x12\x18\n\x14\x43ONSISTENCY_EVENTUAL\x10\x01\x12\x16\n\x12\x43ONSISTENCY_STRONG\x10\x02\"\xad\x01\n\x11\x43onfigurationItem\x12\r\n\x05value\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12G\n\x08metadata\x18\x03 \x03(\x0b\x32\x35.dapr.proto.common.v1.ConfigurationItem.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42i\n\nio.dapr.v1B\x0c\x43ommonProtosZ/github.com/dapr/dapr/pkg/proto/common/v1;common\xaa\x02\x1b\x44\x61pr.Client.Autogen.Grpc.v1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dapr.proto.common.v1.common_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\nio.dapr.v1B\014CommonProtosZ/github.com/dapr/dapr/pkg/proto/common/v1;common\252\002\033Dapr.Client.Autogen.Grpc.v1' + _STATEITEM_METADATAENTRY._options = None + _STATEITEM_METADATAENTRY._serialized_options = b'8\001' + _CONFIGURATIONITEM_METADATAENTRY._options = None + _CONFIGURATIONITEM_METADATAENTRY._serialized_options = b'8\001' + _globals['_HTTPEXTENSION']._serialized_start=87 + _globals['_HTTPEXTENSION']._serialized_end=295 + _globals['_HTTPEXTENSION_VERB']._serialized_start=181 + _globals['_HTTPEXTENSION_VERB']._serialized_end=295 + _globals['_INVOKEREQUEST']._serialized_start=298 + _globals['_INVOKEREQUEST']._serialized_end=448 + _globals['_INVOKERESPONSE']._serialized_start=450 + _globals['_INVOKERESPONSE']._serialized_end=524 + _globals['_STREAMPAYLOAD']._serialized_start=526 + _globals['_STREAMPAYLOAD']._serialized_end=568 + _globals['_STATEITEM']._serialized_start=571 + _globals['_STATEITEM']._serialized_end=819 + _globals['_STATEITEM_METADATAENTRY']._serialized_start=772 + _globals['_STATEITEM_METADATAENTRY']._serialized_end=819 + _globals['_ETAG']._serialized_start=821 + _globals['_ETAG']._serialized_end=842 + _globals['_STATEOPTIONS']._serialized_start=845 + _globals['_STATEOPTIONS']._serialized_end=1212 + _globals['_STATEOPTIONS_STATECONCURRENCY']._serialized_start=1009 + _globals['_STATEOPTIONS_STATECONCURRENCY']._serialized_end=1113 + _globals['_STATEOPTIONS_STATECONSISTENCY']._serialized_start=1115 + _globals['_STATEOPTIONS_STATECONSISTENCY']._serialized_end=1212 + _globals['_CONFIGURATIONITEM']._serialized_start=1215 + _globals['_CONFIGURATIONITEM']._serialized_end=1388 + _globals['_CONFIGURATIONITEM_METADATAENTRY']._serialized_start=772 + _globals['_CONFIGURATIONITEM_METADATAENTRY']._serialized_end=819 +# @@protoc_insertion_point(module_scope) diff --git a/dapr/proto/common/v1/common_pb2.pyi b/dapr/proto/common/v1/common_pb2.pyi index 0b23ce54d..969f24f19 100644 --- a/dapr/proto/common/v1/common_pb2.pyi +++ b/dapr/proto/common/v1/common_pb2.pyi @@ -1,367 +1,367 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file - -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import google.protobuf.any_pb2 -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class HTTPExtension(google.protobuf.message.Message): - """HTTPExtension includes HTTP verb and querystring - when Dapr runtime delivers HTTP content. - - For example, when callers calls http invoke api - `POST http://localhost:3500/v1.0/invoke//method/?query1=value1&query2=value2` - - Dapr runtime will parse POST as a verb and extract querystring to quersytring map. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _Verb: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _VerbEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[HTTPExtension._Verb.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - NONE: HTTPExtension._Verb.ValueType # 0 - GET: HTTPExtension._Verb.ValueType # 1 - HEAD: HTTPExtension._Verb.ValueType # 2 - POST: HTTPExtension._Verb.ValueType # 3 - PUT: HTTPExtension._Verb.ValueType # 4 - DELETE: HTTPExtension._Verb.ValueType # 5 - CONNECT: HTTPExtension._Verb.ValueType # 6 - OPTIONS: HTTPExtension._Verb.ValueType # 7 - TRACE: HTTPExtension._Verb.ValueType # 8 - PATCH: HTTPExtension._Verb.ValueType # 9 - - class Verb(_Verb, metaclass=_VerbEnumTypeWrapper): - """Type of HTTP 1.1 Methods - RFC 7231: https://tools.ietf.org/html/rfc7231#page-24 - RFC 5789: https://datatracker.ietf.org/doc/html/rfc5789 - """ - - NONE: HTTPExtension.Verb.ValueType # 0 - GET: HTTPExtension.Verb.ValueType # 1 - HEAD: HTTPExtension.Verb.ValueType # 2 - POST: HTTPExtension.Verb.ValueType # 3 - PUT: HTTPExtension.Verb.ValueType # 4 - DELETE: HTTPExtension.Verb.ValueType # 5 - CONNECT: HTTPExtension.Verb.ValueType # 6 - OPTIONS: HTTPExtension.Verb.ValueType # 7 - TRACE: HTTPExtension.Verb.ValueType # 8 - PATCH: HTTPExtension.Verb.ValueType # 9 - - VERB_FIELD_NUMBER: builtins.int - QUERYSTRING_FIELD_NUMBER: builtins.int - verb: global___HTTPExtension.Verb.ValueType - """Required. HTTP verb.""" - querystring: builtins.str - """Optional. querystring represents an encoded HTTP url query string in the following format: name=value&name2=value2""" - def __init__( - self, - *, - verb: global___HTTPExtension.Verb.ValueType = ..., - querystring: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["querystring", b"querystring", "verb", b"verb"]) -> None: ... - -global___HTTPExtension = HTTPExtension - -@typing_extensions.final -class InvokeRequest(google.protobuf.message.Message): - """InvokeRequest is the message to invoke a method with the data. - This message is used in InvokeService of Dapr gRPC Service and OnInvoke - of AppCallback gRPC service. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - METHOD_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - CONTENT_TYPE_FIELD_NUMBER: builtins.int - HTTP_EXTENSION_FIELD_NUMBER: builtins.int - method: builtins.str - """Required. method is a method name which will be invoked by caller.""" - @property - def data(self) -> google.protobuf.any_pb2.Any: - """Required in unary RPCs. Bytes value or Protobuf message which caller sent. - Dapr treats Any.value as bytes type if Any.type_url is unset. - """ - content_type: builtins.str - """The type of data content. - - This field is required if data delivers http request body - Otherwise, this is optional. - """ - @property - def http_extension(self) -> global___HTTPExtension: - """HTTP specific fields if request conveys http-compatible request. - - This field is required for http-compatible request. Otherwise, - this field is optional. - """ - def __init__( - self, - *, - method: builtins.str = ..., - data: google.protobuf.any_pb2.Any | None = ..., - content_type: builtins.str = ..., - http_extension: global___HTTPExtension | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["data", b"data", "http_extension", b"http_extension"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "data", b"data", "http_extension", b"http_extension", "method", b"method"]) -> None: ... - -global___InvokeRequest = InvokeRequest - -@typing_extensions.final -class InvokeResponse(google.protobuf.message.Message): - """InvokeResponse is the response message including data and its content type - from app callback. - This message is used in InvokeService of Dapr gRPC Service and OnInvoke - of AppCallback gRPC service. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_FIELD_NUMBER: builtins.int - CONTENT_TYPE_FIELD_NUMBER: builtins.int - @property - def data(self) -> google.protobuf.any_pb2.Any: - """Required in unary RPCs. The content body of InvokeService response.""" - content_type: builtins.str - """Required. The type of data content.""" - def __init__( - self, - *, - data: google.protobuf.any_pb2.Any | None = ..., - content_type: builtins.str = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["data", b"data"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "data", b"data"]) -> None: ... - -global___InvokeResponse = InvokeResponse - -@typing_extensions.final -class StreamPayload(google.protobuf.message.Message): - """Chunk of data sent in a streaming request or response. - This is used in requests including InternalInvokeRequestStream. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_FIELD_NUMBER: builtins.int - SEQ_FIELD_NUMBER: builtins.int - data: builtins.bytes - """Data sent in the chunk. - The amount of data included in each chunk is up to the discretion of the sender, and can be empty. - Additionally, the amount of data doesn't need to be fixed and subsequent messages can send more, or less, data. - Receivers must not make assumptions about the number of bytes they'll receive in each chunk. - """ - seq: builtins.int - """Sequence number. This is a counter that starts from 0 and increments by 1 on each chunk sent.""" - def __init__( - self, - *, - data: builtins.bytes = ..., - seq: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "seq", b"seq"]) -> None: ... - -global___StreamPayload = StreamPayload - -@typing_extensions.final -class StateItem(google.protobuf.message.Message): - """StateItem represents state key, value, and additional options to save state.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - OPTIONS_FIELD_NUMBER: builtins.int - key: builtins.str - """Required. The state key""" - value: builtins.bytes - """Required. The state data for key""" - @property - def etag(self) -> global___Etag: - """The entity tag which represents the specific version of data. - The exact ETag format is defined by the corresponding data store. - """ - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be passed to state store component.""" - @property - def options(self) -> global___StateOptions: - """Options for concurrency and consistency to save the state.""" - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.bytes = ..., - etag: global___Etag | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - options: global___StateOptions | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["etag", b"etag", "options", b"options"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["etag", b"etag", "key", b"key", "metadata", b"metadata", "options", b"options", "value", b"value"]) -> None: ... - -global___StateItem = StateItem - -@typing_extensions.final -class Etag(google.protobuf.message.Message): - """Etag represents a state item version""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VALUE_FIELD_NUMBER: builtins.int - value: builtins.str - """value sets the etag value""" - def __init__( - self, - *, - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ... - -global___Etag = Etag - -@typing_extensions.final -class StateOptions(google.protobuf.message.Message): - """StateOptions configures concurrency and consistency for state operations""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _StateConcurrency: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _StateConcurrencyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[StateOptions._StateConcurrency.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - CONCURRENCY_UNSPECIFIED: StateOptions._StateConcurrency.ValueType # 0 - CONCURRENCY_FIRST_WRITE: StateOptions._StateConcurrency.ValueType # 1 - CONCURRENCY_LAST_WRITE: StateOptions._StateConcurrency.ValueType # 2 - - class StateConcurrency(_StateConcurrency, metaclass=_StateConcurrencyEnumTypeWrapper): - """Enum describing the supported concurrency for state.""" - - CONCURRENCY_UNSPECIFIED: StateOptions.StateConcurrency.ValueType # 0 - CONCURRENCY_FIRST_WRITE: StateOptions.StateConcurrency.ValueType # 1 - CONCURRENCY_LAST_WRITE: StateOptions.StateConcurrency.ValueType # 2 - - class _StateConsistency: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _StateConsistencyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[StateOptions._StateConsistency.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - CONSISTENCY_UNSPECIFIED: StateOptions._StateConsistency.ValueType # 0 - CONSISTENCY_EVENTUAL: StateOptions._StateConsistency.ValueType # 1 - CONSISTENCY_STRONG: StateOptions._StateConsistency.ValueType # 2 - - class StateConsistency(_StateConsistency, metaclass=_StateConsistencyEnumTypeWrapper): - """Enum describing the supported consistency for state.""" - - CONSISTENCY_UNSPECIFIED: StateOptions.StateConsistency.ValueType # 0 - CONSISTENCY_EVENTUAL: StateOptions.StateConsistency.ValueType # 1 - CONSISTENCY_STRONG: StateOptions.StateConsistency.ValueType # 2 - - CONCURRENCY_FIELD_NUMBER: builtins.int - CONSISTENCY_FIELD_NUMBER: builtins.int - concurrency: global___StateOptions.StateConcurrency.ValueType - consistency: global___StateOptions.StateConsistency.ValueType - def __init__( - self, - *, - concurrency: global___StateOptions.StateConcurrency.ValueType = ..., - consistency: global___StateOptions.StateConsistency.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["concurrency", b"concurrency", "consistency", b"consistency"]) -> None: ... - -global___StateOptions = StateOptions - -@typing_extensions.final -class ConfigurationItem(google.protobuf.message.Message): - """ConfigurationItem represents all the configuration with its name(key).""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - VALUE_FIELD_NUMBER: builtins.int - VERSION_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - value: builtins.str - """Required. The value of configuration item.""" - version: builtins.str - """Version is response only and cannot be fetched. Store is not expected to keep all versions available""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """the metadata which will be passed to/from configuration store component.""" - def __init__( - self, - *, - value: builtins.str = ..., - version: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "value", b"value", "version", b"version"]) -> None: ... - -global___ConfigurationItem = ConfigurationItem +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file + +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import builtins +import collections.abc +import google.protobuf.any_pb2 +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing_extensions.final +class HTTPExtension(google.protobuf.message.Message): + """HTTPExtension includes HTTP verb and querystring + when Dapr runtime delivers HTTP content. + + For example, when callers calls http invoke api + `POST http://localhost:3500/v1.0/invoke//method/?query1=value1&query2=value2` + + Dapr runtime will parse POST as a verb and extract querystring to quersytring map. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Verb: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _VerbEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[HTTPExtension._Verb.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + NONE: HTTPExtension._Verb.ValueType # 0 + GET: HTTPExtension._Verb.ValueType # 1 + HEAD: HTTPExtension._Verb.ValueType # 2 + POST: HTTPExtension._Verb.ValueType # 3 + PUT: HTTPExtension._Verb.ValueType # 4 + DELETE: HTTPExtension._Verb.ValueType # 5 + CONNECT: HTTPExtension._Verb.ValueType # 6 + OPTIONS: HTTPExtension._Verb.ValueType # 7 + TRACE: HTTPExtension._Verb.ValueType # 8 + PATCH: HTTPExtension._Verb.ValueType # 9 + + class Verb(_Verb, metaclass=_VerbEnumTypeWrapper): + """Type of HTTP 1.1 Methods + RFC 7231: https://tools.ietf.org/html/rfc7231#page-24 + RFC 5789: https://datatracker.ietf.org/doc/html/rfc5789 + """ + + NONE: HTTPExtension.Verb.ValueType # 0 + GET: HTTPExtension.Verb.ValueType # 1 + HEAD: HTTPExtension.Verb.ValueType # 2 + POST: HTTPExtension.Verb.ValueType # 3 + PUT: HTTPExtension.Verb.ValueType # 4 + DELETE: HTTPExtension.Verb.ValueType # 5 + CONNECT: HTTPExtension.Verb.ValueType # 6 + OPTIONS: HTTPExtension.Verb.ValueType # 7 + TRACE: HTTPExtension.Verb.ValueType # 8 + PATCH: HTTPExtension.Verb.ValueType # 9 + + VERB_FIELD_NUMBER: builtins.int + QUERYSTRING_FIELD_NUMBER: builtins.int + verb: global___HTTPExtension.Verb.ValueType + """Required. HTTP verb.""" + querystring: builtins.str + """Optional. querystring represents an encoded HTTP url query string in the following format: name=value&name2=value2""" + def __init__( + self, + *, + verb: global___HTTPExtension.Verb.ValueType = ..., + querystring: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["querystring", b"querystring", "verb", b"verb"]) -> None: ... + +global___HTTPExtension = HTTPExtension + +@typing_extensions.final +class InvokeRequest(google.protobuf.message.Message): + """InvokeRequest is the message to invoke a method with the data. + This message is used in InvokeService of Dapr gRPC Service and OnInvoke + of AppCallback gRPC service. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + METHOD_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + CONTENT_TYPE_FIELD_NUMBER: builtins.int + HTTP_EXTENSION_FIELD_NUMBER: builtins.int + method: builtins.str + """Required. method is a method name which will be invoked by caller.""" + @property + def data(self) -> google.protobuf.any_pb2.Any: + """Required in unary RPCs. Bytes value or Protobuf message which caller sent. + Dapr treats Any.value as bytes type if Any.type_url is unset. + """ + content_type: builtins.str + """The type of data content. + + This field is required if data delivers http request body + Otherwise, this is optional. + """ + @property + def http_extension(self) -> global___HTTPExtension: + """HTTP specific fields if request conveys http-compatible request. + + This field is required for http-compatible request. Otherwise, + this field is optional. + """ + def __init__( + self, + *, + method: builtins.str = ..., + data: google.protobuf.any_pb2.Any | None = ..., + content_type: builtins.str = ..., + http_extension: global___HTTPExtension | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["data", b"data", "http_extension", b"http_extension"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "data", b"data", "http_extension", b"http_extension", "method", b"method"]) -> None: ... + +global___InvokeRequest = InvokeRequest + +@typing_extensions.final +class InvokeResponse(google.protobuf.message.Message): + """InvokeResponse is the response message including data and its content type + from app callback. + This message is used in InvokeService of Dapr gRPC Service and OnInvoke + of AppCallback gRPC service. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DATA_FIELD_NUMBER: builtins.int + CONTENT_TYPE_FIELD_NUMBER: builtins.int + @property + def data(self) -> google.protobuf.any_pb2.Any: + """Required in unary RPCs. The content body of InvokeService response.""" + content_type: builtins.str + """Required. The type of data content.""" + def __init__( + self, + *, + data: google.protobuf.any_pb2.Any | None = ..., + content_type: builtins.str = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["data", b"data"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "data", b"data"]) -> None: ... + +global___InvokeResponse = InvokeResponse + +@typing_extensions.final +class StreamPayload(google.protobuf.message.Message): + """Chunk of data sent in a streaming request or response. + This is used in requests including InternalInvokeRequestStream. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DATA_FIELD_NUMBER: builtins.int + SEQ_FIELD_NUMBER: builtins.int + data: builtins.bytes + """Data sent in the chunk. + The amount of data included in each chunk is up to the discretion of the sender, and can be empty. + Additionally, the amount of data doesn't need to be fixed and subsequent messages can send more, or less, data. + Receivers must not make assumptions about the number of bytes they'll receive in each chunk. + """ + seq: builtins.int + """Sequence number. This is a counter that starts from 0 and increments by 1 on each chunk sent.""" + def __init__( + self, + *, + data: builtins.bytes = ..., + seq: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "seq", b"seq"]) -> None: ... + +global___StreamPayload = StreamPayload + +@typing_extensions.final +class StateItem(google.protobuf.message.Message): + """StateItem represents state key, value, and additional options to save state.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + ETAG_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + OPTIONS_FIELD_NUMBER: builtins.int + key: builtins.str + """Required. The state key""" + value: builtins.bytes + """Required. The state data for key""" + @property + def etag(self) -> global___Etag: + """The entity tag which represents the specific version of data. + The exact ETag format is defined by the corresponding data store. + """ + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be passed to state store component.""" + @property + def options(self) -> global___StateOptions: + """Options for concurrency and consistency to save the state.""" + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.bytes = ..., + etag: global___Etag | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + options: global___StateOptions | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["etag", b"etag", "options", b"options"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["etag", b"etag", "key", b"key", "metadata", b"metadata", "options", b"options", "value", b"value"]) -> None: ... + +global___StateItem = StateItem + +@typing_extensions.final +class Etag(google.protobuf.message.Message): + """Etag represents a state item version""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + VALUE_FIELD_NUMBER: builtins.int + value: builtins.str + """value sets the etag value""" + def __init__( + self, + *, + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["value", b"value"]) -> None: ... + +global___Etag = Etag + +@typing_extensions.final +class StateOptions(google.protobuf.message.Message): + """StateOptions configures concurrency and consistency for state operations""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _StateConcurrency: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StateConcurrencyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[StateOptions._StateConcurrency.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + CONCURRENCY_UNSPECIFIED: StateOptions._StateConcurrency.ValueType # 0 + CONCURRENCY_FIRST_WRITE: StateOptions._StateConcurrency.ValueType # 1 + CONCURRENCY_LAST_WRITE: StateOptions._StateConcurrency.ValueType # 2 + + class StateConcurrency(_StateConcurrency, metaclass=_StateConcurrencyEnumTypeWrapper): + """Enum describing the supported concurrency for state.""" + + CONCURRENCY_UNSPECIFIED: StateOptions.StateConcurrency.ValueType # 0 + CONCURRENCY_FIRST_WRITE: StateOptions.StateConcurrency.ValueType # 1 + CONCURRENCY_LAST_WRITE: StateOptions.StateConcurrency.ValueType # 2 + + class _StateConsistency: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StateConsistencyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[StateOptions._StateConsistency.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + CONSISTENCY_UNSPECIFIED: StateOptions._StateConsistency.ValueType # 0 + CONSISTENCY_EVENTUAL: StateOptions._StateConsistency.ValueType # 1 + CONSISTENCY_STRONG: StateOptions._StateConsistency.ValueType # 2 + + class StateConsistency(_StateConsistency, metaclass=_StateConsistencyEnumTypeWrapper): + """Enum describing the supported consistency for state.""" + + CONSISTENCY_UNSPECIFIED: StateOptions.StateConsistency.ValueType # 0 + CONSISTENCY_EVENTUAL: StateOptions.StateConsistency.ValueType # 1 + CONSISTENCY_STRONG: StateOptions.StateConsistency.ValueType # 2 + + CONCURRENCY_FIELD_NUMBER: builtins.int + CONSISTENCY_FIELD_NUMBER: builtins.int + concurrency: global___StateOptions.StateConcurrency.ValueType + consistency: global___StateOptions.StateConsistency.ValueType + def __init__( + self, + *, + concurrency: global___StateOptions.StateConcurrency.ValueType = ..., + consistency: global___StateOptions.StateConsistency.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["concurrency", b"concurrency", "consistency", b"consistency"]) -> None: ... + +global___StateOptions = StateOptions + +@typing_extensions.final +class ConfigurationItem(google.protobuf.message.Message): + """ConfigurationItem represents all the configuration with its name(key).""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + VALUE_FIELD_NUMBER: builtins.int + VERSION_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + value: builtins.str + """Required. The value of configuration item.""" + version: builtins.str + """Version is response only and cannot be fetched. Store is not expected to keep all versions available""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """the metadata which will be passed to/from configuration store component.""" + def __init__( + self, + *, + value: builtins.str = ..., + version: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "value", b"value", "version", b"version"]) -> None: ... + +global___ConfigurationItem = ConfigurationItem diff --git a/dapr/proto/common/v1/common_pb2_grpc.py b/dapr/proto/common/v1/common_pb2_grpc.py index 2daafffeb..910a4354e 100644 --- a/dapr/proto/common/v1/common_pb2_grpc.py +++ b/dapr/proto/common/v1/common_pb2_grpc.py @@ -1,4 +1,4 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/dapr/proto/runtime/__init__.py b/dapr/proto/runtime/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/proto/runtime/__init__.py +++ b/dapr/proto/runtime/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/proto/runtime/v1/__init__.py b/dapr/proto/runtime/v1/__init__.py index a47978853..3239faf3e 100644 --- a/dapr/proto/runtime/v1/__init__.py +++ b/dapr/proto/runtime/v1/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/dapr/proto/runtime/v1/appcallback_pb2.py b/dapr/proto/runtime/v1/appcallback_pb2.py index f661ff995..61f4c7477 100644 --- a/dapr/proto/runtime/v1/appcallback_pb2.py +++ b/dapr/proto/runtime/v1/appcallback_pb2.py @@ -1,91 +1,91 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: dapr/proto/runtime/v1/appcallback.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 -from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'dapr/proto/runtime/v1/appcallback.proto\x12\x15\x64\x61pr.proto.runtime.v1\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a!dapr/proto/common/v1/common.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xa6\x01\n\x0fJobEventRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x14\n\x0c\x63ontent_type\x18\x04 \x01(\t\x12;\n\x0ehttp_extension\x18\x05 \x01(\x0b\x32#.dapr.proto.common.v1.HTTPExtension\"\x12\n\x10JobEventResponse\"\xdb\x01\n\x11TopicEventRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x14\n\x0cspec_version\x18\x04 \x01(\t\x12\x19\n\x11\x64\x61ta_content_type\x18\x05 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x07 \x01(\x0c\x12\r\n\x05topic\x18\x06 \x01(\t\x12\x13\n\x0bpubsub_name\x18\x08 \x01(\t\x12\x0c\n\x04path\x18\t \x01(\t\x12+\n\nextensions\x18\n \x01(\x0b\x32\x17.google.protobuf.Struct\"\xa6\x01\n\x12TopicEventResponse\x12R\n\x06status\x18\x01 \x01(\x0e\x32\x42.dapr.proto.runtime.v1.TopicEventResponse.TopicEventResponseStatus\"<\n\x18TopicEventResponseStatus\x12\x0b\n\x07SUCCESS\x10\x00\x12\t\n\x05RETRY\x10\x01\x12\x08\n\x04\x44ROP\x10\x02\"\xab\x01\n\x13TopicEventCERequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x14\n\x0cspec_version\x18\x04 \x01(\t\x12\x19\n\x11\x64\x61ta_content_type\x18\x05 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x06 \x01(\x0c\x12+\n\nextensions\x18\x07 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xa5\x02\n\x1aTopicEventBulkRequestEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\x0f\n\x05\x62ytes\x18\x02 \x01(\x0cH\x00\x12\x41\n\x0b\x63loud_event\x18\x03 \x01(\x0b\x32*.dapr.proto.runtime.v1.TopicEventCERequestH\x00\x12\x14\n\x0c\x63ontent_type\x18\x04 \x01(\t\x12Q\n\x08metadata\x18\x05 \x03(\x0b\x32?.dapr.proto.runtime.v1.TopicEventBulkRequestEntry.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x07\n\x05\x65vent\"\xa6\x02\n\x15TopicEventBulkRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x42\n\x07\x65ntries\x18\x02 \x03(\x0b\x32\x31.dapr.proto.runtime.v1.TopicEventBulkRequestEntry\x12L\n\x08metadata\x18\x03 \x03(\x0b\x32:.dapr.proto.runtime.v1.TopicEventBulkRequest.MetadataEntry\x12\r\n\x05topic\x18\x04 \x01(\t\x12\x13\n\x0bpubsub_name\x18\x05 \x01(\t\x12\x0c\n\x04type\x18\x06 \x01(\t\x12\x0c\n\x04path\x18\x07 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x83\x01\n\x1bTopicEventBulkResponseEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12R\n\x06status\x18\x02 \x01(\x0e\x32\x42.dapr.proto.runtime.v1.TopicEventResponse.TopicEventResponseStatus\"^\n\x16TopicEventBulkResponse\x12\x44\n\x08statuses\x18\x01 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.TopicEventBulkResponseEntry\"\xae\x01\n\x13\x42indingEventRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12J\n\x08metadata\x18\x03 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.BindingEventRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x88\x02\n\x14\x42indingEventResponse\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12/\n\x06states\x18\x02 \x03(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\x12\n\n\x02to\x18\x03 \x03(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12X\n\x0b\x63oncurrency\x18\x05 \x01(\x0e\x32\x43.dapr.proto.runtime.v1.BindingEventResponse.BindingEventConcurrency\"7\n\x17\x42indingEventConcurrency\x12\x0e\n\nSEQUENTIAL\x10\x00\x12\x0c\n\x08PARALLEL\x10\x01\"a\n\x1eListTopicSubscriptionsResponse\x12?\n\rsubscriptions\x18\x01 \x03(\x0b\x32(.dapr.proto.runtime.v1.TopicSubscription\"\xc5\x02\n\x11TopicSubscription\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12H\n\x08metadata\x18\x03 \x03(\x0b\x32\x36.dapr.proto.runtime.v1.TopicSubscription.MetadataEntry\x12\x32\n\x06routes\x18\x05 \x01(\x0b\x32\".dapr.proto.runtime.v1.TopicRoutes\x12\x19\n\x11\x64\x65\x61\x64_letter_topic\x18\x06 \x01(\t\x12\x42\n\x0e\x62ulk_subscribe\x18\x07 \x01(\x0b\x32*.dapr.proto.runtime.v1.BulkSubscribeConfig\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"O\n\x0bTopicRoutes\x12/\n\x05rules\x18\x01 \x03(\x0b\x32 .dapr.proto.runtime.v1.TopicRule\x12\x0f\n\x07\x64\x65\x66\x61ult\x18\x02 \x01(\t\"(\n\tTopicRule\x12\r\n\x05match\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"a\n\x13\x42ulkSubscribeConfig\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x1a\n\x12max_messages_count\x18\x02 \x01(\x05\x12\x1d\n\x15max_await_duration_ms\x18\x03 \x01(\x05\"-\n\x19ListInputBindingsResponse\x12\x10\n\x08\x62indings\x18\x01 \x03(\t\"\x15\n\x13HealthCheckResponse2\x86\x04\n\x0b\x41ppCallback\x12W\n\x08OnInvoke\x12#.dapr.proto.common.v1.InvokeRequest\x1a$.dapr.proto.common.v1.InvokeResponse\"\x00\x12i\n\x16ListTopicSubscriptions\x12\x16.google.protobuf.Empty\x1a\x35.dapr.proto.runtime.v1.ListTopicSubscriptionsResponse\"\x00\x12\x65\n\x0cOnTopicEvent\x12(.dapr.proto.runtime.v1.TopicEventRequest\x1a).dapr.proto.runtime.v1.TopicEventResponse\"\x00\x12_\n\x11ListInputBindings\x12\x16.google.protobuf.Empty\x1a\x30.dapr.proto.runtime.v1.ListInputBindingsResponse\"\x00\x12k\n\x0eOnBindingEvent\x12*.dapr.proto.runtime.v1.BindingEventRequest\x1a+.dapr.proto.runtime.v1.BindingEventResponse\"\x00\x32m\n\x16\x41ppCallbackHealthCheck\x12S\n\x0bHealthCheck\x12\x16.google.protobuf.Empty\x1a*.dapr.proto.runtime.v1.HealthCheckResponse\"\x00\x32\xf0\x01\n\x10\x41ppCallbackAlpha\x12w\n\x16OnBulkTopicEventAlpha1\x12,.dapr.proto.runtime.v1.TopicEventBulkRequest\x1a-.dapr.proto.runtime.v1.TopicEventBulkResponse\"\x00\x12\x63\n\x10OnJobEventAlpha1\x12&.dapr.proto.runtime.v1.JobEventRequest\x1a\'.dapr.proto.runtime.v1.JobEventResponseBy\n\nio.dapr.v1B\x15\x44\x61prAppCallbackProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\xaa\x02 Dapr.AppCallback.Autogen.Grpc.v1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dapr.proto.runtime.v1.appcallback_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\nio.dapr.v1B\025DaprAppCallbackProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\252\002 Dapr.AppCallback.Autogen.Grpc.v1' - _TOPICEVENTBULKREQUESTENTRY_METADATAENTRY._options = None - _TOPICEVENTBULKREQUESTENTRY_METADATAENTRY._serialized_options = b'8\001' - _TOPICEVENTBULKREQUEST_METADATAENTRY._options = None - _TOPICEVENTBULKREQUEST_METADATAENTRY._serialized_options = b'8\001' - _BINDINGEVENTREQUEST_METADATAENTRY._options = None - _BINDINGEVENTREQUEST_METADATAENTRY._serialized_options = b'8\001' - _TOPICSUBSCRIPTION_METADATAENTRY._options = None - _TOPICSUBSCRIPTION_METADATAENTRY._serialized_options = b'8\001' - _globals['_JOBEVENTREQUEST']._serialized_start=188 - _globals['_JOBEVENTREQUEST']._serialized_end=354 - _globals['_JOBEVENTRESPONSE']._serialized_start=356 - _globals['_JOBEVENTRESPONSE']._serialized_end=374 - _globals['_TOPICEVENTREQUEST']._serialized_start=377 - _globals['_TOPICEVENTREQUEST']._serialized_end=596 - _globals['_TOPICEVENTRESPONSE']._serialized_start=599 - _globals['_TOPICEVENTRESPONSE']._serialized_end=765 - _globals['_TOPICEVENTRESPONSE_TOPICEVENTRESPONSESTATUS']._serialized_start=705 - _globals['_TOPICEVENTRESPONSE_TOPICEVENTRESPONSESTATUS']._serialized_end=765 - _globals['_TOPICEVENTCEREQUEST']._serialized_start=768 - _globals['_TOPICEVENTCEREQUEST']._serialized_end=939 - _globals['_TOPICEVENTBULKREQUESTENTRY']._serialized_start=942 - _globals['_TOPICEVENTBULKREQUESTENTRY']._serialized_end=1235 - _globals['_TOPICEVENTBULKREQUESTENTRY_METADATAENTRY']._serialized_start=1179 - _globals['_TOPICEVENTBULKREQUESTENTRY_METADATAENTRY']._serialized_end=1226 - _globals['_TOPICEVENTBULKREQUEST']._serialized_start=1238 - _globals['_TOPICEVENTBULKREQUEST']._serialized_end=1532 - _globals['_TOPICEVENTBULKREQUEST_METADATAENTRY']._serialized_start=1179 - _globals['_TOPICEVENTBULKREQUEST_METADATAENTRY']._serialized_end=1226 - _globals['_TOPICEVENTBULKRESPONSEENTRY']._serialized_start=1535 - _globals['_TOPICEVENTBULKRESPONSEENTRY']._serialized_end=1666 - _globals['_TOPICEVENTBULKRESPONSE']._serialized_start=1668 - _globals['_TOPICEVENTBULKRESPONSE']._serialized_end=1762 - _globals['_BINDINGEVENTREQUEST']._serialized_start=1765 - _globals['_BINDINGEVENTREQUEST']._serialized_end=1939 - _globals['_BINDINGEVENTREQUEST_METADATAENTRY']._serialized_start=1179 - _globals['_BINDINGEVENTREQUEST_METADATAENTRY']._serialized_end=1226 - _globals['_BINDINGEVENTRESPONSE']._serialized_start=1942 - _globals['_BINDINGEVENTRESPONSE']._serialized_end=2206 - _globals['_BINDINGEVENTRESPONSE_BINDINGEVENTCONCURRENCY']._serialized_start=2151 - _globals['_BINDINGEVENTRESPONSE_BINDINGEVENTCONCURRENCY']._serialized_end=2206 - _globals['_LISTTOPICSUBSCRIPTIONSRESPONSE']._serialized_start=2208 - _globals['_LISTTOPICSUBSCRIPTIONSRESPONSE']._serialized_end=2305 - _globals['_TOPICSUBSCRIPTION']._serialized_start=2308 - _globals['_TOPICSUBSCRIPTION']._serialized_end=2633 - _globals['_TOPICSUBSCRIPTION_METADATAENTRY']._serialized_start=1179 - _globals['_TOPICSUBSCRIPTION_METADATAENTRY']._serialized_end=1226 - _globals['_TOPICROUTES']._serialized_start=2635 - _globals['_TOPICROUTES']._serialized_end=2714 - _globals['_TOPICRULE']._serialized_start=2716 - _globals['_TOPICRULE']._serialized_end=2756 - _globals['_BULKSUBSCRIBECONFIG']._serialized_start=2758 - _globals['_BULKSUBSCRIBECONFIG']._serialized_end=2855 - _globals['_LISTINPUTBINDINGSRESPONSE']._serialized_start=2857 - _globals['_LISTINPUTBINDINGSRESPONSE']._serialized_end=2902 - _globals['_HEALTHCHECKRESPONSE']._serialized_start=2904 - _globals['_HEALTHCHECKRESPONSE']._serialized_end=2925 - _globals['_APPCALLBACK']._serialized_start=2928 - _globals['_APPCALLBACK']._serialized_end=3446 - _globals['_APPCALLBACKHEALTHCHECK']._serialized_start=3448 - _globals['_APPCALLBACKHEALTHCHECK']._serialized_end=3557 - _globals['_APPCALLBACKALPHA']._serialized_start=3560 - _globals['_APPCALLBACKALPHA']._serialized_end=3800 -# @@protoc_insertion_point(module_scope) +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: dapr/proto/runtime/v1/appcallback.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 +from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'dapr/proto/runtime/v1/appcallback.proto\x12\x15\x64\x61pr.proto.runtime.v1\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a!dapr/proto/common/v1/common.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xa6\x01\n\x0fJobEventRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x04\x64\x61ta\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x14\n\x0c\x63ontent_type\x18\x04 \x01(\t\x12;\n\x0ehttp_extension\x18\x05 \x01(\x0b\x32#.dapr.proto.common.v1.HTTPExtension\"\x12\n\x10JobEventResponse\"\xdb\x01\n\x11TopicEventRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x14\n\x0cspec_version\x18\x04 \x01(\t\x12\x19\n\x11\x64\x61ta_content_type\x18\x05 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x07 \x01(\x0c\x12\r\n\x05topic\x18\x06 \x01(\t\x12\x13\n\x0bpubsub_name\x18\x08 \x01(\t\x12\x0c\n\x04path\x18\t \x01(\t\x12+\n\nextensions\x18\n \x01(\x0b\x32\x17.google.protobuf.Struct\"\xa6\x01\n\x12TopicEventResponse\x12R\n\x06status\x18\x01 \x01(\x0e\x32\x42.dapr.proto.runtime.v1.TopicEventResponse.TopicEventResponseStatus\"<\n\x18TopicEventResponseStatus\x12\x0b\n\x07SUCCESS\x10\x00\x12\t\n\x05RETRY\x10\x01\x12\x08\n\x04\x44ROP\x10\x02\"\xab\x01\n\x13TopicEventCERequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x14\n\x0cspec_version\x18\x04 \x01(\t\x12\x19\n\x11\x64\x61ta_content_type\x18\x05 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x06 \x01(\x0c\x12+\n\nextensions\x18\x07 \x01(\x0b\x32\x17.google.protobuf.Struct\"\xa5\x02\n\x1aTopicEventBulkRequestEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\x0f\n\x05\x62ytes\x18\x02 \x01(\x0cH\x00\x12\x41\n\x0b\x63loud_event\x18\x03 \x01(\x0b\x32*.dapr.proto.runtime.v1.TopicEventCERequestH\x00\x12\x14\n\x0c\x63ontent_type\x18\x04 \x01(\t\x12Q\n\x08metadata\x18\x05 \x03(\x0b\x32?.dapr.proto.runtime.v1.TopicEventBulkRequestEntry.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x07\n\x05\x65vent\"\xa6\x02\n\x15TopicEventBulkRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x42\n\x07\x65ntries\x18\x02 \x03(\x0b\x32\x31.dapr.proto.runtime.v1.TopicEventBulkRequestEntry\x12L\n\x08metadata\x18\x03 \x03(\x0b\x32:.dapr.proto.runtime.v1.TopicEventBulkRequest.MetadataEntry\x12\r\n\x05topic\x18\x04 \x01(\t\x12\x13\n\x0bpubsub_name\x18\x05 \x01(\t\x12\x0c\n\x04type\x18\x06 \x01(\t\x12\x0c\n\x04path\x18\x07 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x83\x01\n\x1bTopicEventBulkResponseEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12R\n\x06status\x18\x02 \x01(\x0e\x32\x42.dapr.proto.runtime.v1.TopicEventResponse.TopicEventResponseStatus\"^\n\x16TopicEventBulkResponse\x12\x44\n\x08statuses\x18\x01 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.TopicEventBulkResponseEntry\"\xae\x01\n\x13\x42indingEventRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12J\n\x08metadata\x18\x03 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.BindingEventRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x88\x02\n\x14\x42indingEventResponse\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12/\n\x06states\x18\x02 \x03(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\x12\n\n\x02to\x18\x03 \x03(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12X\n\x0b\x63oncurrency\x18\x05 \x01(\x0e\x32\x43.dapr.proto.runtime.v1.BindingEventResponse.BindingEventConcurrency\"7\n\x17\x42indingEventConcurrency\x12\x0e\n\nSEQUENTIAL\x10\x00\x12\x0c\n\x08PARALLEL\x10\x01\"a\n\x1eListTopicSubscriptionsResponse\x12?\n\rsubscriptions\x18\x01 \x03(\x0b\x32(.dapr.proto.runtime.v1.TopicSubscription\"\xc5\x02\n\x11TopicSubscription\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12H\n\x08metadata\x18\x03 \x03(\x0b\x32\x36.dapr.proto.runtime.v1.TopicSubscription.MetadataEntry\x12\x32\n\x06routes\x18\x05 \x01(\x0b\x32\".dapr.proto.runtime.v1.TopicRoutes\x12\x19\n\x11\x64\x65\x61\x64_letter_topic\x18\x06 \x01(\t\x12\x42\n\x0e\x62ulk_subscribe\x18\x07 \x01(\x0b\x32*.dapr.proto.runtime.v1.BulkSubscribeConfig\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"O\n\x0bTopicRoutes\x12/\n\x05rules\x18\x01 \x03(\x0b\x32 .dapr.proto.runtime.v1.TopicRule\x12\x0f\n\x07\x64\x65\x66\x61ult\x18\x02 \x01(\t\"(\n\tTopicRule\x12\r\n\x05match\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"a\n\x13\x42ulkSubscribeConfig\x12\x0f\n\x07\x65nabled\x18\x01 \x01(\x08\x12\x1a\n\x12max_messages_count\x18\x02 \x01(\x05\x12\x1d\n\x15max_await_duration_ms\x18\x03 \x01(\x05\"-\n\x19ListInputBindingsResponse\x12\x10\n\x08\x62indings\x18\x01 \x03(\t\"\x15\n\x13HealthCheckResponse2\x86\x04\n\x0b\x41ppCallback\x12W\n\x08OnInvoke\x12#.dapr.proto.common.v1.InvokeRequest\x1a$.dapr.proto.common.v1.InvokeResponse\"\x00\x12i\n\x16ListTopicSubscriptions\x12\x16.google.protobuf.Empty\x1a\x35.dapr.proto.runtime.v1.ListTopicSubscriptionsResponse\"\x00\x12\x65\n\x0cOnTopicEvent\x12(.dapr.proto.runtime.v1.TopicEventRequest\x1a).dapr.proto.runtime.v1.TopicEventResponse\"\x00\x12_\n\x11ListInputBindings\x12\x16.google.protobuf.Empty\x1a\x30.dapr.proto.runtime.v1.ListInputBindingsResponse\"\x00\x12k\n\x0eOnBindingEvent\x12*.dapr.proto.runtime.v1.BindingEventRequest\x1a+.dapr.proto.runtime.v1.BindingEventResponse\"\x00\x32m\n\x16\x41ppCallbackHealthCheck\x12S\n\x0bHealthCheck\x12\x16.google.protobuf.Empty\x1a*.dapr.proto.runtime.v1.HealthCheckResponse\"\x00\x32\xf0\x01\n\x10\x41ppCallbackAlpha\x12w\n\x16OnBulkTopicEventAlpha1\x12,.dapr.proto.runtime.v1.TopicEventBulkRequest\x1a-.dapr.proto.runtime.v1.TopicEventBulkResponse\"\x00\x12\x63\n\x10OnJobEventAlpha1\x12&.dapr.proto.runtime.v1.JobEventRequest\x1a\'.dapr.proto.runtime.v1.JobEventResponseBy\n\nio.dapr.v1B\x15\x44\x61prAppCallbackProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\xaa\x02 Dapr.AppCallback.Autogen.Grpc.v1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dapr.proto.runtime.v1.appcallback_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\nio.dapr.v1B\025DaprAppCallbackProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\252\002 Dapr.AppCallback.Autogen.Grpc.v1' + _TOPICEVENTBULKREQUESTENTRY_METADATAENTRY._options = None + _TOPICEVENTBULKREQUESTENTRY_METADATAENTRY._serialized_options = b'8\001' + _TOPICEVENTBULKREQUEST_METADATAENTRY._options = None + _TOPICEVENTBULKREQUEST_METADATAENTRY._serialized_options = b'8\001' + _BINDINGEVENTREQUEST_METADATAENTRY._options = None + _BINDINGEVENTREQUEST_METADATAENTRY._serialized_options = b'8\001' + _TOPICSUBSCRIPTION_METADATAENTRY._options = None + _TOPICSUBSCRIPTION_METADATAENTRY._serialized_options = b'8\001' + _globals['_JOBEVENTREQUEST']._serialized_start=188 + _globals['_JOBEVENTREQUEST']._serialized_end=354 + _globals['_JOBEVENTRESPONSE']._serialized_start=356 + _globals['_JOBEVENTRESPONSE']._serialized_end=374 + _globals['_TOPICEVENTREQUEST']._serialized_start=377 + _globals['_TOPICEVENTREQUEST']._serialized_end=596 + _globals['_TOPICEVENTRESPONSE']._serialized_start=599 + _globals['_TOPICEVENTRESPONSE']._serialized_end=765 + _globals['_TOPICEVENTRESPONSE_TOPICEVENTRESPONSESTATUS']._serialized_start=705 + _globals['_TOPICEVENTRESPONSE_TOPICEVENTRESPONSESTATUS']._serialized_end=765 + _globals['_TOPICEVENTCEREQUEST']._serialized_start=768 + _globals['_TOPICEVENTCEREQUEST']._serialized_end=939 + _globals['_TOPICEVENTBULKREQUESTENTRY']._serialized_start=942 + _globals['_TOPICEVENTBULKREQUESTENTRY']._serialized_end=1235 + _globals['_TOPICEVENTBULKREQUESTENTRY_METADATAENTRY']._serialized_start=1179 + _globals['_TOPICEVENTBULKREQUESTENTRY_METADATAENTRY']._serialized_end=1226 + _globals['_TOPICEVENTBULKREQUEST']._serialized_start=1238 + _globals['_TOPICEVENTBULKREQUEST']._serialized_end=1532 + _globals['_TOPICEVENTBULKREQUEST_METADATAENTRY']._serialized_start=1179 + _globals['_TOPICEVENTBULKREQUEST_METADATAENTRY']._serialized_end=1226 + _globals['_TOPICEVENTBULKRESPONSEENTRY']._serialized_start=1535 + _globals['_TOPICEVENTBULKRESPONSEENTRY']._serialized_end=1666 + _globals['_TOPICEVENTBULKRESPONSE']._serialized_start=1668 + _globals['_TOPICEVENTBULKRESPONSE']._serialized_end=1762 + _globals['_BINDINGEVENTREQUEST']._serialized_start=1765 + _globals['_BINDINGEVENTREQUEST']._serialized_end=1939 + _globals['_BINDINGEVENTREQUEST_METADATAENTRY']._serialized_start=1179 + _globals['_BINDINGEVENTREQUEST_METADATAENTRY']._serialized_end=1226 + _globals['_BINDINGEVENTRESPONSE']._serialized_start=1942 + _globals['_BINDINGEVENTRESPONSE']._serialized_end=2206 + _globals['_BINDINGEVENTRESPONSE_BINDINGEVENTCONCURRENCY']._serialized_start=2151 + _globals['_BINDINGEVENTRESPONSE_BINDINGEVENTCONCURRENCY']._serialized_end=2206 + _globals['_LISTTOPICSUBSCRIPTIONSRESPONSE']._serialized_start=2208 + _globals['_LISTTOPICSUBSCRIPTIONSRESPONSE']._serialized_end=2305 + _globals['_TOPICSUBSCRIPTION']._serialized_start=2308 + _globals['_TOPICSUBSCRIPTION']._serialized_end=2633 + _globals['_TOPICSUBSCRIPTION_METADATAENTRY']._serialized_start=1179 + _globals['_TOPICSUBSCRIPTION_METADATAENTRY']._serialized_end=1226 + _globals['_TOPICROUTES']._serialized_start=2635 + _globals['_TOPICROUTES']._serialized_end=2714 + _globals['_TOPICRULE']._serialized_start=2716 + _globals['_TOPICRULE']._serialized_end=2756 + _globals['_BULKSUBSCRIBECONFIG']._serialized_start=2758 + _globals['_BULKSUBSCRIBECONFIG']._serialized_end=2855 + _globals['_LISTINPUTBINDINGSRESPONSE']._serialized_start=2857 + _globals['_LISTINPUTBINDINGSRESPONSE']._serialized_end=2902 + _globals['_HEALTHCHECKRESPONSE']._serialized_start=2904 + _globals['_HEALTHCHECKRESPONSE']._serialized_end=2925 + _globals['_APPCALLBACK']._serialized_start=2928 + _globals['_APPCALLBACK']._serialized_end=3446 + _globals['_APPCALLBACKHEALTHCHECK']._serialized_start=3448 + _globals['_APPCALLBACKHEALTHCHECK']._serialized_end=3557 + _globals['_APPCALLBACKALPHA']._serialized_start=3560 + _globals['_APPCALLBACKALPHA']._serialized_end=3800 +# @@protoc_insertion_point(module_scope) diff --git a/dapr/proto/runtime/v1/appcallback_pb2.pyi b/dapr/proto/runtime/v1/appcallback_pb2.pyi index b302559fe..86794caed 100644 --- a/dapr/proto/runtime/v1/appcallback_pb2.pyi +++ b/dapr/proto/runtime/v1/appcallback_pb2.pyi @@ -1,685 +1,685 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file - -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import dapr.proto.common.v1.common_pb2 -import google.protobuf.any_pb2 -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import google.protobuf.struct_pb2 -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -@typing_extensions.final -class JobEventRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - METHOD_FIELD_NUMBER: builtins.int - CONTENT_TYPE_FIELD_NUMBER: builtins.int - HTTP_EXTENSION_FIELD_NUMBER: builtins.int - name: builtins.str - """Job name.""" - @property - def data(self) -> google.protobuf.any_pb2.Any: - """Job data to be sent back to app.""" - method: builtins.str - """Required. method is a method name which will be invoked by caller.""" - content_type: builtins.str - """The type of data content. - - This field is required if data delivers http request body - Otherwise, this is optional. - """ - @property - def http_extension(self) -> dapr.proto.common.v1.common_pb2.HTTPExtension: - """HTTP specific fields if request conveys http-compatible request. - - This field is required for http-compatible request. Otherwise, - this field is optional. - """ - def __init__( - self, - *, - name: builtins.str = ..., - data: google.protobuf.any_pb2.Any | None = ..., - method: builtins.str = ..., - content_type: builtins.str = ..., - http_extension: dapr.proto.common.v1.common_pb2.HTTPExtension | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["data", b"data", "http_extension", b"http_extension"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "data", b"data", "http_extension", b"http_extension", "method", b"method", "name", b"name"]) -> None: ... - -global___JobEventRequest = JobEventRequest - -@typing_extensions.final -class JobEventResponse(google.protobuf.message.Message): - """JobEventResponse is the response from the app when a job is triggered.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___JobEventResponse = JobEventResponse - -@typing_extensions.final -class TopicEventRequest(google.protobuf.message.Message): - """TopicEventRequest message is compatible with CloudEvent spec v1.0 - https://github.com/cloudevents/spec/blob/v1.0/spec.md - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - SOURCE_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - SPEC_VERSION_FIELD_NUMBER: builtins.int - DATA_CONTENT_TYPE_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - PUBSUB_NAME_FIELD_NUMBER: builtins.int - PATH_FIELD_NUMBER: builtins.int - EXTENSIONS_FIELD_NUMBER: builtins.int - id: builtins.str - """id identifies the event. Producers MUST ensure that source + id - is unique for each distinct event. If a duplicate event is re-sent - (e.g. due to a network error) it MAY have the same id. - """ - source: builtins.str - """source identifies the context in which an event happened. - Often this will include information such as the type of the - event source, the organization publishing the event or the process - that produced the event. The exact syntax and semantics behind - the data encoded in the URI is defined by the event producer. - """ - type: builtins.str - """The type of event related to the originating occurrence.""" - spec_version: builtins.str - """The version of the CloudEvents specification.""" - data_content_type: builtins.str - """The content type of data value.""" - data: builtins.bytes - """The content of the event.""" - topic: builtins.str - """The pubsub topic which publisher sent to.""" - pubsub_name: builtins.str - """The name of the pubsub the publisher sent to.""" - path: builtins.str - """The matching path from TopicSubscription/routes (if specified) for this event. - This value is used by OnTopicEvent to "switch" inside the handler. - """ - @property - def extensions(self) -> google.protobuf.struct_pb2.Struct: - """The map of additional custom properties to be sent to the app. These are considered to be cloud event extensions.""" - def __init__( - self, - *, - id: builtins.str = ..., - source: builtins.str = ..., - type: builtins.str = ..., - spec_version: builtins.str = ..., - data_content_type: builtins.str = ..., - data: builtins.bytes = ..., - topic: builtins.str = ..., - pubsub_name: builtins.str = ..., - path: builtins.str = ..., - extensions: google.protobuf.struct_pb2.Struct | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["extensions", b"extensions"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "data_content_type", b"data_content_type", "extensions", b"extensions", "id", b"id", "path", b"path", "pubsub_name", b"pubsub_name", "source", b"source", "spec_version", b"spec_version", "topic", b"topic", "type", b"type"]) -> None: ... - -global___TopicEventRequest = TopicEventRequest - -@typing_extensions.final -class TopicEventResponse(google.protobuf.message.Message): - """TopicEventResponse is response from app on published message""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _TopicEventResponseStatus: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _TopicEventResponseStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[TopicEventResponse._TopicEventResponseStatus.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SUCCESS: TopicEventResponse._TopicEventResponseStatus.ValueType # 0 - """SUCCESS is the default behavior: message is acknowledged and not retried or logged.""" - RETRY: TopicEventResponse._TopicEventResponseStatus.ValueType # 1 - """RETRY status signals Dapr to retry the message as part of an expected scenario (no warning is logged).""" - DROP: TopicEventResponse._TopicEventResponseStatus.ValueType # 2 - """DROP status signals Dapr to drop the message as part of an unexpected scenario (warning is logged).""" - - class TopicEventResponseStatus(_TopicEventResponseStatus, metaclass=_TopicEventResponseStatusEnumTypeWrapper): - """TopicEventResponseStatus allows apps to have finer control over handling of the message.""" - - SUCCESS: TopicEventResponse.TopicEventResponseStatus.ValueType # 0 - """SUCCESS is the default behavior: message is acknowledged and not retried or logged.""" - RETRY: TopicEventResponse.TopicEventResponseStatus.ValueType # 1 - """RETRY status signals Dapr to retry the message as part of an expected scenario (no warning is logged).""" - DROP: TopicEventResponse.TopicEventResponseStatus.ValueType # 2 - """DROP status signals Dapr to drop the message as part of an unexpected scenario (warning is logged).""" - - STATUS_FIELD_NUMBER: builtins.int - status: global___TopicEventResponse.TopicEventResponseStatus.ValueType - """The list of output bindings.""" - def __init__( - self, - *, - status: global___TopicEventResponse.TopicEventResponseStatus.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["status", b"status"]) -> None: ... - -global___TopicEventResponse = TopicEventResponse - -@typing_extensions.final -class TopicEventCERequest(google.protobuf.message.Message): - """TopicEventCERequest message is compatible with CloudEvent spec v1.0""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - SOURCE_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - SPEC_VERSION_FIELD_NUMBER: builtins.int - DATA_CONTENT_TYPE_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - EXTENSIONS_FIELD_NUMBER: builtins.int - id: builtins.str - """The unique identifier of this cloud event.""" - source: builtins.str - """source identifies the context in which an event happened.""" - type: builtins.str - """The type of event related to the originating occurrence.""" - spec_version: builtins.str - """The version of the CloudEvents specification.""" - data_content_type: builtins.str - """The content type of data value.""" - data: builtins.bytes - """The content of the event.""" - @property - def extensions(self) -> google.protobuf.struct_pb2.Struct: - """Custom attributes which includes cloud event extensions.""" - def __init__( - self, - *, - id: builtins.str = ..., - source: builtins.str = ..., - type: builtins.str = ..., - spec_version: builtins.str = ..., - data_content_type: builtins.str = ..., - data: builtins.bytes = ..., - extensions: google.protobuf.struct_pb2.Struct | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["extensions", b"extensions"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "data_content_type", b"data_content_type", "extensions", b"extensions", "id", b"id", "source", b"source", "spec_version", b"spec_version", "type", b"type"]) -> None: ... - -global___TopicEventCERequest = TopicEventCERequest - -@typing_extensions.final -class TopicEventBulkRequestEntry(google.protobuf.message.Message): - """TopicEventBulkRequestEntry represents a single message inside a bulk request""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ENTRY_ID_FIELD_NUMBER: builtins.int - BYTES_FIELD_NUMBER: builtins.int - CLOUD_EVENT_FIELD_NUMBER: builtins.int - CONTENT_TYPE_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - entry_id: builtins.str - """Unique identifier for the message.""" - bytes: builtins.bytes - @property - def cloud_event(self) -> global___TopicEventCERequest: ... - content_type: builtins.str - """content type of the event contained.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata associated with the event.""" - def __init__( - self, - *, - entry_id: builtins.str = ..., - bytes: builtins.bytes = ..., - cloud_event: global___TopicEventCERequest | None = ..., - content_type: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["bytes", b"bytes", "cloud_event", b"cloud_event", "event", b"event"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["bytes", b"bytes", "cloud_event", b"cloud_event", "content_type", b"content_type", "entry_id", b"entry_id", "event", b"event", "metadata", b"metadata"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["event", b"event"]) -> typing_extensions.Literal["bytes", "cloud_event"] | None: ... - -global___TopicEventBulkRequestEntry = TopicEventBulkRequestEntry - -@typing_extensions.final -class TopicEventBulkRequest(google.protobuf.message.Message): - """TopicEventBulkRequest represents request for bulk message""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ID_FIELD_NUMBER: builtins.int - ENTRIES_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - PUBSUB_NAME_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - PATH_FIELD_NUMBER: builtins.int - id: builtins.str - """Unique identifier for the bulk request.""" - @property - def entries(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicEventBulkRequestEntry]: - """The list of items inside this bulk request.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata associated with the this bulk request.""" - topic: builtins.str - """The pubsub topic which publisher sent to.""" - pubsub_name: builtins.str - """The name of the pubsub the publisher sent to.""" - type: builtins.str - """The type of event related to the originating occurrence.""" - path: builtins.str - """The matching path from TopicSubscription/routes (if specified) for this event. - This value is used by OnTopicEvent to "switch" inside the handler. - """ - def __init__( - self, - *, - id: builtins.str = ..., - entries: collections.abc.Iterable[global___TopicEventBulkRequestEntry] | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - topic: builtins.str = ..., - pubsub_name: builtins.str = ..., - type: builtins.str = ..., - path: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["entries", b"entries", "id", b"id", "metadata", b"metadata", "path", b"path", "pubsub_name", b"pubsub_name", "topic", b"topic", "type", b"type"]) -> None: ... - -global___TopicEventBulkRequest = TopicEventBulkRequest - -@typing_extensions.final -class TopicEventBulkResponseEntry(google.protobuf.message.Message): - """TopicEventBulkResponseEntry Represents single response, as part of TopicEventBulkResponse, to be - sent by subscibed App for the corresponding single message during bulk subscribe - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ENTRY_ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - entry_id: builtins.str - """Unique identifier associated the message.""" - status: global___TopicEventResponse.TopicEventResponseStatus.ValueType - """The status of the response.""" - def __init__( - self, - *, - entry_id: builtins.str = ..., - status: global___TopicEventResponse.TopicEventResponseStatus.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["entry_id", b"entry_id", "status", b"status"]) -> None: ... - -global___TopicEventBulkResponseEntry = TopicEventBulkResponseEntry - -@typing_extensions.final -class TopicEventBulkResponse(google.protobuf.message.Message): - """AppBulkResponse is response from app on published message""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STATUSES_FIELD_NUMBER: builtins.int - @property - def statuses(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicEventBulkResponseEntry]: - """The list of all responses for the bulk request.""" - def __init__( - self, - *, - statuses: collections.abc.Iterable[global___TopicEventBulkResponseEntry] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["statuses", b"statuses"]) -> None: ... - -global___TopicEventBulkResponse = TopicEventBulkResponse - -@typing_extensions.final -class BindingEventRequest(google.protobuf.message.Message): - """BindingEventRequest represents input bindings event.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - NAME_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - name: builtins.str - """Required. The name of the input binding component.""" - data: builtins.bytes - """Required. The payload that the input bindings sent""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata set by the input binging components.""" - def __init__( - self, - *, - name: builtins.str = ..., - data: builtins.bytes = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata", "name", b"name"]) -> None: ... - -global___BindingEventRequest = BindingEventRequest - -@typing_extensions.final -class BindingEventResponse(google.protobuf.message.Message): - """BindingEventResponse includes operations to save state or - send data to output bindings optionally. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _BindingEventConcurrency: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _BindingEventConcurrencyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BindingEventResponse._BindingEventConcurrency.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SEQUENTIAL: BindingEventResponse._BindingEventConcurrency.ValueType # 0 - """SEQUENTIAL sends data to output bindings specified in "to" sequentially.""" - PARALLEL: BindingEventResponse._BindingEventConcurrency.ValueType # 1 - """PARALLEL sends data to output bindings specified in "to" in parallel.""" - - class BindingEventConcurrency(_BindingEventConcurrency, metaclass=_BindingEventConcurrencyEnumTypeWrapper): - """BindingEventConcurrency is the kind of concurrency""" - - SEQUENTIAL: BindingEventResponse.BindingEventConcurrency.ValueType # 0 - """SEQUENTIAL sends data to output bindings specified in "to" sequentially.""" - PARALLEL: BindingEventResponse.BindingEventConcurrency.ValueType # 1 - """PARALLEL sends data to output bindings specified in "to" in parallel.""" - - STORE_NAME_FIELD_NUMBER: builtins.int - STATES_FIELD_NUMBER: builtins.int - TO_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - CONCURRENCY_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store where states are saved.""" - @property - def states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[dapr.proto.common.v1.common_pb2.StateItem]: - """The state key values which will be stored in store_name.""" - @property - def to(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """The list of output bindings.""" - data: builtins.bytes - """The content which will be sent to "to" output bindings.""" - concurrency: global___BindingEventResponse.BindingEventConcurrency.ValueType - """The concurrency of output bindings to send data to - "to" output bindings list. The default is SEQUENTIAL. - """ - def __init__( - self, - *, - store_name: builtins.str = ..., - states: collections.abc.Iterable[dapr.proto.common.v1.common_pb2.StateItem] | None = ..., - to: collections.abc.Iterable[builtins.str] | None = ..., - data: builtins.bytes = ..., - concurrency: global___BindingEventResponse.BindingEventConcurrency.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["concurrency", b"concurrency", "data", b"data", "states", b"states", "store_name", b"store_name", "to", b"to"]) -> None: ... - -global___BindingEventResponse = BindingEventResponse - -@typing_extensions.final -class ListTopicSubscriptionsResponse(google.protobuf.message.Message): - """ListTopicSubscriptionsResponse is the message including the list of the subscribing topics.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUBSCRIPTIONS_FIELD_NUMBER: builtins.int - @property - def subscriptions(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicSubscription]: - """The list of topics.""" - def __init__( - self, - *, - subscriptions: collections.abc.Iterable[global___TopicSubscription] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["subscriptions", b"subscriptions"]) -> None: ... - -global___ListTopicSubscriptionsResponse = ListTopicSubscriptionsResponse - -@typing_extensions.final -class TopicSubscription(google.protobuf.message.Message): - """TopicSubscription represents topic and metadata.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - PUBSUB_NAME_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - ROUTES_FIELD_NUMBER: builtins.int - DEAD_LETTER_TOPIC_FIELD_NUMBER: builtins.int - BULK_SUBSCRIBE_FIELD_NUMBER: builtins.int - pubsub_name: builtins.str - """Required. The name of the pubsub containing the topic below to subscribe to.""" - topic: builtins.str - """Required. The name of topic which will be subscribed""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The optional properties used for this topic's subscription e.g. session id""" - @property - def routes(self) -> global___TopicRoutes: - """The optional routing rules to match against. In the gRPC interface, OnTopicEvent - is still invoked but the matching path is sent in the TopicEventRequest. - """ - dead_letter_topic: builtins.str - """The optional dead letter queue for this topic to send events to.""" - @property - def bulk_subscribe(self) -> global___BulkSubscribeConfig: - """The optional bulk subscribe settings for this topic.""" - def __init__( - self, - *, - pubsub_name: builtins.str = ..., - topic: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - routes: global___TopicRoutes | None = ..., - dead_letter_topic: builtins.str = ..., - bulk_subscribe: global___BulkSubscribeConfig | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["bulk_subscribe", b"bulk_subscribe", "routes", b"routes"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["bulk_subscribe", b"bulk_subscribe", "dead_letter_topic", b"dead_letter_topic", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "routes", b"routes", "topic", b"topic"]) -> None: ... - -global___TopicSubscription = TopicSubscription - -@typing_extensions.final -class TopicRoutes(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RULES_FIELD_NUMBER: builtins.int - DEFAULT_FIELD_NUMBER: builtins.int - @property - def rules(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicRule]: - """The list of rules for this topic.""" - default: builtins.str - """The default path for this topic.""" - def __init__( - self, - *, - rules: collections.abc.Iterable[global___TopicRule] | None = ..., - default: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["default", b"default", "rules", b"rules"]) -> None: ... - -global___TopicRoutes = TopicRoutes - -@typing_extensions.final -class TopicRule(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MATCH_FIELD_NUMBER: builtins.int - PATH_FIELD_NUMBER: builtins.int - match: builtins.str - """The optional CEL expression used to match the event. - If the match is not specified, then the route is considered - the default. - """ - path: builtins.str - """The path used to identify matches for this subscription. - This value is passed in TopicEventRequest and used by OnTopicEvent to "switch" - inside the handler. - """ - def __init__( - self, - *, - match: builtins.str = ..., - path: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["match", b"match", "path", b"path"]) -> None: ... - -global___TopicRule = TopicRule - -@typing_extensions.final -class BulkSubscribeConfig(google.protobuf.message.Message): - """BulkSubscribeConfig is the message to pass settings for bulk subscribe""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ENABLED_FIELD_NUMBER: builtins.int - MAX_MESSAGES_COUNT_FIELD_NUMBER: builtins.int - MAX_AWAIT_DURATION_MS_FIELD_NUMBER: builtins.int - enabled: builtins.bool - """Required. Flag to enable/disable bulk subscribe""" - max_messages_count: builtins.int - """Optional. Max number of messages to be sent in a single bulk request""" - max_await_duration_ms: builtins.int - """Optional. Max duration to wait for messages to be sent in a single bulk request""" - def __init__( - self, - *, - enabled: builtins.bool = ..., - max_messages_count: builtins.int = ..., - max_await_duration_ms: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["enabled", b"enabled", "max_await_duration_ms", b"max_await_duration_ms", "max_messages_count", b"max_messages_count"]) -> None: ... - -global___BulkSubscribeConfig = BulkSubscribeConfig - -@typing_extensions.final -class ListInputBindingsResponse(google.protobuf.message.Message): - """ListInputBindingsResponse is the message including the list of input bindings.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - BINDINGS_FIELD_NUMBER: builtins.int - @property - def bindings(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """The list of input bindings.""" - def __init__( - self, - *, - bindings: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["bindings", b"bindings"]) -> None: ... - -global___ListInputBindingsResponse = ListInputBindingsResponse - -@typing_extensions.final -class HealthCheckResponse(google.protobuf.message.Message): - """HealthCheckResponse is the message with the response to the health check. - This message is currently empty as used as placeholder. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___HealthCheckResponse = HealthCheckResponse +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file + +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import builtins +import collections.abc +import dapr.proto.common.v1.common_pb2 +import google.protobuf.any_pb2 +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import google.protobuf.struct_pb2 +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +@typing_extensions.final +class JobEventRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + METHOD_FIELD_NUMBER: builtins.int + CONTENT_TYPE_FIELD_NUMBER: builtins.int + HTTP_EXTENSION_FIELD_NUMBER: builtins.int + name: builtins.str + """Job name.""" + @property + def data(self) -> google.protobuf.any_pb2.Any: + """Job data to be sent back to app.""" + method: builtins.str + """Required. method is a method name which will be invoked by caller.""" + content_type: builtins.str + """The type of data content. + + This field is required if data delivers http request body + Otherwise, this is optional. + """ + @property + def http_extension(self) -> dapr.proto.common.v1.common_pb2.HTTPExtension: + """HTTP specific fields if request conveys http-compatible request. + + This field is required for http-compatible request. Otherwise, + this field is optional. + """ + def __init__( + self, + *, + name: builtins.str = ..., + data: google.protobuf.any_pb2.Any | None = ..., + method: builtins.str = ..., + content_type: builtins.str = ..., + http_extension: dapr.proto.common.v1.common_pb2.HTTPExtension | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["data", b"data", "http_extension", b"http_extension"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "data", b"data", "http_extension", b"http_extension", "method", b"method", "name", b"name"]) -> None: ... + +global___JobEventRequest = JobEventRequest + +@typing_extensions.final +class JobEventResponse(google.protobuf.message.Message): + """JobEventResponse is the response from the app when a job is triggered.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___JobEventResponse = JobEventResponse + +@typing_extensions.final +class TopicEventRequest(google.protobuf.message.Message): + """TopicEventRequest message is compatible with CloudEvent spec v1.0 + https://github.com/cloudevents/spec/blob/v1.0/spec.md + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ID_FIELD_NUMBER: builtins.int + SOURCE_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + SPEC_VERSION_FIELD_NUMBER: builtins.int + DATA_CONTENT_TYPE_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + PUBSUB_NAME_FIELD_NUMBER: builtins.int + PATH_FIELD_NUMBER: builtins.int + EXTENSIONS_FIELD_NUMBER: builtins.int + id: builtins.str + """id identifies the event. Producers MUST ensure that source + id + is unique for each distinct event. If a duplicate event is re-sent + (e.g. due to a network error) it MAY have the same id. + """ + source: builtins.str + """source identifies the context in which an event happened. + Often this will include information such as the type of the + event source, the organization publishing the event or the process + that produced the event. The exact syntax and semantics behind + the data encoded in the URI is defined by the event producer. + """ + type: builtins.str + """The type of event related to the originating occurrence.""" + spec_version: builtins.str + """The version of the CloudEvents specification.""" + data_content_type: builtins.str + """The content type of data value.""" + data: builtins.bytes + """The content of the event.""" + topic: builtins.str + """The pubsub topic which publisher sent to.""" + pubsub_name: builtins.str + """The name of the pubsub the publisher sent to.""" + path: builtins.str + """The matching path from TopicSubscription/routes (if specified) for this event. + This value is used by OnTopicEvent to "switch" inside the handler. + """ + @property + def extensions(self) -> google.protobuf.struct_pb2.Struct: + """The map of additional custom properties to be sent to the app. These are considered to be cloud event extensions.""" + def __init__( + self, + *, + id: builtins.str = ..., + source: builtins.str = ..., + type: builtins.str = ..., + spec_version: builtins.str = ..., + data_content_type: builtins.str = ..., + data: builtins.bytes = ..., + topic: builtins.str = ..., + pubsub_name: builtins.str = ..., + path: builtins.str = ..., + extensions: google.protobuf.struct_pb2.Struct | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["extensions", b"extensions"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "data_content_type", b"data_content_type", "extensions", b"extensions", "id", b"id", "path", b"path", "pubsub_name", b"pubsub_name", "source", b"source", "spec_version", b"spec_version", "topic", b"topic", "type", b"type"]) -> None: ... + +global___TopicEventRequest = TopicEventRequest + +@typing_extensions.final +class TopicEventResponse(google.protobuf.message.Message): + """TopicEventResponse is response from app on published message""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _TopicEventResponseStatus: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _TopicEventResponseStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[TopicEventResponse._TopicEventResponseStatus.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + SUCCESS: TopicEventResponse._TopicEventResponseStatus.ValueType # 0 + """SUCCESS is the default behavior: message is acknowledged and not retried or logged.""" + RETRY: TopicEventResponse._TopicEventResponseStatus.ValueType # 1 + """RETRY status signals Dapr to retry the message as part of an expected scenario (no warning is logged).""" + DROP: TopicEventResponse._TopicEventResponseStatus.ValueType # 2 + """DROP status signals Dapr to drop the message as part of an unexpected scenario (warning is logged).""" + + class TopicEventResponseStatus(_TopicEventResponseStatus, metaclass=_TopicEventResponseStatusEnumTypeWrapper): + """TopicEventResponseStatus allows apps to have finer control over handling of the message.""" + + SUCCESS: TopicEventResponse.TopicEventResponseStatus.ValueType # 0 + """SUCCESS is the default behavior: message is acknowledged and not retried or logged.""" + RETRY: TopicEventResponse.TopicEventResponseStatus.ValueType # 1 + """RETRY status signals Dapr to retry the message as part of an expected scenario (no warning is logged).""" + DROP: TopicEventResponse.TopicEventResponseStatus.ValueType # 2 + """DROP status signals Dapr to drop the message as part of an unexpected scenario (warning is logged).""" + + STATUS_FIELD_NUMBER: builtins.int + status: global___TopicEventResponse.TopicEventResponseStatus.ValueType + """The list of output bindings.""" + def __init__( + self, + *, + status: global___TopicEventResponse.TopicEventResponseStatus.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["status", b"status"]) -> None: ... + +global___TopicEventResponse = TopicEventResponse + +@typing_extensions.final +class TopicEventCERequest(google.protobuf.message.Message): + """TopicEventCERequest message is compatible with CloudEvent spec v1.0""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ID_FIELD_NUMBER: builtins.int + SOURCE_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + SPEC_VERSION_FIELD_NUMBER: builtins.int + DATA_CONTENT_TYPE_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + EXTENSIONS_FIELD_NUMBER: builtins.int + id: builtins.str + """The unique identifier of this cloud event.""" + source: builtins.str + """source identifies the context in which an event happened.""" + type: builtins.str + """The type of event related to the originating occurrence.""" + spec_version: builtins.str + """The version of the CloudEvents specification.""" + data_content_type: builtins.str + """The content type of data value.""" + data: builtins.bytes + """The content of the event.""" + @property + def extensions(self) -> google.protobuf.struct_pb2.Struct: + """Custom attributes which includes cloud event extensions.""" + def __init__( + self, + *, + id: builtins.str = ..., + source: builtins.str = ..., + type: builtins.str = ..., + spec_version: builtins.str = ..., + data_content_type: builtins.str = ..., + data: builtins.bytes = ..., + extensions: google.protobuf.struct_pb2.Struct | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["extensions", b"extensions"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "data_content_type", b"data_content_type", "extensions", b"extensions", "id", b"id", "source", b"source", "spec_version", b"spec_version", "type", b"type"]) -> None: ... + +global___TopicEventCERequest = TopicEventCERequest + +@typing_extensions.final +class TopicEventBulkRequestEntry(google.protobuf.message.Message): + """TopicEventBulkRequestEntry represents a single message inside a bulk request""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ENTRY_ID_FIELD_NUMBER: builtins.int + BYTES_FIELD_NUMBER: builtins.int + CLOUD_EVENT_FIELD_NUMBER: builtins.int + CONTENT_TYPE_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + entry_id: builtins.str + """Unique identifier for the message.""" + bytes: builtins.bytes + @property + def cloud_event(self) -> global___TopicEventCERequest: ... + content_type: builtins.str + """content type of the event contained.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata associated with the event.""" + def __init__( + self, + *, + entry_id: builtins.str = ..., + bytes: builtins.bytes = ..., + cloud_event: global___TopicEventCERequest | None = ..., + content_type: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["bytes", b"bytes", "cloud_event", b"cloud_event", "event", b"event"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["bytes", b"bytes", "cloud_event", b"cloud_event", "content_type", b"content_type", "entry_id", b"entry_id", "event", b"event", "metadata", b"metadata"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["event", b"event"]) -> typing_extensions.Literal["bytes", "cloud_event"] | None: ... + +global___TopicEventBulkRequestEntry = TopicEventBulkRequestEntry + +@typing_extensions.final +class TopicEventBulkRequest(google.protobuf.message.Message): + """TopicEventBulkRequest represents request for bulk message""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ID_FIELD_NUMBER: builtins.int + ENTRIES_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + PUBSUB_NAME_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + PATH_FIELD_NUMBER: builtins.int + id: builtins.str + """Unique identifier for the bulk request.""" + @property + def entries(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicEventBulkRequestEntry]: + """The list of items inside this bulk request.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata associated with the this bulk request.""" + topic: builtins.str + """The pubsub topic which publisher sent to.""" + pubsub_name: builtins.str + """The name of the pubsub the publisher sent to.""" + type: builtins.str + """The type of event related to the originating occurrence.""" + path: builtins.str + """The matching path from TopicSubscription/routes (if specified) for this event. + This value is used by OnTopicEvent to "switch" inside the handler. + """ + def __init__( + self, + *, + id: builtins.str = ..., + entries: collections.abc.Iterable[global___TopicEventBulkRequestEntry] | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + topic: builtins.str = ..., + pubsub_name: builtins.str = ..., + type: builtins.str = ..., + path: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["entries", b"entries", "id", b"id", "metadata", b"metadata", "path", b"path", "pubsub_name", b"pubsub_name", "topic", b"topic", "type", b"type"]) -> None: ... + +global___TopicEventBulkRequest = TopicEventBulkRequest + +@typing_extensions.final +class TopicEventBulkResponseEntry(google.protobuf.message.Message): + """TopicEventBulkResponseEntry Represents single response, as part of TopicEventBulkResponse, to be + sent by subscibed App for the corresponding single message during bulk subscribe + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ENTRY_ID_FIELD_NUMBER: builtins.int + STATUS_FIELD_NUMBER: builtins.int + entry_id: builtins.str + """Unique identifier associated the message.""" + status: global___TopicEventResponse.TopicEventResponseStatus.ValueType + """The status of the response.""" + def __init__( + self, + *, + entry_id: builtins.str = ..., + status: global___TopicEventResponse.TopicEventResponseStatus.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["entry_id", b"entry_id", "status", b"status"]) -> None: ... + +global___TopicEventBulkResponseEntry = TopicEventBulkResponseEntry + +@typing_extensions.final +class TopicEventBulkResponse(google.protobuf.message.Message): + """AppBulkResponse is response from app on published message""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STATUSES_FIELD_NUMBER: builtins.int + @property + def statuses(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicEventBulkResponseEntry]: + """The list of all responses for the bulk request.""" + def __init__( + self, + *, + statuses: collections.abc.Iterable[global___TopicEventBulkResponseEntry] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["statuses", b"statuses"]) -> None: ... + +global___TopicEventBulkResponse = TopicEventBulkResponse + +@typing_extensions.final +class BindingEventRequest(google.protobuf.message.Message): + """BindingEventRequest represents input bindings event.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + NAME_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + name: builtins.str + """Required. The name of the input binding component.""" + data: builtins.bytes + """Required. The payload that the input bindings sent""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata set by the input binging components.""" + def __init__( + self, + *, + name: builtins.str = ..., + data: builtins.bytes = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata", "name", b"name"]) -> None: ... + +global___BindingEventRequest = BindingEventRequest + +@typing_extensions.final +class BindingEventResponse(google.protobuf.message.Message): + """BindingEventResponse includes operations to save state or + send data to output bindings optionally. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _BindingEventConcurrency: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _BindingEventConcurrencyEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[BindingEventResponse._BindingEventConcurrency.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + SEQUENTIAL: BindingEventResponse._BindingEventConcurrency.ValueType # 0 + """SEQUENTIAL sends data to output bindings specified in "to" sequentially.""" + PARALLEL: BindingEventResponse._BindingEventConcurrency.ValueType # 1 + """PARALLEL sends data to output bindings specified in "to" in parallel.""" + + class BindingEventConcurrency(_BindingEventConcurrency, metaclass=_BindingEventConcurrencyEnumTypeWrapper): + """BindingEventConcurrency is the kind of concurrency""" + + SEQUENTIAL: BindingEventResponse.BindingEventConcurrency.ValueType # 0 + """SEQUENTIAL sends data to output bindings specified in "to" sequentially.""" + PARALLEL: BindingEventResponse.BindingEventConcurrency.ValueType # 1 + """PARALLEL sends data to output bindings specified in "to" in parallel.""" + + STORE_NAME_FIELD_NUMBER: builtins.int + STATES_FIELD_NUMBER: builtins.int + TO_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + CONCURRENCY_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store where states are saved.""" + @property + def states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[dapr.proto.common.v1.common_pb2.StateItem]: + """The state key values which will be stored in store_name.""" + @property + def to(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """The list of output bindings.""" + data: builtins.bytes + """The content which will be sent to "to" output bindings.""" + concurrency: global___BindingEventResponse.BindingEventConcurrency.ValueType + """The concurrency of output bindings to send data to + "to" output bindings list. The default is SEQUENTIAL. + """ + def __init__( + self, + *, + store_name: builtins.str = ..., + states: collections.abc.Iterable[dapr.proto.common.v1.common_pb2.StateItem] | None = ..., + to: collections.abc.Iterable[builtins.str] | None = ..., + data: builtins.bytes = ..., + concurrency: global___BindingEventResponse.BindingEventConcurrency.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["concurrency", b"concurrency", "data", b"data", "states", b"states", "store_name", b"store_name", "to", b"to"]) -> None: ... + +global___BindingEventResponse = BindingEventResponse + +@typing_extensions.final +class ListTopicSubscriptionsResponse(google.protobuf.message.Message): + """ListTopicSubscriptionsResponse is the message including the list of the subscribing topics.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SUBSCRIPTIONS_FIELD_NUMBER: builtins.int + @property + def subscriptions(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicSubscription]: + """The list of topics.""" + def __init__( + self, + *, + subscriptions: collections.abc.Iterable[global___TopicSubscription] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["subscriptions", b"subscriptions"]) -> None: ... + +global___ListTopicSubscriptionsResponse = ListTopicSubscriptionsResponse + +@typing_extensions.final +class TopicSubscription(google.protobuf.message.Message): + """TopicSubscription represents topic and metadata.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + PUBSUB_NAME_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + ROUTES_FIELD_NUMBER: builtins.int + DEAD_LETTER_TOPIC_FIELD_NUMBER: builtins.int + BULK_SUBSCRIBE_FIELD_NUMBER: builtins.int + pubsub_name: builtins.str + """Required. The name of the pubsub containing the topic below to subscribe to.""" + topic: builtins.str + """Required. The name of topic which will be subscribed""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The optional properties used for this topic's subscription e.g. session id""" + @property + def routes(self) -> global___TopicRoutes: + """The optional routing rules to match against. In the gRPC interface, OnTopicEvent + is still invoked but the matching path is sent in the TopicEventRequest. + """ + dead_letter_topic: builtins.str + """The optional dead letter queue for this topic to send events to.""" + @property + def bulk_subscribe(self) -> global___BulkSubscribeConfig: + """The optional bulk subscribe settings for this topic.""" + def __init__( + self, + *, + pubsub_name: builtins.str = ..., + topic: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + routes: global___TopicRoutes | None = ..., + dead_letter_topic: builtins.str = ..., + bulk_subscribe: global___BulkSubscribeConfig | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["bulk_subscribe", b"bulk_subscribe", "routes", b"routes"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["bulk_subscribe", b"bulk_subscribe", "dead_letter_topic", b"dead_letter_topic", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "routes", b"routes", "topic", b"topic"]) -> None: ... + +global___TopicSubscription = TopicSubscription + +@typing_extensions.final +class TopicRoutes(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RULES_FIELD_NUMBER: builtins.int + DEFAULT_FIELD_NUMBER: builtins.int + @property + def rules(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TopicRule]: + """The list of rules for this topic.""" + default: builtins.str + """The default path for this topic.""" + def __init__( + self, + *, + rules: collections.abc.Iterable[global___TopicRule] | None = ..., + default: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["default", b"default", "rules", b"rules"]) -> None: ... + +global___TopicRoutes = TopicRoutes + +@typing_extensions.final +class TopicRule(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MATCH_FIELD_NUMBER: builtins.int + PATH_FIELD_NUMBER: builtins.int + match: builtins.str + """The optional CEL expression used to match the event. + If the match is not specified, then the route is considered + the default. + """ + path: builtins.str + """The path used to identify matches for this subscription. + This value is passed in TopicEventRequest and used by OnTopicEvent to "switch" + inside the handler. + """ + def __init__( + self, + *, + match: builtins.str = ..., + path: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["match", b"match", "path", b"path"]) -> None: ... + +global___TopicRule = TopicRule + +@typing_extensions.final +class BulkSubscribeConfig(google.protobuf.message.Message): + """BulkSubscribeConfig is the message to pass settings for bulk subscribe""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ENABLED_FIELD_NUMBER: builtins.int + MAX_MESSAGES_COUNT_FIELD_NUMBER: builtins.int + MAX_AWAIT_DURATION_MS_FIELD_NUMBER: builtins.int + enabled: builtins.bool + """Required. Flag to enable/disable bulk subscribe""" + max_messages_count: builtins.int + """Optional. Max number of messages to be sent in a single bulk request""" + max_await_duration_ms: builtins.int + """Optional. Max duration to wait for messages to be sent in a single bulk request""" + def __init__( + self, + *, + enabled: builtins.bool = ..., + max_messages_count: builtins.int = ..., + max_await_duration_ms: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["enabled", b"enabled", "max_await_duration_ms", b"max_await_duration_ms", "max_messages_count", b"max_messages_count"]) -> None: ... + +global___BulkSubscribeConfig = BulkSubscribeConfig + +@typing_extensions.final +class ListInputBindingsResponse(google.protobuf.message.Message): + """ListInputBindingsResponse is the message including the list of input bindings.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + BINDINGS_FIELD_NUMBER: builtins.int + @property + def bindings(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """The list of input bindings.""" + def __init__( + self, + *, + bindings: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["bindings", b"bindings"]) -> None: ... + +global___ListInputBindingsResponse = ListInputBindingsResponse + +@typing_extensions.final +class HealthCheckResponse(google.protobuf.message.Message): + """HealthCheckResponse is the message with the response to the health check. + This message is currently empty as used as placeholder. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___HealthCheckResponse = HealthCheckResponse diff --git a/dapr/proto/runtime/v1/appcallback_pb2_grpc.py b/dapr/proto/runtime/v1/appcallback_pb2_grpc.py index b203f7db0..a9bc4c931 100644 --- a/dapr/proto/runtime/v1/appcallback_pb2_grpc.py +++ b/dapr/proto/runtime/v1/appcallback_pb2_grpc.py @@ -1,387 +1,387 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 -from dapr.proto.runtime.v1 import appcallback_pb2 as dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -class AppCallbackStub(object): - """AppCallback V1 allows user application to interact with Dapr runtime. - User application needs to implement AppCallback service if it needs to - receive message from dapr runtime. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.OnInvoke = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallback/OnInvoke', - request_serializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, - ) - self.ListTopicSubscriptions = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallback/ListTopicSubscriptions', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListTopicSubscriptionsResponse.FromString, - ) - self.OnTopicEvent = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallback/OnTopicEvent', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventResponse.FromString, - ) - self.ListInputBindings = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallback/ListInputBindings', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListInputBindingsResponse.FromString, - ) - self.OnBindingEvent = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallback/OnBindingEvent', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventResponse.FromString, - ) - - -class AppCallbackServicer(object): - """AppCallback V1 allows user application to interact with Dapr runtime. - User application needs to implement AppCallback service if it needs to - receive message from dapr runtime. - """ - - def OnInvoke(self, request, context): - """Invokes service method with InvokeRequest. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListTopicSubscriptions(self, request, context): - """Lists all topics subscribed by this app. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def OnTopicEvent(self, request, context): - """Subscribes events from Pubsub - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ListInputBindings(self, request, context): - """Lists all input bindings subscribed by this app. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def OnBindingEvent(self, request, context): - """Listens events from the input bindings - - User application can save the states or send the events to the output - bindings optionally by returning BindingEventResponse. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_AppCallbackServicer_to_server(servicer, server): - rpc_method_handlers = { - 'OnInvoke': grpc.unary_unary_rpc_method_handler( - servicer.OnInvoke, - request_deserializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeRequest.FromString, - response_serializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.SerializeToString, - ), - 'ListTopicSubscriptions': grpc.unary_unary_rpc_method_handler( - servicer.ListTopicSubscriptions, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListTopicSubscriptionsResponse.SerializeToString, - ), - 'OnTopicEvent': grpc.unary_unary_rpc_method_handler( - servicer.OnTopicEvent, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventResponse.SerializeToString, - ), - 'ListInputBindings': grpc.unary_unary_rpc_method_handler( - servicer.ListInputBindings, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListInputBindingsResponse.SerializeToString, - ), - 'OnBindingEvent': grpc.unary_unary_rpc_method_handler( - servicer.OnBindingEvent, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'dapr.proto.runtime.v1.AppCallback', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class AppCallback(object): - """AppCallback V1 allows user application to interact with Dapr runtime. - User application needs to implement AppCallback service if it needs to - receive message from dapr runtime. - """ - - @staticmethod - def OnInvoke(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/OnInvoke', - dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeRequest.SerializeToString, - dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListTopicSubscriptions(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/ListTopicSubscriptions', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListTopicSubscriptionsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def OnTopicEvent(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/OnTopicEvent', - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ListInputBindings(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/ListInputBindings', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListInputBindingsResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def OnBindingEvent(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/OnBindingEvent', - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - -class AppCallbackHealthCheckStub(object): - """AppCallbackHealthCheck V1 is an optional extension to AppCallback V1 to implement - the HealthCheck method. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.HealthCheck = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallbackHealthCheck/HealthCheck', - request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.HealthCheckResponse.FromString, - ) - - -class AppCallbackHealthCheckServicer(object): - """AppCallbackHealthCheck V1 is an optional extension to AppCallback V1 to implement - the HealthCheck method. - """ - - def HealthCheck(self, request, context): - """Health check. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_AppCallbackHealthCheckServicer_to_server(servicer, server): - rpc_method_handlers = { - 'HealthCheck': grpc.unary_unary_rpc_method_handler( - servicer.HealthCheck, - request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.HealthCheckResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'dapr.proto.runtime.v1.AppCallbackHealthCheck', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class AppCallbackHealthCheck(object): - """AppCallbackHealthCheck V1 is an optional extension to AppCallback V1 to implement - the HealthCheck method. - """ - - @staticmethod - def HealthCheck(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallbackHealthCheck/HealthCheck', - google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.HealthCheckResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - -class AppCallbackAlphaStub(object): - """AppCallbackAlpha V1 is an optional extension to AppCallback V1 to opt - for Alpha RPCs. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.OnBulkTopicEventAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallbackAlpha/OnBulkTopicEventAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkResponse.FromString, - ) - self.OnJobEventAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.AppCallbackAlpha/OnJobEventAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventResponse.FromString, - ) - - -class AppCallbackAlphaServicer(object): - """AppCallbackAlpha V1 is an optional extension to AppCallback V1 to opt - for Alpha RPCs. - """ - - def OnBulkTopicEventAlpha1(self, request, context): - """Subscribes bulk events from Pubsub - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def OnJobEventAlpha1(self, request, context): - """Sends job back to the app's endpoint at trigger time. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_AppCallbackAlphaServicer_to_server(servicer, server): - rpc_method_handlers = { - 'OnBulkTopicEventAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.OnBulkTopicEventAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkResponse.SerializeToString, - ), - 'OnJobEventAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.OnJobEventAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'dapr.proto.runtime.v1.AppCallbackAlpha', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class AppCallbackAlpha(object): - """AppCallbackAlpha V1 is an optional extension to AppCallback V1 to opt - for Alpha RPCs. - """ - - @staticmethod - def OnBulkTopicEventAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallbackAlpha/OnBulkTopicEventAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def OnJobEventAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallbackAlpha/OnJobEventAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 +from dapr.proto.runtime.v1 import appcallback_pb2 as dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2 +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 + + +class AppCallbackStub(object): + """AppCallback V1 allows user application to interact with Dapr runtime. + User application needs to implement AppCallback service if it needs to + receive message from dapr runtime. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.OnInvoke = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallback/OnInvoke', + request_serializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, + ) + self.ListTopicSubscriptions = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallback/ListTopicSubscriptions', + request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListTopicSubscriptionsResponse.FromString, + ) + self.OnTopicEvent = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallback/OnTopicEvent', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventResponse.FromString, + ) + self.ListInputBindings = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallback/ListInputBindings', + request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListInputBindingsResponse.FromString, + ) + self.OnBindingEvent = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallback/OnBindingEvent', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventResponse.FromString, + ) + + +class AppCallbackServicer(object): + """AppCallback V1 allows user application to interact with Dapr runtime. + User application needs to implement AppCallback service if it needs to + receive message from dapr runtime. + """ + + def OnInvoke(self, request, context): + """Invokes service method with InvokeRequest. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListTopicSubscriptions(self, request, context): + """Lists all topics subscribed by this app. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def OnTopicEvent(self, request, context): + """Subscribes events from Pubsub + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListInputBindings(self, request, context): + """Lists all input bindings subscribed by this app. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def OnBindingEvent(self, request, context): + """Listens events from the input bindings + + User application can save the states or send the events to the output + bindings optionally by returning BindingEventResponse. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AppCallbackServicer_to_server(servicer, server): + rpc_method_handlers = { + 'OnInvoke': grpc.unary_unary_rpc_method_handler( + servicer.OnInvoke, + request_deserializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeRequest.FromString, + response_serializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.SerializeToString, + ), + 'ListTopicSubscriptions': grpc.unary_unary_rpc_method_handler( + servicer.ListTopicSubscriptions, + request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListTopicSubscriptionsResponse.SerializeToString, + ), + 'OnTopicEvent': grpc.unary_unary_rpc_method_handler( + servicer.OnTopicEvent, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventResponse.SerializeToString, + ), + 'ListInputBindings': grpc.unary_unary_rpc_method_handler( + servicer.ListInputBindings, + request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListInputBindingsResponse.SerializeToString, + ), + 'OnBindingEvent': grpc.unary_unary_rpc_method_handler( + servicer.OnBindingEvent, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'dapr.proto.runtime.v1.AppCallback', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AppCallback(object): + """AppCallback V1 allows user application to interact with Dapr runtime. + User application needs to implement AppCallback service if it needs to + receive message from dapr runtime. + """ + + @staticmethod + def OnInvoke(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/OnInvoke', + dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeRequest.SerializeToString, + dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListTopicSubscriptions(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/ListTopicSubscriptions', + google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListTopicSubscriptionsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def OnTopicEvent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/OnTopicEvent', + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ListInputBindings(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/ListInputBindings', + google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.ListInputBindingsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def OnBindingEvent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallback/OnBindingEvent', + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.BindingEventResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class AppCallbackHealthCheckStub(object): + """AppCallbackHealthCheck V1 is an optional extension to AppCallback V1 to implement + the HealthCheck method. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.HealthCheck = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallbackHealthCheck/HealthCheck', + request_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.HealthCheckResponse.FromString, + ) + + +class AppCallbackHealthCheckServicer(object): + """AppCallbackHealthCheck V1 is an optional extension to AppCallback V1 to implement + the HealthCheck method. + """ + + def HealthCheck(self, request, context): + """Health check. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AppCallbackHealthCheckServicer_to_server(servicer, server): + rpc_method_handlers = { + 'HealthCheck': grpc.unary_unary_rpc_method_handler( + servicer.HealthCheck, + request_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.HealthCheckResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'dapr.proto.runtime.v1.AppCallbackHealthCheck', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AppCallbackHealthCheck(object): + """AppCallbackHealthCheck V1 is an optional extension to AppCallback V1 to implement + the HealthCheck method. + """ + + @staticmethod + def HealthCheck(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallbackHealthCheck/HealthCheck', + google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.HealthCheckResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + +class AppCallbackAlphaStub(object): + """AppCallbackAlpha V1 is an optional extension to AppCallback V1 to opt + for Alpha RPCs. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.OnBulkTopicEventAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallbackAlpha/OnBulkTopicEventAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkResponse.FromString, + ) + self.OnJobEventAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.AppCallbackAlpha/OnJobEventAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventResponse.FromString, + ) + + +class AppCallbackAlphaServicer(object): + """AppCallbackAlpha V1 is an optional extension to AppCallback V1 to opt + for Alpha RPCs. + """ + + def OnBulkTopicEventAlpha1(self, request, context): + """Subscribes bulk events from Pubsub + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def OnJobEventAlpha1(self, request, context): + """Sends job back to the app's endpoint at trigger time. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AppCallbackAlphaServicer_to_server(servicer, server): + rpc_method_handlers = { + 'OnBulkTopicEventAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.OnBulkTopicEventAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkResponse.SerializeToString, + ), + 'OnJobEventAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.OnJobEventAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'dapr.proto.runtime.v1.AppCallbackAlpha', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class AppCallbackAlpha(object): + """AppCallbackAlpha V1 is an optional extension to AppCallback V1 to opt + for Alpha RPCs. + """ + + @staticmethod + def OnBulkTopicEventAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallbackAlpha/OnBulkTopicEventAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.TopicEventBulkResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def OnJobEventAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.AppCallbackAlpha/OnJobEventAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2.JobEventResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/dapr/proto/runtime/v1/dapr_pb2.py b/dapr/proto/runtime/v1/dapr_pb2.py index e2e9ccbc5..74c5aabbe 100644 --- a/dapr/proto/runtime/v1/dapr_pb2.py +++ b/dapr/proto/runtime/v1/dapr_pb2.py @@ -1,382 +1,382 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: dapr/proto/runtime/v1/dapr.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 -from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 -from dapr.proto.runtime.v1 import appcallback_pb2 as dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2 - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n dapr/proto/runtime/v1/dapr.proto\x12\x15\x64\x61pr.proto.runtime.v1\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!dapr/proto/common/v1/common.proto\x1a\'dapr/proto/runtime/v1/appcallback.proto\"X\n\x14InvokeServiceRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x34\n\x07message\x18\x03 \x01(\x0b\x32#.dapr.proto.common.v1.InvokeRequest\"\xf5\x01\n\x0fGetStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12H\n\x0b\x63onsistency\x18\x03 \x01(\x0e\x32\x33.dapr.proto.common.v1.StateOptions.StateConsistency\x12\x46\n\x08metadata\x18\x04 \x03(\x0b\x32\x34.dapr.proto.runtime.v1.GetStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc9\x01\n\x13GetBulkStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12\x13\n\x0bparallelism\x18\x03 \x01(\x05\x12J\n\x08metadata\x18\x04 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.GetBulkStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"K\n\x14GetBulkStateResponse\x12\x33\n\x05items\x18\x01 \x03(\x0b\x32$.dapr.proto.runtime.v1.BulkStateItem\"\xbe\x01\n\rBulkStateItem\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12\x44\n\x08metadata\x18\x05 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.BulkStateItem.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa8\x01\n\x10GetStateResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x02 \x01(\t\x12G\n\x08metadata\x18\x03 \x03(\x0b\x32\x35.dapr.proto.runtime.v1.GetStateResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x90\x02\n\x12\x44\x65leteStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12(\n\x04\x65tag\x18\x03 \x01(\x0b\x32\x1a.dapr.proto.common.v1.Etag\x12\x33\n\x07options\x18\x04 \x01(\x0b\x32\".dapr.proto.common.v1.StateOptions\x12I\n\x08metadata\x18\x05 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.DeleteStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"]\n\x16\x44\x65leteBulkStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12/\n\x06states\x18\x02 \x03(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\"W\n\x10SaveStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12/\n\x06states\x18\x02 \x03(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\"\xbc\x01\n\x11QueryStateRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\r\n\x05query\x18\x02 \x01(\t\x12H\n\x08metadata\x18\x03 \x03(\x0b\x32\x36.dapr.proto.runtime.v1.QueryStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"H\n\x0eQueryStateItem\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\xd7\x01\n\x12QueryStateResponse\x12\x36\n\x07results\x18\x01 \x03(\x0b\x32%.dapr.proto.runtime.v1.QueryStateItem\x12\r\n\x05token\x18\x02 \x01(\t\x12I\n\x08metadata\x18\x03 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.QueryStateResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xdf\x01\n\x13PublishEventRequest\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x19\n\x11\x64\x61ta_content_type\x18\x04 \x01(\t\x12J\n\x08metadata\x18\x05 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.PublishEventRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf5\x01\n\x12\x42ulkPublishRequest\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12?\n\x07\x65ntries\x18\x03 \x03(\x0b\x32..dapr.proto.runtime.v1.BulkPublishRequestEntry\x12I\n\x08metadata\x18\x04 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.BulkPublishRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd1\x01\n\x17\x42ulkPublishRequestEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\r\n\x05\x65vent\x18\x02 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12N\n\x08metadata\x18\x04 \x03(\x0b\x32<.dapr.proto.runtime.v1.BulkPublishRequestEntry.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"c\n\x13\x42ulkPublishResponse\x12L\n\rfailedEntries\x18\x01 \x03(\x0b\x32\x35.dapr.proto.runtime.v1.BulkPublishResponseFailedEntry\"A\n\x1e\x42ulkPublishResponseFailedEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x84\x02\n!SubscribeTopicEventsRequestAlpha1\x12Z\n\x0finitial_request\x18\x01 \x01(\x0b\x32?.dapr.proto.runtime.v1.SubscribeTopicEventsRequestInitialAlpha1H\x00\x12\\\n\x0f\x65vent_processed\x18\x02 \x01(\x0b\x32\x41.dapr.proto.runtime.v1.SubscribeTopicEventsRequestProcessedAlpha1H\x00\x42%\n#subscribe_topic_events_request_type\"\x96\x02\n(SubscribeTopicEventsRequestInitialAlpha1\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12_\n\x08metadata\x18\x03 \x03(\x0b\x32M.dapr.proto.runtime.v1.SubscribeTopicEventsRequestInitialAlpha1.MetadataEntry\x12\x1e\n\x11\x64\x65\x61\x64_letter_topic\x18\x04 \x01(\tH\x00\x88\x01\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x14\n\x12_dead_letter_topic\"s\n*SubscribeTopicEventsRequestProcessedAlpha1\x12\n\n\x02id\x18\x01 \x01(\t\x12\x39\n\x06status\x18\x02 \x01(\x0b\x32).dapr.proto.runtime.v1.TopicEventResponse\"\xed\x01\n\"SubscribeTopicEventsResponseAlpha1\x12\\\n\x10initial_response\x18\x01 \x01(\x0b\x32@.dapr.proto.runtime.v1.SubscribeTopicEventsResponseInitialAlpha1H\x00\x12\x41\n\revent_message\x18\x02 \x01(\x0b\x32(.dapr.proto.runtime.v1.TopicEventRequestH\x00\x42&\n$subscribe_topic_events_response_type\"+\n)SubscribeTopicEventsResponseInitialAlpha1\"\xc3\x01\n\x14InvokeBindingRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12K\n\x08metadata\x18\x03 \x03(\x0b\x32\x39.dapr.proto.runtime.v1.InvokeBindingRequest.MetadataEntry\x12\x11\n\toperation\x18\x04 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa4\x01\n\x15InvokeBindingResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12L\n\x08metadata\x18\x02 \x03(\x0b\x32:.dapr.proto.runtime.v1.InvokeBindingResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb8\x01\n\x10GetSecretRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\x0b\n\x03key\x18\x02 \x01(\t\x12G\n\x08metadata\x18\x03 \x03(\x0b\x32\x35.dapr.proto.runtime.v1.GetSecretRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x82\x01\n\x11GetSecretResponse\x12@\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.GetSecretResponse.DataEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb3\x01\n\x14GetBulkSecretRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12K\n\x08metadata\x18\x02 \x03(\x0b\x32\x39.dapr.proto.runtime.v1.GetBulkSecretRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x85\x01\n\x0eSecretResponse\x12\x43\n\x07secrets\x18\x01 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.SecretResponse.SecretsEntry\x1a.\n\x0cSecretsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb1\x01\n\x15GetBulkSecretResponse\x12\x44\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x36.dapr.proto.runtime.v1.GetBulkSecretResponse.DataEntry\x1aR\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x34\n\x05value\x18\x02 \x01(\x0b\x32%.dapr.proto.runtime.v1.SecretResponse:\x02\x38\x01\"f\n\x1bTransactionalStateOperation\x12\x15\n\roperationType\x18\x01 \x01(\t\x12\x30\n\x07request\x18\x02 \x01(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\"\x83\x02\n\x1e\x45xecuteStateTransactionRequest\x12\x11\n\tstoreName\x18\x01 \x01(\t\x12\x46\n\noperations\x18\x02 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.TransactionalStateOperation\x12U\n\x08metadata\x18\x03 \x03(\x0b\x32\x43.dapr.proto.runtime.v1.ExecuteStateTransactionRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbb\x01\n\x19RegisterActorTimerRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x19\n\x08\x64ue_time\x18\x04 \x01(\tR\x07\x64ueTime\x12\x0e\n\x06period\x18\x05 \x01(\t\x12\x10\n\x08\x63\x61llback\x18\x06 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x07 \x01(\x0c\x12\x0b\n\x03ttl\x18\x08 \x01(\t\"e\n\x1bUnregisterActorTimerRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\"\xac\x01\n\x1cRegisterActorReminderRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x19\n\x08\x64ue_time\x18\x04 \x01(\tR\x07\x64ueTime\x12\x0e\n\x06period\x18\x05 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x06 \x01(\x0c\x12\x0b\n\x03ttl\x18\x07 \x01(\t\"h\n\x1eUnregisterActorReminderRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\"]\n\x14GetActorStateRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0b\n\x03key\x18\x03 \x01(\t\"\xa4\x01\n\x15GetActorStateResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12L\n\x08metadata\x18\x02 \x03(\x0b\x32:.dapr.proto.runtime.v1.GetActorStateResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xac\x01\n#ExecuteActorStateTransactionRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12K\n\noperations\x18\x03 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.TransactionalActorStateOperation\"\xf5\x01\n TransactionalActorStateOperation\x12\x15\n\roperationType\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12#\n\x05value\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any\x12W\n\x08metadata\x18\x04 \x03(\x0b\x32\x45.dapr.proto.runtime.v1.TransactionalActorStateOperation.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe8\x01\n\x12InvokeActorRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12I\n\x08metadata\x18\x05 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.InvokeActorRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"#\n\x13InvokeActorResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x14\n\x12GetMetadataRequest\"\x9b\x06\n\x13GetMetadataResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12Q\n\x13\x61\x63tive_actors_count\x18\x02 \x03(\x0b\x32(.dapr.proto.runtime.v1.ActiveActorsCountB\x02\x18\x01R\x06\x61\x63tors\x12V\n\x15registered_components\x18\x03 \x03(\x0b\x32+.dapr.proto.runtime.v1.RegisteredComponentsR\ncomponents\x12\x65\n\x11\x65xtended_metadata\x18\x04 \x03(\x0b\x32@.dapr.proto.runtime.v1.GetMetadataResponse.ExtendedMetadataEntryR\x08\x65xtended\x12O\n\rsubscriptions\x18\x05 \x03(\x0b\x32).dapr.proto.runtime.v1.PubsubSubscriptionR\rsubscriptions\x12R\n\x0ehttp_endpoints\x18\x06 \x03(\x0b\x32+.dapr.proto.runtime.v1.MetadataHTTPEndpointR\rhttpEndpoints\x12j\n\x19\x61pp_connection_properties\x18\x07 \x01(\x0b\x32..dapr.proto.runtime.v1.AppConnectionPropertiesR\x17\x61ppConnectionProperties\x12\'\n\x0fruntime_version\x18\x08 \x01(\tR\x0eruntimeVersion\x12)\n\x10\x65nabled_features\x18\t \x03(\tR\x0f\x65nabledFeatures\x12H\n\ractor_runtime\x18\n \x01(\x0b\x32#.dapr.proto.runtime.v1.ActorRuntimeR\x0c\x61\x63torRuntime\x1a\x37\n\x15\x45xtendedMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x02\n\x0c\x41\x63torRuntime\x12]\n\x0eruntime_status\x18\x01 \x01(\x0e\x32\x36.dapr.proto.runtime.v1.ActorRuntime.ActorRuntimeStatusR\rruntimeStatus\x12M\n\ractive_actors\x18\x02 \x03(\x0b\x32(.dapr.proto.runtime.v1.ActiveActorsCountR\x0c\x61\x63tiveActors\x12\x1d\n\nhost_ready\x18\x03 \x01(\x08R\thostReady\x12\x1c\n\tplacement\x18\x04 \x01(\tR\tplacement\"A\n\x12\x41\x63torRuntimeStatus\x12\x10\n\x0cINITIALIZING\x10\x00\x12\x0c\n\x08\x44ISABLED\x10\x01\x12\x0b\n\x07RUNNING\x10\x02\"0\n\x11\x41\x63tiveActorsCount\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\"Y\n\x14RegisteredComponents\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x04 \x03(\t\"*\n\x14MetadataHTTPEndpoint\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"\xd1\x01\n\x17\x41ppConnectionProperties\x12\x0c\n\x04port\x18\x01 \x01(\x05\x12\x10\n\x08protocol\x18\x02 \x01(\t\x12\'\n\x0f\x63hannel_address\x18\x03 \x01(\tR\x0e\x63hannelAddress\x12\'\n\x0fmax_concurrency\x18\x04 \x01(\x05R\x0emaxConcurrency\x12\x44\n\x06health\x18\x05 \x01(\x0b\x32\x34.dapr.proto.runtime.v1.AppConnectionHealthProperties\"\xdc\x01\n\x1d\x41ppConnectionHealthProperties\x12*\n\x11health_check_path\x18\x01 \x01(\tR\x0fhealthCheckPath\x12\x32\n\x15health_probe_interval\x18\x02 \x01(\tR\x13healthProbeInterval\x12\x30\n\x14health_probe_timeout\x18\x03 \x01(\tR\x12healthProbeTimeout\x12)\n\x10health_threshold\x18\x04 \x01(\x05R\x0fhealthThreshold\"\x86\x03\n\x12PubsubSubscription\x12\x1f\n\x0bpubsub_name\x18\x01 \x01(\tR\npubsubname\x12\x14\n\x05topic\x18\x02 \x01(\tR\x05topic\x12S\n\x08metadata\x18\x03 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.PubsubSubscription.MetadataEntryR\x08metadata\x12\x44\n\x05rules\x18\x04 \x01(\x0b\x32..dapr.proto.runtime.v1.PubsubSubscriptionRulesR\x05rules\x12*\n\x11\x64\x65\x61\x64_letter_topic\x18\x05 \x01(\tR\x0f\x64\x65\x61\x64LetterTopic\x12\x41\n\x04type\x18\x06 \x01(\x0e\x32-.dapr.proto.runtime.v1.PubsubSubscriptionTypeR\x04type\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"W\n\x17PubsubSubscriptionRules\x12<\n\x05rules\x18\x01 \x03(\x0b\x32-.dapr.proto.runtime.v1.PubsubSubscriptionRule\"5\n\x16PubsubSubscriptionRule\x12\r\n\x05match\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"0\n\x12SetMetadataRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\xbc\x01\n\x17GetConfigurationRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12N\n\x08metadata\x18\x03 \x03(\x0b\x32<.dapr.proto.runtime.v1.GetConfigurationRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x01\n\x18GetConfigurationResponse\x12I\n\x05items\x18\x01 \x03(\x0b\x32:.dapr.proto.runtime.v1.GetConfigurationResponse.ItemsEntry\x1aU\n\nItemsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.dapr.proto.common.v1.ConfigurationItem:\x02\x38\x01\"\xc8\x01\n\x1dSubscribeConfigurationRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12T\n\x08metadata\x18\x03 \x03(\x0b\x32\x42.dapr.proto.runtime.v1.SubscribeConfigurationRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"A\n\x1fUnsubscribeConfigurationRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\"\xd4\x01\n\x1eSubscribeConfigurationResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12O\n\x05items\x18\x02 \x03(\x0b\x32@.dapr.proto.runtime.v1.SubscribeConfigurationResponse.ItemsEntry\x1aU\n\nItemsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.dapr.proto.common.v1.ConfigurationItem:\x02\x38\x01\"?\n UnsubscribeConfigurationResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"\x9b\x01\n\x0eTryLockRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\x1f\n\x0bresource_id\x18\x02 \x01(\tR\nresourceId\x12\x1d\n\nlock_owner\x18\x03 \x01(\tR\tlockOwner\x12*\n\x11\x65xpiry_in_seconds\x18\x04 \x01(\x05R\x0f\x65xpiryInSeconds\"\"\n\x0fTryLockResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"n\n\rUnlockRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\x1f\n\x0bresource_id\x18\x02 \x01(\tR\nresourceId\x12\x1d\n\nlock_owner\x18\x03 \x01(\tR\tlockOwner\"\xae\x01\n\x0eUnlockResponse\x12<\n\x06status\x18\x01 \x01(\x0e\x32,.dapr.proto.runtime.v1.UnlockResponse.Status\"^\n\x06Status\x12\x0b\n\x07SUCCESS\x10\x00\x12\x17\n\x13LOCK_DOES_NOT_EXIST\x10\x01\x12\x1a\n\x16LOCK_BELONGS_TO_OTHERS\x10\x02\x12\x12\n\x0eINTERNAL_ERROR\x10\x03\"\xb0\x01\n\x13SubtleGetKeyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x44\n\x06\x66ormat\x18\x03 \x01(\x0e\x32\x34.dapr.proto.runtime.v1.SubtleGetKeyRequest.KeyFormat\"\x1e\n\tKeyFormat\x12\x07\n\x03PEM\x10\x00\x12\x08\n\x04JSON\x10\x01\"C\n\x14SubtleGetKeyResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1d\n\npublic_key\x18\x02 \x01(\tR\tpublicKey\"\xb6\x01\n\x14SubtleEncryptRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x11\n\tplaintext\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x06 \x01(\x0cR\x0e\x61ssociatedData\"8\n\x15SubtleEncryptResponse\x12\x12\n\nciphertext\x18\x01 \x01(\x0c\x12\x0b\n\x03tag\x18\x02 \x01(\x0c\"\xc4\x01\n\x14SubtleDecryptRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x12\n\nciphertext\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\x0b\n\x03tag\x18\x06 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x07 \x01(\x0cR\x0e\x61ssociatedData\"*\n\x15SubtleDecryptResponse\x12\x11\n\tplaintext\x18\x01 \x01(\x0c\"\xc8\x01\n\x14SubtleWrapKeyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12#\n\rplaintext_key\x18\x02 \x01(\x0cR\x0cplaintextKey\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x06 \x01(\x0cR\x0e\x61ssociatedData\"E\n\x15SubtleWrapKeyResponse\x12\x1f\n\x0bwrapped_key\x18\x01 \x01(\x0cR\nwrappedKey\x12\x0b\n\x03tag\x18\x02 \x01(\x0c\"\xd3\x01\n\x16SubtleUnwrapKeyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x1f\n\x0bwrapped_key\x18\x02 \x01(\x0cR\nwrappedKey\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\x0b\n\x03tag\x18\x06 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x07 \x01(\x0cR\x0e\x61ssociatedData\">\n\x17SubtleUnwrapKeyResponse\x12#\n\rplaintext_key\x18\x01 \x01(\x0cR\x0cplaintextKey\"x\n\x11SubtleSignRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x0e\n\x06\x64igest\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\"\'\n\x12SubtleSignResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"\x8d\x01\n\x13SubtleVerifyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x0e\n\x06\x64igest\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\x11\n\tsignature\x18\x05 \x01(\x0c\"%\n\x14SubtleVerifyResponse\x12\r\n\x05valid\x18\x01 \x01(\x08\"\x85\x01\n\x0e\x45ncryptRequest\x12=\n\x07options\x18\x01 \x01(\x0b\x32,.dapr.proto.runtime.v1.EncryptRequestOptions\x12\x34\n\x07payload\x18\x02 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"\xfe\x01\n\x15\x45ncryptRequestOptions\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x19\n\x08key_name\x18\x02 \x01(\tR\x07keyName\x12\x1a\n\x12key_wrap_algorithm\x18\x03 \x01(\t\x12\x1e\n\x16\x64\x61ta_encryption_cipher\x18\n \x01(\t\x12\x37\n\x18omit_decryption_key_name\x18\x0b \x01(\x08R\x15omitDecryptionKeyName\x12.\n\x13\x64\x65\x63ryption_key_name\x18\x0c \x01(\tR\x11\x64\x65\x63ryptionKeyName\"G\n\x0f\x45ncryptResponse\x12\x34\n\x07payload\x18\x01 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"\x85\x01\n\x0e\x44\x65\x63ryptRequest\x12=\n\x07options\x18\x01 \x01(\x0b\x32,.dapr.proto.runtime.v1.DecryptRequestOptions\x12\x34\n\x07payload\x18\x02 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"Y\n\x15\x44\x65\x63ryptRequestOptions\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x19\n\x08key_name\x18\x0c \x01(\tR\x07keyName\"G\n\x0f\x44\x65\x63ryptResponse\x12\x34\n\x07payload\x18\x01 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"d\n\x12GetWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"\x84\x03\n\x13GetWorkflowResponse\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12#\n\rworkflow_name\x18\x02 \x01(\tR\x0cworkflowName\x12\x39\n\ncreated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x42\n\x0flast_updated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\rlastUpdatedAt\x12%\n\x0eruntime_status\x18\x05 \x01(\tR\rruntimeStatus\x12N\n\nproperties\x18\x06 \x03(\x0b\x32:.dapr.proto.runtime.v1.GetWorkflowResponse.PropertiesEntry\x1a\x31\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x95\x02\n\x14StartWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\x12#\n\rworkflow_name\x18\x03 \x01(\tR\x0cworkflowName\x12I\n\x07options\x18\x04 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.StartWorkflowRequest.OptionsEntry\x12\r\n\x05input\x18\x05 \x01(\x0c\x1a.\n\x0cOptionsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"8\n\x15StartWorkflowResponse\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\"j\n\x18TerminateWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"f\n\x14PauseWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"g\n\x15ResumeWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"\x9e\x01\n\x19RaiseEventWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\x12\x1d\n\nevent_name\x18\x03 \x01(\tR\teventName\x12\x12\n\nevent_data\x18\x04 \x01(\x0c\"f\n\x14PurgeWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"\x11\n\x0fShutdownRequest\"\xe8\x01\n\x03Job\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n\x08schedule\x18\x02 \x01(\tH\x00R\x08schedule\x88\x01\x01\x12\x1d\n\x07repeats\x18\x03 \x01(\rH\x01R\x07repeats\x88\x01\x01\x12\x1e\n\x08\x64ue_time\x18\x04 \x01(\tH\x02R\x07\x64ueTime\x88\x01\x01\x12\x15\n\x03ttl\x18\x05 \x01(\tH\x03R\x03ttl\x88\x01\x01\x12(\n\x04\x64\x61ta\x18\x06 \x01(\x0b\x32\x14.google.protobuf.AnyR\x04\x64\x61taB\x0b\n\t_scheduleB\n\n\x08_repeatsB\x0b\n\t_due_timeB\x06\n\x04_ttl\"=\n\x12ScheduleJobRequest\x12\'\n\x03job\x18\x01 \x01(\x0b\x32\x1a.dapr.proto.runtime.v1.Job\"\x15\n\x13ScheduleJobResponse\"\x1d\n\rGetJobRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"9\n\x0eGetJobResponse\x12\'\n\x03job\x18\x01 \x01(\x0b\x32\x1a.dapr.proto.runtime.v1.Job\" \n\x10\x44\x65leteJobRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x13\n\x11\x44\x65leteJobResponse\"\xf9\x03\n\x19\x43onversationAlpha1Request\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\tcontextID\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x38\n\x06inputs\x18\x03 \x03(\x0b\x32(.dapr.proto.runtime.v1.ConversationInput\x12T\n\nparameters\x18\x04 \x03(\x0b\x32@.dapr.proto.runtime.v1.ConversationAlpha1Request.ParametersEntry\x12P\n\x08metadata\x18\x05 \x03(\x0b\x32>.dapr.proto.runtime.v1.ConversationAlpha1Request.MetadataEntry\x12\x15\n\x08scrubPII\x18\x06 \x01(\x08H\x01\x88\x01\x01\x12\x18\n\x0btemperature\x18\x07 \x01(\x01H\x02\x88\x01\x01\x1aG\n\x0fParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any:\x02\x38\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0c\n\n_contextIDB\x0b\n\t_scrubPIIB\x0e\n\x0c_temperature\"d\n\x11\x43onversationInput\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x11\n\x04role\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08scrubPII\x18\x03 \x01(\x08H\x01\x88\x01\x01\x42\x07\n\x05_roleB\x0b\n\t_scrubPII\"\xc8\x01\n\x18\x43onversationAlpha1Result\x12\x0e\n\x06result\x18\x01 \x01(\t\x12S\n\nparameters\x18\x02 \x03(\x0b\x32?.dapr.proto.runtime.v1.ConversationAlpha1Result.ParametersEntry\x1aG\n\x0fParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any:\x02\x38\x01\"\x84\x01\n\x1a\x43onversationAlpha1Response\x12\x16\n\tcontextID\x18\x01 \x01(\tH\x00\x88\x01\x01\x12@\n\x07outputs\x18\x02 \x03(\x0b\x32/.dapr.proto.runtime.v1.ConversationAlpha1ResultB\x0c\n\n_contextID*W\n\x16PubsubSubscriptionType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0f\n\x0b\x44\x45\x43LARATIVE\x10\x01\x12\x10\n\x0cPROGRAMMATIC\x10\x02\x12\r\n\tSTREAMING\x10\x03\x32\xb5\x31\n\x04\x44\x61pr\x12\x64\n\rInvokeService\x12+.dapr.proto.runtime.v1.InvokeServiceRequest\x1a$.dapr.proto.common.v1.InvokeResponse\"\x00\x12]\n\x08GetState\x12&.dapr.proto.runtime.v1.GetStateRequest\x1a\'.dapr.proto.runtime.v1.GetStateResponse\"\x00\x12i\n\x0cGetBulkState\x12*.dapr.proto.runtime.v1.GetBulkStateRequest\x1a+.dapr.proto.runtime.v1.GetBulkStateResponse\"\x00\x12N\n\tSaveState\x12\'.dapr.proto.runtime.v1.SaveStateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12i\n\x10QueryStateAlpha1\x12(.dapr.proto.runtime.v1.QueryStateRequest\x1a).dapr.proto.runtime.v1.QueryStateResponse\"\x00\x12R\n\x0b\x44\x65leteState\x12).dapr.proto.runtime.v1.DeleteStateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Z\n\x0f\x44\x65leteBulkState\x12-.dapr.proto.runtime.v1.DeleteBulkStateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12j\n\x17\x45xecuteStateTransaction\x12\x35.dapr.proto.runtime.v1.ExecuteStateTransactionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12T\n\x0cPublishEvent\x12*.dapr.proto.runtime.v1.PublishEventRequest\x1a\x16.google.protobuf.Empty\"\x00\x12q\n\x16\x42ulkPublishEventAlpha1\x12).dapr.proto.runtime.v1.BulkPublishRequest\x1a*.dapr.proto.runtime.v1.BulkPublishResponse\"\x00\x12\x97\x01\n\x1aSubscribeTopicEventsAlpha1\x12\x38.dapr.proto.runtime.v1.SubscribeTopicEventsRequestAlpha1\x1a\x39.dapr.proto.runtime.v1.SubscribeTopicEventsResponseAlpha1\"\x00(\x01\x30\x01\x12l\n\rInvokeBinding\x12+.dapr.proto.runtime.v1.InvokeBindingRequest\x1a,.dapr.proto.runtime.v1.InvokeBindingResponse\"\x00\x12`\n\tGetSecret\x12\'.dapr.proto.runtime.v1.GetSecretRequest\x1a(.dapr.proto.runtime.v1.GetSecretResponse\"\x00\x12l\n\rGetBulkSecret\x12+.dapr.proto.runtime.v1.GetBulkSecretRequest\x1a,.dapr.proto.runtime.v1.GetBulkSecretResponse\"\x00\x12`\n\x12RegisterActorTimer\x12\x30.dapr.proto.runtime.v1.RegisterActorTimerRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x64\n\x14UnregisterActorTimer\x12\x32.dapr.proto.runtime.v1.UnregisterActorTimerRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x66\n\x15RegisterActorReminder\x12\x33.dapr.proto.runtime.v1.RegisterActorReminderRequest\x1a\x16.google.protobuf.Empty\"\x00\x12j\n\x17UnregisterActorReminder\x12\x35.dapr.proto.runtime.v1.UnregisterActorReminderRequest\x1a\x16.google.protobuf.Empty\"\x00\x12l\n\rGetActorState\x12+.dapr.proto.runtime.v1.GetActorStateRequest\x1a,.dapr.proto.runtime.v1.GetActorStateResponse\"\x00\x12t\n\x1c\x45xecuteActorStateTransaction\x12:.dapr.proto.runtime.v1.ExecuteActorStateTransactionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x66\n\x0bInvokeActor\x12).dapr.proto.runtime.v1.InvokeActorRequest\x1a*.dapr.proto.runtime.v1.InvokeActorResponse\"\x00\x12{\n\x16GetConfigurationAlpha1\x12..dapr.proto.runtime.v1.GetConfigurationRequest\x1a/.dapr.proto.runtime.v1.GetConfigurationResponse\"\x00\x12u\n\x10GetConfiguration\x12..dapr.proto.runtime.v1.GetConfigurationRequest\x1a/.dapr.proto.runtime.v1.GetConfigurationResponse\"\x00\x12\x8f\x01\n\x1cSubscribeConfigurationAlpha1\x12\x34.dapr.proto.runtime.v1.SubscribeConfigurationRequest\x1a\x35.dapr.proto.runtime.v1.SubscribeConfigurationResponse\"\x00\x30\x01\x12\x89\x01\n\x16SubscribeConfiguration\x12\x34.dapr.proto.runtime.v1.SubscribeConfigurationRequest\x1a\x35.dapr.proto.runtime.v1.SubscribeConfigurationResponse\"\x00\x30\x01\x12\x93\x01\n\x1eUnsubscribeConfigurationAlpha1\x12\x36.dapr.proto.runtime.v1.UnsubscribeConfigurationRequest\x1a\x37.dapr.proto.runtime.v1.UnsubscribeConfigurationResponse\"\x00\x12\x8d\x01\n\x18UnsubscribeConfiguration\x12\x36.dapr.proto.runtime.v1.UnsubscribeConfigurationRequest\x1a\x37.dapr.proto.runtime.v1.UnsubscribeConfigurationResponse\"\x00\x12`\n\rTryLockAlpha1\x12%.dapr.proto.runtime.v1.TryLockRequest\x1a&.dapr.proto.runtime.v1.TryLockResponse\"\x00\x12]\n\x0cUnlockAlpha1\x12$.dapr.proto.runtime.v1.UnlockRequest\x1a%.dapr.proto.runtime.v1.UnlockResponse\"\x00\x12\x62\n\rEncryptAlpha1\x12%.dapr.proto.runtime.v1.EncryptRequest\x1a&.dapr.proto.runtime.v1.EncryptResponse(\x01\x30\x01\x12\x62\n\rDecryptAlpha1\x12%.dapr.proto.runtime.v1.DecryptRequest\x1a&.dapr.proto.runtime.v1.DecryptResponse(\x01\x30\x01\x12\x66\n\x0bGetMetadata\x12).dapr.proto.runtime.v1.GetMetadataRequest\x1a*.dapr.proto.runtime.v1.GetMetadataResponse\"\x00\x12R\n\x0bSetMetadata\x12).dapr.proto.runtime.v1.SetMetadataRequest\x1a\x16.google.protobuf.Empty\"\x00\x12m\n\x12SubtleGetKeyAlpha1\x12*.dapr.proto.runtime.v1.SubtleGetKeyRequest\x1a+.dapr.proto.runtime.v1.SubtleGetKeyResponse\x12p\n\x13SubtleEncryptAlpha1\x12+.dapr.proto.runtime.v1.SubtleEncryptRequest\x1a,.dapr.proto.runtime.v1.SubtleEncryptResponse\x12p\n\x13SubtleDecryptAlpha1\x12+.dapr.proto.runtime.v1.SubtleDecryptRequest\x1a,.dapr.proto.runtime.v1.SubtleDecryptResponse\x12p\n\x13SubtleWrapKeyAlpha1\x12+.dapr.proto.runtime.v1.SubtleWrapKeyRequest\x1a,.dapr.proto.runtime.v1.SubtleWrapKeyResponse\x12v\n\x15SubtleUnwrapKeyAlpha1\x12-.dapr.proto.runtime.v1.SubtleUnwrapKeyRequest\x1a..dapr.proto.runtime.v1.SubtleUnwrapKeyResponse\x12g\n\x10SubtleSignAlpha1\x12(.dapr.proto.runtime.v1.SubtleSignRequest\x1a).dapr.proto.runtime.v1.SubtleSignResponse\x12m\n\x12SubtleVerifyAlpha1\x12*.dapr.proto.runtime.v1.SubtleVerifyRequest\x1a+.dapr.proto.runtime.v1.SubtleVerifyResponse\x12r\n\x13StartWorkflowAlpha1\x12+.dapr.proto.runtime.v1.StartWorkflowRequest\x1a,.dapr.proto.runtime.v1.StartWorkflowResponse\"\x00\x12l\n\x11GetWorkflowAlpha1\x12).dapr.proto.runtime.v1.GetWorkflowRequest\x1a*.dapr.proto.runtime.v1.GetWorkflowResponse\"\x00\x12\\\n\x13PurgeWorkflowAlpha1\x12+.dapr.proto.runtime.v1.PurgeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x64\n\x17TerminateWorkflowAlpha1\x12/.dapr.proto.runtime.v1.TerminateWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\\\n\x13PauseWorkflowAlpha1\x12+.dapr.proto.runtime.v1.PauseWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12^\n\x14ResumeWorkflowAlpha1\x12,.dapr.proto.runtime.v1.ResumeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x66\n\x18RaiseEventWorkflowAlpha1\x12\x30.dapr.proto.runtime.v1.RaiseEventWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12q\n\x12StartWorkflowBeta1\x12+.dapr.proto.runtime.v1.StartWorkflowRequest\x1a,.dapr.proto.runtime.v1.StartWorkflowResponse\"\x00\x12k\n\x10GetWorkflowBeta1\x12).dapr.proto.runtime.v1.GetWorkflowRequest\x1a*.dapr.proto.runtime.v1.GetWorkflowResponse\"\x00\x12[\n\x12PurgeWorkflowBeta1\x12+.dapr.proto.runtime.v1.PurgeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x63\n\x16TerminateWorkflowBeta1\x12/.dapr.proto.runtime.v1.TerminateWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12[\n\x12PauseWorkflowBeta1\x12+.dapr.proto.runtime.v1.PauseWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12]\n\x13ResumeWorkflowBeta1\x12,.dapr.proto.runtime.v1.ResumeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x65\n\x17RaiseEventWorkflowBeta1\x12\x30.dapr.proto.runtime.v1.RaiseEventWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12L\n\x08Shutdown\x12&.dapr.proto.runtime.v1.ShutdownRequest\x1a\x16.google.protobuf.Empty\"\x00\x12l\n\x11ScheduleJobAlpha1\x12).dapr.proto.runtime.v1.ScheduleJobRequest\x1a*.dapr.proto.runtime.v1.ScheduleJobResponse\"\x00\x12]\n\x0cGetJobAlpha1\x12$.dapr.proto.runtime.v1.GetJobRequest\x1a%.dapr.proto.runtime.v1.GetJobResponse\"\x00\x12\x66\n\x0f\x44\x65leteJobAlpha1\x12\'.dapr.proto.runtime.v1.DeleteJobRequest\x1a(.dapr.proto.runtime.v1.DeleteJobResponse\"\x00\x12w\n\x0e\x43onverseAlpha1\x12\x30.dapr.proto.runtime.v1.ConversationAlpha1Request\x1a\x31.dapr.proto.runtime.v1.ConversationAlpha1Response\"\x00\x42i\n\nio.dapr.v1B\nDaprProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\xaa\x02\x1b\x44\x61pr.Client.Autogen.Grpc.v1b\x06proto3') - -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dapr.proto.runtime.v1.dapr_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - - DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b'\n\nio.dapr.v1B\nDaprProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\252\002\033Dapr.Client.Autogen.Grpc.v1' - _GETSTATEREQUEST_METADATAENTRY._options = None - _GETSTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' - _GETBULKSTATEREQUEST_METADATAENTRY._options = None - _GETBULKSTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' - _BULKSTATEITEM_METADATAENTRY._options = None - _BULKSTATEITEM_METADATAENTRY._serialized_options = b'8\001' - _GETSTATERESPONSE_METADATAENTRY._options = None - _GETSTATERESPONSE_METADATAENTRY._serialized_options = b'8\001' - _DELETESTATEREQUEST_METADATAENTRY._options = None - _DELETESTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' - _QUERYSTATEREQUEST_METADATAENTRY._options = None - _QUERYSTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' - _QUERYSTATERESPONSE_METADATAENTRY._options = None - _QUERYSTATERESPONSE_METADATAENTRY._serialized_options = b'8\001' - _PUBLISHEVENTREQUEST_METADATAENTRY._options = None - _PUBLISHEVENTREQUEST_METADATAENTRY._serialized_options = b'8\001' - _BULKPUBLISHREQUEST_METADATAENTRY._options = None - _BULKPUBLISHREQUEST_METADATAENTRY._serialized_options = b'8\001' - _BULKPUBLISHREQUESTENTRY_METADATAENTRY._options = None - _BULKPUBLISHREQUESTENTRY_METADATAENTRY._serialized_options = b'8\001' - _SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY._options = None - _SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY._serialized_options = b'8\001' - _INVOKEBINDINGREQUEST_METADATAENTRY._options = None - _INVOKEBINDINGREQUEST_METADATAENTRY._serialized_options = b'8\001' - _INVOKEBINDINGRESPONSE_METADATAENTRY._options = None - _INVOKEBINDINGRESPONSE_METADATAENTRY._serialized_options = b'8\001' - _GETSECRETREQUEST_METADATAENTRY._options = None - _GETSECRETREQUEST_METADATAENTRY._serialized_options = b'8\001' - _GETSECRETRESPONSE_DATAENTRY._options = None - _GETSECRETRESPONSE_DATAENTRY._serialized_options = b'8\001' - _GETBULKSECRETREQUEST_METADATAENTRY._options = None - _GETBULKSECRETREQUEST_METADATAENTRY._serialized_options = b'8\001' - _SECRETRESPONSE_SECRETSENTRY._options = None - _SECRETRESPONSE_SECRETSENTRY._serialized_options = b'8\001' - _GETBULKSECRETRESPONSE_DATAENTRY._options = None - _GETBULKSECRETRESPONSE_DATAENTRY._serialized_options = b'8\001' - _EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY._options = None - _EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY._serialized_options = b'8\001' - _GETACTORSTATERESPONSE_METADATAENTRY._options = None - _GETACTORSTATERESPONSE_METADATAENTRY._serialized_options = b'8\001' - _TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY._options = None - _TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY._serialized_options = b'8\001' - _INVOKEACTORREQUEST_METADATAENTRY._options = None - _INVOKEACTORREQUEST_METADATAENTRY._serialized_options = b'8\001' - _GETMETADATARESPONSE_EXTENDEDMETADATAENTRY._options = None - _GETMETADATARESPONSE_EXTENDEDMETADATAENTRY._serialized_options = b'8\001' - _GETMETADATARESPONSE.fields_by_name['active_actors_count']._options = None - _GETMETADATARESPONSE.fields_by_name['active_actors_count']._serialized_options = b'\030\001' - _PUBSUBSUBSCRIPTION_METADATAENTRY._options = None - _PUBSUBSUBSCRIPTION_METADATAENTRY._serialized_options = b'8\001' - _GETCONFIGURATIONREQUEST_METADATAENTRY._options = None - _GETCONFIGURATIONREQUEST_METADATAENTRY._serialized_options = b'8\001' - _GETCONFIGURATIONRESPONSE_ITEMSENTRY._options = None - _GETCONFIGURATIONRESPONSE_ITEMSENTRY._serialized_options = b'8\001' - _SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY._options = None - _SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY._serialized_options = b'8\001' - _SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY._options = None - _SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY._serialized_options = b'8\001' - _GETWORKFLOWRESPONSE_PROPERTIESENTRY._options = None - _GETWORKFLOWRESPONSE_PROPERTIESENTRY._serialized_options = b'8\001' - _STARTWORKFLOWREQUEST_OPTIONSENTRY._options = None - _STARTWORKFLOWREQUEST_OPTIONSENTRY._serialized_options = b'8\001' - _CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY._options = None - _CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY._serialized_options = b'8\001' - _CONVERSATIONALPHA1REQUEST_METADATAENTRY._options = None - _CONVERSATIONALPHA1REQUEST_METADATAENTRY._serialized_options = b'8\001' - _CONVERSATIONALPHA1RESULT_PARAMETERSENTRY._options = None - _CONVERSATIONALPHA1RESULT_PARAMETERSENTRY._serialized_options = b'8\001' - _globals['_PUBSUBSUBSCRIPTIONTYPE']._serialized_start=16034 - _globals['_PUBSUBSUBSCRIPTIONTYPE']._serialized_end=16121 - _globals['_INVOKESERVICEREQUEST']._serialized_start=224 - _globals['_INVOKESERVICEREQUEST']._serialized_end=312 - _globals['_GETSTATEREQUEST']._serialized_start=315 - _globals['_GETSTATEREQUEST']._serialized_end=560 - _globals['_GETSTATEREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_GETSTATEREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_GETBULKSTATEREQUEST']._serialized_start=563 - _globals['_GETBULKSTATEREQUEST']._serialized_end=764 - _globals['_GETBULKSTATEREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_GETBULKSTATEREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_GETBULKSTATERESPONSE']._serialized_start=766 - _globals['_GETBULKSTATERESPONSE']._serialized_end=841 - _globals['_BULKSTATEITEM']._serialized_start=844 - _globals['_BULKSTATEITEM']._serialized_end=1034 - _globals['_BULKSTATEITEM_METADATAENTRY']._serialized_start=513 - _globals['_BULKSTATEITEM_METADATAENTRY']._serialized_end=560 - _globals['_GETSTATERESPONSE']._serialized_start=1037 - _globals['_GETSTATERESPONSE']._serialized_end=1205 - _globals['_GETSTATERESPONSE_METADATAENTRY']._serialized_start=513 - _globals['_GETSTATERESPONSE_METADATAENTRY']._serialized_end=560 - _globals['_DELETESTATEREQUEST']._serialized_start=1208 - _globals['_DELETESTATEREQUEST']._serialized_end=1480 - _globals['_DELETESTATEREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_DELETESTATEREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_DELETEBULKSTATEREQUEST']._serialized_start=1482 - _globals['_DELETEBULKSTATEREQUEST']._serialized_end=1575 - _globals['_SAVESTATEREQUEST']._serialized_start=1577 - _globals['_SAVESTATEREQUEST']._serialized_end=1664 - _globals['_QUERYSTATEREQUEST']._serialized_start=1667 - _globals['_QUERYSTATEREQUEST']._serialized_end=1855 - _globals['_QUERYSTATEREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_QUERYSTATEREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_QUERYSTATEITEM']._serialized_start=1857 - _globals['_QUERYSTATEITEM']._serialized_end=1929 - _globals['_QUERYSTATERESPONSE']._serialized_start=1932 - _globals['_QUERYSTATERESPONSE']._serialized_end=2147 - _globals['_QUERYSTATERESPONSE_METADATAENTRY']._serialized_start=513 - _globals['_QUERYSTATERESPONSE_METADATAENTRY']._serialized_end=560 - _globals['_PUBLISHEVENTREQUEST']._serialized_start=2150 - _globals['_PUBLISHEVENTREQUEST']._serialized_end=2373 - _globals['_PUBLISHEVENTREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_PUBLISHEVENTREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_BULKPUBLISHREQUEST']._serialized_start=2376 - _globals['_BULKPUBLISHREQUEST']._serialized_end=2621 - _globals['_BULKPUBLISHREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_BULKPUBLISHREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_BULKPUBLISHREQUESTENTRY']._serialized_start=2624 - _globals['_BULKPUBLISHREQUESTENTRY']._serialized_end=2833 - _globals['_BULKPUBLISHREQUESTENTRY_METADATAENTRY']._serialized_start=513 - _globals['_BULKPUBLISHREQUESTENTRY_METADATAENTRY']._serialized_end=560 - _globals['_BULKPUBLISHRESPONSE']._serialized_start=2835 - _globals['_BULKPUBLISHRESPONSE']._serialized_end=2934 - _globals['_BULKPUBLISHRESPONSEFAILEDENTRY']._serialized_start=2936 - _globals['_BULKPUBLISHRESPONSEFAILEDENTRY']._serialized_end=3001 - _globals['_SUBSCRIBETOPICEVENTSREQUESTALPHA1']._serialized_start=3004 - _globals['_SUBSCRIBETOPICEVENTSREQUESTALPHA1']._serialized_end=3264 - _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1']._serialized_start=3267 - _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1']._serialized_end=3545 - _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY']._serialized_start=513 - _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY']._serialized_end=560 - _globals['_SUBSCRIBETOPICEVENTSREQUESTPROCESSEDALPHA1']._serialized_start=3547 - _globals['_SUBSCRIBETOPICEVENTSREQUESTPROCESSEDALPHA1']._serialized_end=3662 - _globals['_SUBSCRIBETOPICEVENTSRESPONSEALPHA1']._serialized_start=3665 - _globals['_SUBSCRIBETOPICEVENTSRESPONSEALPHA1']._serialized_end=3902 - _globals['_SUBSCRIBETOPICEVENTSRESPONSEINITIALALPHA1']._serialized_start=3904 - _globals['_SUBSCRIBETOPICEVENTSRESPONSEINITIALALPHA1']._serialized_end=3947 - _globals['_INVOKEBINDINGREQUEST']._serialized_start=3950 - _globals['_INVOKEBINDINGREQUEST']._serialized_end=4145 - _globals['_INVOKEBINDINGREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_INVOKEBINDINGREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_INVOKEBINDINGRESPONSE']._serialized_start=4148 - _globals['_INVOKEBINDINGRESPONSE']._serialized_end=4312 - _globals['_INVOKEBINDINGRESPONSE_METADATAENTRY']._serialized_start=513 - _globals['_INVOKEBINDINGRESPONSE_METADATAENTRY']._serialized_end=560 - _globals['_GETSECRETREQUEST']._serialized_start=4315 - _globals['_GETSECRETREQUEST']._serialized_end=4499 - _globals['_GETSECRETREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_GETSECRETREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_GETSECRETRESPONSE']._serialized_start=4502 - _globals['_GETSECRETRESPONSE']._serialized_end=4632 - _globals['_GETSECRETRESPONSE_DATAENTRY']._serialized_start=4589 - _globals['_GETSECRETRESPONSE_DATAENTRY']._serialized_end=4632 - _globals['_GETBULKSECRETREQUEST']._serialized_start=4635 - _globals['_GETBULKSECRETREQUEST']._serialized_end=4814 - _globals['_GETBULKSECRETREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_GETBULKSECRETREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_SECRETRESPONSE']._serialized_start=4817 - _globals['_SECRETRESPONSE']._serialized_end=4950 - _globals['_SECRETRESPONSE_SECRETSENTRY']._serialized_start=4904 - _globals['_SECRETRESPONSE_SECRETSENTRY']._serialized_end=4950 - _globals['_GETBULKSECRETRESPONSE']._serialized_start=4953 - _globals['_GETBULKSECRETRESPONSE']._serialized_end=5130 - _globals['_GETBULKSECRETRESPONSE_DATAENTRY']._serialized_start=5048 - _globals['_GETBULKSECRETRESPONSE_DATAENTRY']._serialized_end=5130 - _globals['_TRANSACTIONALSTATEOPERATION']._serialized_start=5132 - _globals['_TRANSACTIONALSTATEOPERATION']._serialized_end=5234 - _globals['_EXECUTESTATETRANSACTIONREQUEST']._serialized_start=5237 - _globals['_EXECUTESTATETRANSACTIONREQUEST']._serialized_end=5496 - _globals['_EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_REGISTERACTORTIMERREQUEST']._serialized_start=5499 - _globals['_REGISTERACTORTIMERREQUEST']._serialized_end=5686 - _globals['_UNREGISTERACTORTIMERREQUEST']._serialized_start=5688 - _globals['_UNREGISTERACTORTIMERREQUEST']._serialized_end=5789 - _globals['_REGISTERACTORREMINDERREQUEST']._serialized_start=5792 - _globals['_REGISTERACTORREMINDERREQUEST']._serialized_end=5964 - _globals['_UNREGISTERACTORREMINDERREQUEST']._serialized_start=5966 - _globals['_UNREGISTERACTORREMINDERREQUEST']._serialized_end=6070 - _globals['_GETACTORSTATEREQUEST']._serialized_start=6072 - _globals['_GETACTORSTATEREQUEST']._serialized_end=6165 - _globals['_GETACTORSTATERESPONSE']._serialized_start=6168 - _globals['_GETACTORSTATERESPONSE']._serialized_end=6332 - _globals['_GETACTORSTATERESPONSE_METADATAENTRY']._serialized_start=513 - _globals['_GETACTORSTATERESPONSE_METADATAENTRY']._serialized_end=560 - _globals['_EXECUTEACTORSTATETRANSACTIONREQUEST']._serialized_start=6335 - _globals['_EXECUTEACTORSTATETRANSACTIONREQUEST']._serialized_end=6507 - _globals['_TRANSACTIONALACTORSTATEOPERATION']._serialized_start=6510 - _globals['_TRANSACTIONALACTORSTATEOPERATION']._serialized_end=6755 - _globals['_TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY']._serialized_start=513 - _globals['_TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY']._serialized_end=560 - _globals['_INVOKEACTORREQUEST']._serialized_start=6758 - _globals['_INVOKEACTORREQUEST']._serialized_end=6990 - _globals['_INVOKEACTORREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_INVOKEACTORREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_INVOKEACTORRESPONSE']._serialized_start=6992 - _globals['_INVOKEACTORRESPONSE']._serialized_end=7027 - _globals['_GETMETADATAREQUEST']._serialized_start=7029 - _globals['_GETMETADATAREQUEST']._serialized_end=7049 - _globals['_GETMETADATARESPONSE']._serialized_start=7052 - _globals['_GETMETADATARESPONSE']._serialized_end=7847 - _globals['_GETMETADATARESPONSE_EXTENDEDMETADATAENTRY']._serialized_start=7792 - _globals['_GETMETADATARESPONSE_EXTENDEDMETADATAENTRY']._serialized_end=7847 - _globals['_ACTORRUNTIME']._serialized_start=7850 - _globals['_ACTORRUNTIME']._serialized_end=8166 - _globals['_ACTORRUNTIME_ACTORRUNTIMESTATUS']._serialized_start=8101 - _globals['_ACTORRUNTIME_ACTORRUNTIMESTATUS']._serialized_end=8166 - _globals['_ACTIVEACTORSCOUNT']._serialized_start=8168 - _globals['_ACTIVEACTORSCOUNT']._serialized_end=8216 - _globals['_REGISTEREDCOMPONENTS']._serialized_start=8218 - _globals['_REGISTEREDCOMPONENTS']._serialized_end=8307 - _globals['_METADATAHTTPENDPOINT']._serialized_start=8309 - _globals['_METADATAHTTPENDPOINT']._serialized_end=8351 - _globals['_APPCONNECTIONPROPERTIES']._serialized_start=8354 - _globals['_APPCONNECTIONPROPERTIES']._serialized_end=8563 - _globals['_APPCONNECTIONHEALTHPROPERTIES']._serialized_start=8566 - _globals['_APPCONNECTIONHEALTHPROPERTIES']._serialized_end=8786 - _globals['_PUBSUBSUBSCRIPTION']._serialized_start=8789 - _globals['_PUBSUBSUBSCRIPTION']._serialized_end=9179 - _globals['_PUBSUBSUBSCRIPTION_METADATAENTRY']._serialized_start=513 - _globals['_PUBSUBSUBSCRIPTION_METADATAENTRY']._serialized_end=560 - _globals['_PUBSUBSUBSCRIPTIONRULES']._serialized_start=9181 - _globals['_PUBSUBSUBSCRIPTIONRULES']._serialized_end=9268 - _globals['_PUBSUBSUBSCRIPTIONRULE']._serialized_start=9270 - _globals['_PUBSUBSUBSCRIPTIONRULE']._serialized_end=9323 - _globals['_SETMETADATAREQUEST']._serialized_start=9325 - _globals['_SETMETADATAREQUEST']._serialized_end=9373 - _globals['_GETCONFIGURATIONREQUEST']._serialized_start=9376 - _globals['_GETCONFIGURATIONREQUEST']._serialized_end=9564 - _globals['_GETCONFIGURATIONREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_GETCONFIGURATIONREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_GETCONFIGURATIONRESPONSE']._serialized_start=9567 - _globals['_GETCONFIGURATIONRESPONSE']._serialized_end=9755 - _globals['_GETCONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_start=9670 - _globals['_GETCONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_end=9755 - _globals['_SUBSCRIBECONFIGURATIONREQUEST']._serialized_start=9758 - _globals['_SUBSCRIBECONFIGURATIONREQUEST']._serialized_end=9958 - _globals['_SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY']._serialized_start=513 - _globals['_SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY']._serialized_end=560 - _globals['_UNSUBSCRIBECONFIGURATIONREQUEST']._serialized_start=9960 - _globals['_UNSUBSCRIBECONFIGURATIONREQUEST']._serialized_end=10025 - _globals['_SUBSCRIBECONFIGURATIONRESPONSE']._serialized_start=10028 - _globals['_SUBSCRIBECONFIGURATIONRESPONSE']._serialized_end=10240 - _globals['_SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_start=9670 - _globals['_SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_end=9755 - _globals['_UNSUBSCRIBECONFIGURATIONRESPONSE']._serialized_start=10242 - _globals['_UNSUBSCRIBECONFIGURATIONRESPONSE']._serialized_end=10305 - _globals['_TRYLOCKREQUEST']._serialized_start=10308 - _globals['_TRYLOCKREQUEST']._serialized_end=10463 - _globals['_TRYLOCKRESPONSE']._serialized_start=10465 - _globals['_TRYLOCKRESPONSE']._serialized_end=10499 - _globals['_UNLOCKREQUEST']._serialized_start=10501 - _globals['_UNLOCKREQUEST']._serialized_end=10611 - _globals['_UNLOCKRESPONSE']._serialized_start=10614 - _globals['_UNLOCKRESPONSE']._serialized_end=10788 - _globals['_UNLOCKRESPONSE_STATUS']._serialized_start=10694 - _globals['_UNLOCKRESPONSE_STATUS']._serialized_end=10788 - _globals['_SUBTLEGETKEYREQUEST']._serialized_start=10791 - _globals['_SUBTLEGETKEYREQUEST']._serialized_end=10967 - _globals['_SUBTLEGETKEYREQUEST_KEYFORMAT']._serialized_start=10937 - _globals['_SUBTLEGETKEYREQUEST_KEYFORMAT']._serialized_end=10967 - _globals['_SUBTLEGETKEYRESPONSE']._serialized_start=10969 - _globals['_SUBTLEGETKEYRESPONSE']._serialized_end=11036 - _globals['_SUBTLEENCRYPTREQUEST']._serialized_start=11039 - _globals['_SUBTLEENCRYPTREQUEST']._serialized_end=11221 - _globals['_SUBTLEENCRYPTRESPONSE']._serialized_start=11223 - _globals['_SUBTLEENCRYPTRESPONSE']._serialized_end=11279 - _globals['_SUBTLEDECRYPTREQUEST']._serialized_start=11282 - _globals['_SUBTLEDECRYPTREQUEST']._serialized_end=11478 - _globals['_SUBTLEDECRYPTRESPONSE']._serialized_start=11480 - _globals['_SUBTLEDECRYPTRESPONSE']._serialized_end=11522 - _globals['_SUBTLEWRAPKEYREQUEST']._serialized_start=11525 - _globals['_SUBTLEWRAPKEYREQUEST']._serialized_end=11725 - _globals['_SUBTLEWRAPKEYRESPONSE']._serialized_start=11727 - _globals['_SUBTLEWRAPKEYRESPONSE']._serialized_end=11796 - _globals['_SUBTLEUNWRAPKEYREQUEST']._serialized_start=11799 - _globals['_SUBTLEUNWRAPKEYREQUEST']._serialized_end=12010 - _globals['_SUBTLEUNWRAPKEYRESPONSE']._serialized_start=12012 - _globals['_SUBTLEUNWRAPKEYRESPONSE']._serialized_end=12074 - _globals['_SUBTLESIGNREQUEST']._serialized_start=12076 - _globals['_SUBTLESIGNREQUEST']._serialized_end=12196 - _globals['_SUBTLESIGNRESPONSE']._serialized_start=12198 - _globals['_SUBTLESIGNRESPONSE']._serialized_end=12237 - _globals['_SUBTLEVERIFYREQUEST']._serialized_start=12240 - _globals['_SUBTLEVERIFYREQUEST']._serialized_end=12381 - _globals['_SUBTLEVERIFYRESPONSE']._serialized_start=12383 - _globals['_SUBTLEVERIFYRESPONSE']._serialized_end=12420 - _globals['_ENCRYPTREQUEST']._serialized_start=12423 - _globals['_ENCRYPTREQUEST']._serialized_end=12556 - _globals['_ENCRYPTREQUESTOPTIONS']._serialized_start=12559 - _globals['_ENCRYPTREQUESTOPTIONS']._serialized_end=12813 - _globals['_ENCRYPTRESPONSE']._serialized_start=12815 - _globals['_ENCRYPTRESPONSE']._serialized_end=12886 - _globals['_DECRYPTREQUEST']._serialized_start=12889 - _globals['_DECRYPTREQUEST']._serialized_end=13022 - _globals['_DECRYPTREQUESTOPTIONS']._serialized_start=13024 - _globals['_DECRYPTREQUESTOPTIONS']._serialized_end=13113 - _globals['_DECRYPTRESPONSE']._serialized_start=13115 - _globals['_DECRYPTRESPONSE']._serialized_end=13186 - _globals['_GETWORKFLOWREQUEST']._serialized_start=13188 - _globals['_GETWORKFLOWREQUEST']._serialized_end=13288 - _globals['_GETWORKFLOWRESPONSE']._serialized_start=13291 - _globals['_GETWORKFLOWRESPONSE']._serialized_end=13679 - _globals['_GETWORKFLOWRESPONSE_PROPERTIESENTRY']._serialized_start=13630 - _globals['_GETWORKFLOWRESPONSE_PROPERTIESENTRY']._serialized_end=13679 - _globals['_STARTWORKFLOWREQUEST']._serialized_start=13682 - _globals['_STARTWORKFLOWREQUEST']._serialized_end=13959 - _globals['_STARTWORKFLOWREQUEST_OPTIONSENTRY']._serialized_start=13913 - _globals['_STARTWORKFLOWREQUEST_OPTIONSENTRY']._serialized_end=13959 - _globals['_STARTWORKFLOWRESPONSE']._serialized_start=13961 - _globals['_STARTWORKFLOWRESPONSE']._serialized_end=14017 - _globals['_TERMINATEWORKFLOWREQUEST']._serialized_start=14019 - _globals['_TERMINATEWORKFLOWREQUEST']._serialized_end=14125 - _globals['_PAUSEWORKFLOWREQUEST']._serialized_start=14127 - _globals['_PAUSEWORKFLOWREQUEST']._serialized_end=14229 - _globals['_RESUMEWORKFLOWREQUEST']._serialized_start=14231 - _globals['_RESUMEWORKFLOWREQUEST']._serialized_end=14334 - _globals['_RAISEEVENTWORKFLOWREQUEST']._serialized_start=14337 - _globals['_RAISEEVENTWORKFLOWREQUEST']._serialized_end=14495 - _globals['_PURGEWORKFLOWREQUEST']._serialized_start=14497 - _globals['_PURGEWORKFLOWREQUEST']._serialized_end=14599 - _globals['_SHUTDOWNREQUEST']._serialized_start=14601 - _globals['_SHUTDOWNREQUEST']._serialized_end=14618 - _globals['_JOB']._serialized_start=14621 - _globals['_JOB']._serialized_end=14853 - _globals['_SCHEDULEJOBREQUEST']._serialized_start=14855 - _globals['_SCHEDULEJOBREQUEST']._serialized_end=14916 - _globals['_SCHEDULEJOBRESPONSE']._serialized_start=14918 - _globals['_SCHEDULEJOBRESPONSE']._serialized_end=14939 - _globals['_GETJOBREQUEST']._serialized_start=14941 - _globals['_GETJOBREQUEST']._serialized_end=14970 - _globals['_GETJOBRESPONSE']._serialized_start=14972 - _globals['_GETJOBRESPONSE']._serialized_end=15029 - _globals['_DELETEJOBREQUEST']._serialized_start=15031 - _globals['_DELETEJOBREQUEST']._serialized_end=15063 - _globals['_DELETEJOBRESPONSE']._serialized_start=15065 - _globals['_DELETEJOBRESPONSE']._serialized_end=15084 - _globals['_CONVERSATIONALPHA1REQUEST']._serialized_start=15087 - _globals['_CONVERSATIONALPHA1REQUEST']._serialized_end=15592 - _globals['_CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY']._serialized_start=15429 - _globals['_CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY']._serialized_end=15500 - _globals['_CONVERSATIONALPHA1REQUEST_METADATAENTRY']._serialized_start=513 - _globals['_CONVERSATIONALPHA1REQUEST_METADATAENTRY']._serialized_end=560 - _globals['_CONVERSATIONINPUT']._serialized_start=15594 - _globals['_CONVERSATIONINPUT']._serialized_end=15694 - _globals['_CONVERSATIONALPHA1RESULT']._serialized_start=15697 - _globals['_CONVERSATIONALPHA1RESULT']._serialized_end=15897 - _globals['_CONVERSATIONALPHA1RESULT_PARAMETERSENTRY']._serialized_start=15429 - _globals['_CONVERSATIONALPHA1RESULT_PARAMETERSENTRY']._serialized_end=15500 - _globals['_CONVERSATIONALPHA1RESPONSE']._serialized_start=15900 - _globals['_CONVERSATIONALPHA1RESPONSE']._serialized_end=16032 - _globals['_DAPR']._serialized_start=16124 - _globals['_DAPR']._serialized_end=22449 -# @@protoc_insertion_point(module_scope) +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: dapr/proto/runtime/v1/dapr.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import any_pb2 as google_dot_protobuf_dot_any__pb2 +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 +from dapr.proto.runtime.v1 import appcallback_pb2 as dapr_dot_proto_dot_runtime_dot_v1_dot_appcallback__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n dapr/proto/runtime/v1/dapr.proto\x12\x15\x64\x61pr.proto.runtime.v1\x1a\x19google/protobuf/any.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a!dapr/proto/common/v1/common.proto\x1a\'dapr/proto/runtime/v1/appcallback.proto\"X\n\x14InvokeServiceRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x34\n\x07message\x18\x03 \x01(\x0b\x32#.dapr.proto.common.v1.InvokeRequest\"\xf5\x01\n\x0fGetStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12H\n\x0b\x63onsistency\x18\x03 \x01(\x0e\x32\x33.dapr.proto.common.v1.StateOptions.StateConsistency\x12\x46\n\x08metadata\x18\x04 \x03(\x0b\x32\x34.dapr.proto.runtime.v1.GetStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc9\x01\n\x13GetBulkStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12\x13\n\x0bparallelism\x18\x03 \x01(\x05\x12J\n\x08metadata\x18\x04 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.GetBulkStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"K\n\x14GetBulkStateResponse\x12\x33\n\x05items\x18\x01 \x03(\x0b\x32$.dapr.proto.runtime.v1.BulkStateItem\"\xbe\x01\n\rBulkStateItem\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\x12\x44\n\x08metadata\x18\x05 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.BulkStateItem.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa8\x01\n\x10GetStateResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x02 \x01(\t\x12G\n\x08metadata\x18\x03 \x03(\x0b\x32\x35.dapr.proto.runtime.v1.GetStateResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x90\x02\n\x12\x44\x65leteStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12(\n\x04\x65tag\x18\x03 \x01(\x0b\x32\x1a.dapr.proto.common.v1.Etag\x12\x33\n\x07options\x18\x04 \x01(\x0b\x32\".dapr.proto.common.v1.StateOptions\x12I\n\x08metadata\x18\x05 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.DeleteStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"]\n\x16\x44\x65leteBulkStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12/\n\x06states\x18\x02 \x03(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\"W\n\x10SaveStateRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12/\n\x06states\x18\x02 \x03(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\"\xbc\x01\n\x11QueryStateRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\r\n\x05query\x18\x02 \x01(\t\x12H\n\x08metadata\x18\x03 \x03(\x0b\x32\x36.dapr.proto.runtime.v1.QueryStateRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"H\n\x0eQueryStateItem\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0c\n\x04\x65tag\x18\x03 \x01(\t\x12\r\n\x05\x65rror\x18\x04 \x01(\t\"\xd7\x01\n\x12QueryStateResponse\x12\x36\n\x07results\x18\x01 \x03(\x0b\x32%.dapr.proto.runtime.v1.QueryStateItem\x12\r\n\x05token\x18\x02 \x01(\t\x12I\n\x08metadata\x18\x03 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.QueryStateResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xdf\x01\n\x13PublishEventRequest\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\x12\x19\n\x11\x64\x61ta_content_type\x18\x04 \x01(\t\x12J\n\x08metadata\x18\x05 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.PublishEventRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xf5\x01\n\x12\x42ulkPublishRequest\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12?\n\x07\x65ntries\x18\x03 \x03(\x0b\x32..dapr.proto.runtime.v1.BulkPublishRequestEntry\x12I\n\x08metadata\x18\x04 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.BulkPublishRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xd1\x01\n\x17\x42ulkPublishRequestEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\r\n\x05\x65vent\x18\x02 \x01(\x0c\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12N\n\x08metadata\x18\x04 \x03(\x0b\x32<.dapr.proto.runtime.v1.BulkPublishRequestEntry.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"c\n\x13\x42ulkPublishResponse\x12L\n\rfailedEntries\x18\x01 \x03(\x0b\x32\x35.dapr.proto.runtime.v1.BulkPublishResponseFailedEntry\"A\n\x1e\x42ulkPublishResponseFailedEntry\x12\x10\n\x08\x65ntry_id\x18\x01 \x01(\t\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x84\x02\n!SubscribeTopicEventsRequestAlpha1\x12Z\n\x0finitial_request\x18\x01 \x01(\x0b\x32?.dapr.proto.runtime.v1.SubscribeTopicEventsRequestInitialAlpha1H\x00\x12\\\n\x0f\x65vent_processed\x18\x02 \x01(\x0b\x32\x41.dapr.proto.runtime.v1.SubscribeTopicEventsRequestProcessedAlpha1H\x00\x42%\n#subscribe_topic_events_request_type\"\x96\x02\n(SubscribeTopicEventsRequestInitialAlpha1\x12\x13\n\x0bpubsub_name\x18\x01 \x01(\t\x12\r\n\x05topic\x18\x02 \x01(\t\x12_\n\x08metadata\x18\x03 \x03(\x0b\x32M.dapr.proto.runtime.v1.SubscribeTopicEventsRequestInitialAlpha1.MetadataEntry\x12\x1e\n\x11\x64\x65\x61\x64_letter_topic\x18\x04 \x01(\tH\x00\x88\x01\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x14\n\x12_dead_letter_topic\"s\n*SubscribeTopicEventsRequestProcessedAlpha1\x12\n\n\x02id\x18\x01 \x01(\t\x12\x39\n\x06status\x18\x02 \x01(\x0b\x32).dapr.proto.runtime.v1.TopicEventResponse\"\xed\x01\n\"SubscribeTopicEventsResponseAlpha1\x12\\\n\x10initial_response\x18\x01 \x01(\x0b\x32@.dapr.proto.runtime.v1.SubscribeTopicEventsResponseInitialAlpha1H\x00\x12\x41\n\revent_message\x18\x02 \x01(\x0b\x32(.dapr.proto.runtime.v1.TopicEventRequestH\x00\x42&\n$subscribe_topic_events_response_type\"+\n)SubscribeTopicEventsResponseInitialAlpha1\"\xc3\x01\n\x14InvokeBindingRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12K\n\x08metadata\x18\x03 \x03(\x0b\x32\x39.dapr.proto.runtime.v1.InvokeBindingRequest.MetadataEntry\x12\x11\n\toperation\x18\x04 \x01(\t\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xa4\x01\n\x15InvokeBindingResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12L\n\x08metadata\x18\x02 \x03(\x0b\x32:.dapr.proto.runtime.v1.InvokeBindingResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb8\x01\n\x10GetSecretRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\x0b\n\x03key\x18\x02 \x01(\t\x12G\n\x08metadata\x18\x03 \x03(\x0b\x32\x35.dapr.proto.runtime.v1.GetSecretRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x82\x01\n\x11GetSecretResponse\x12@\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.GetSecretResponse.DataEntry\x1a+\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb3\x01\n\x14GetBulkSecretRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12K\n\x08metadata\x18\x02 \x03(\x0b\x32\x39.dapr.proto.runtime.v1.GetBulkSecretRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x85\x01\n\x0eSecretResponse\x12\x43\n\x07secrets\x18\x01 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.SecretResponse.SecretsEntry\x1a.\n\x0cSecretsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb1\x01\n\x15GetBulkSecretResponse\x12\x44\n\x04\x64\x61ta\x18\x01 \x03(\x0b\x32\x36.dapr.proto.runtime.v1.GetBulkSecretResponse.DataEntry\x1aR\n\tDataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x34\n\x05value\x18\x02 \x01(\x0b\x32%.dapr.proto.runtime.v1.SecretResponse:\x02\x38\x01\"f\n\x1bTransactionalStateOperation\x12\x15\n\roperationType\x18\x01 \x01(\t\x12\x30\n\x07request\x18\x02 \x01(\x0b\x32\x1f.dapr.proto.common.v1.StateItem\"\x83\x02\n\x1e\x45xecuteStateTransactionRequest\x12\x11\n\tstoreName\x18\x01 \x01(\t\x12\x46\n\noperations\x18\x02 \x03(\x0b\x32\x32.dapr.proto.runtime.v1.TransactionalStateOperation\x12U\n\x08metadata\x18\x03 \x03(\x0b\x32\x43.dapr.proto.runtime.v1.ExecuteStateTransactionRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbb\x01\n\x19RegisterActorTimerRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x19\n\x08\x64ue_time\x18\x04 \x01(\tR\x07\x64ueTime\x12\x0e\n\x06period\x18\x05 \x01(\t\x12\x10\n\x08\x63\x61llback\x18\x06 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x07 \x01(\x0c\x12\x0b\n\x03ttl\x18\x08 \x01(\t\"e\n\x1bUnregisterActorTimerRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\"\xac\x01\n\x1cRegisterActorReminderRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x19\n\x08\x64ue_time\x18\x04 \x01(\tR\x07\x64ueTime\x12\x0e\n\x06period\x18\x05 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x06 \x01(\x0c\x12\x0b\n\x03ttl\x18\x07 \x01(\t\"h\n\x1eUnregisterActorReminderRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0c\n\x04name\x18\x03 \x01(\t\"]\n\x14GetActorStateRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0b\n\x03key\x18\x03 \x01(\t\"\xa4\x01\n\x15GetActorStateResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12L\n\x08metadata\x18\x02 \x03(\x0b\x32:.dapr.proto.runtime.v1.GetActorStateResponse.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xac\x01\n#ExecuteActorStateTransactionRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12K\n\noperations\x18\x03 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.TransactionalActorStateOperation\"\xf5\x01\n TransactionalActorStateOperation\x12\x15\n\roperationType\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12#\n\x05value\x18\x03 \x01(\x0b\x32\x14.google.protobuf.Any\x12W\n\x08metadata\x18\x04 \x03(\x0b\x32\x45.dapr.proto.runtime.v1.TransactionalActorStateOperation.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe8\x01\n\x12InvokeActorRequest\x12\x1d\n\nactor_type\x18\x01 \x01(\tR\tactorType\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12\x0e\n\x06method\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12I\n\x08metadata\x18\x05 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.InvokeActorRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"#\n\x13InvokeActorResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x14\n\x12GetMetadataRequest\"\x9b\x06\n\x13GetMetadataResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12Q\n\x13\x61\x63tive_actors_count\x18\x02 \x03(\x0b\x32(.dapr.proto.runtime.v1.ActiveActorsCountB\x02\x18\x01R\x06\x61\x63tors\x12V\n\x15registered_components\x18\x03 \x03(\x0b\x32+.dapr.proto.runtime.v1.RegisteredComponentsR\ncomponents\x12\x65\n\x11\x65xtended_metadata\x18\x04 \x03(\x0b\x32@.dapr.proto.runtime.v1.GetMetadataResponse.ExtendedMetadataEntryR\x08\x65xtended\x12O\n\rsubscriptions\x18\x05 \x03(\x0b\x32).dapr.proto.runtime.v1.PubsubSubscriptionR\rsubscriptions\x12R\n\x0ehttp_endpoints\x18\x06 \x03(\x0b\x32+.dapr.proto.runtime.v1.MetadataHTTPEndpointR\rhttpEndpoints\x12j\n\x19\x61pp_connection_properties\x18\x07 \x01(\x0b\x32..dapr.proto.runtime.v1.AppConnectionPropertiesR\x17\x61ppConnectionProperties\x12\'\n\x0fruntime_version\x18\x08 \x01(\tR\x0eruntimeVersion\x12)\n\x10\x65nabled_features\x18\t \x03(\tR\x0f\x65nabledFeatures\x12H\n\ractor_runtime\x18\n \x01(\x0b\x32#.dapr.proto.runtime.v1.ActorRuntimeR\x0c\x61\x63torRuntime\x1a\x37\n\x15\x45xtendedMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x02\n\x0c\x41\x63torRuntime\x12]\n\x0eruntime_status\x18\x01 \x01(\x0e\x32\x36.dapr.proto.runtime.v1.ActorRuntime.ActorRuntimeStatusR\rruntimeStatus\x12M\n\ractive_actors\x18\x02 \x03(\x0b\x32(.dapr.proto.runtime.v1.ActiveActorsCountR\x0c\x61\x63tiveActors\x12\x1d\n\nhost_ready\x18\x03 \x01(\x08R\thostReady\x12\x1c\n\tplacement\x18\x04 \x01(\tR\tplacement\"A\n\x12\x41\x63torRuntimeStatus\x12\x10\n\x0cINITIALIZING\x10\x00\x12\x0c\n\x08\x44ISABLED\x10\x01\x12\x0b\n\x07RUNNING\x10\x02\"0\n\x11\x41\x63tiveActorsCount\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x05\"Y\n\x14RegisteredComponents\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04type\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x14\n\x0c\x63\x61pabilities\x18\x04 \x03(\t\"*\n\x14MetadataHTTPEndpoint\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\"\xd1\x01\n\x17\x41ppConnectionProperties\x12\x0c\n\x04port\x18\x01 \x01(\x05\x12\x10\n\x08protocol\x18\x02 \x01(\t\x12\'\n\x0f\x63hannel_address\x18\x03 \x01(\tR\x0e\x63hannelAddress\x12\'\n\x0fmax_concurrency\x18\x04 \x01(\x05R\x0emaxConcurrency\x12\x44\n\x06health\x18\x05 \x01(\x0b\x32\x34.dapr.proto.runtime.v1.AppConnectionHealthProperties\"\xdc\x01\n\x1d\x41ppConnectionHealthProperties\x12*\n\x11health_check_path\x18\x01 \x01(\tR\x0fhealthCheckPath\x12\x32\n\x15health_probe_interval\x18\x02 \x01(\tR\x13healthProbeInterval\x12\x30\n\x14health_probe_timeout\x18\x03 \x01(\tR\x12healthProbeTimeout\x12)\n\x10health_threshold\x18\x04 \x01(\x05R\x0fhealthThreshold\"\x86\x03\n\x12PubsubSubscription\x12\x1f\n\x0bpubsub_name\x18\x01 \x01(\tR\npubsubname\x12\x14\n\x05topic\x18\x02 \x01(\tR\x05topic\x12S\n\x08metadata\x18\x03 \x03(\x0b\x32\x37.dapr.proto.runtime.v1.PubsubSubscription.MetadataEntryR\x08metadata\x12\x44\n\x05rules\x18\x04 \x01(\x0b\x32..dapr.proto.runtime.v1.PubsubSubscriptionRulesR\x05rules\x12*\n\x11\x64\x65\x61\x64_letter_topic\x18\x05 \x01(\tR\x0f\x64\x65\x61\x64LetterTopic\x12\x41\n\x04type\x18\x06 \x01(\x0e\x32-.dapr.proto.runtime.v1.PubsubSubscriptionTypeR\x04type\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"W\n\x17PubsubSubscriptionRules\x12<\n\x05rules\x18\x01 \x03(\x0b\x32-.dapr.proto.runtime.v1.PubsubSubscriptionRule\"5\n\x16PubsubSubscriptionRule\x12\r\n\x05match\x18\x01 \x01(\t\x12\x0c\n\x04path\x18\x02 \x01(\t\"0\n\x12SetMetadataRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\xbc\x01\n\x17GetConfigurationRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12N\n\x08metadata\x18\x03 \x03(\x0b\x32<.dapr.proto.runtime.v1.GetConfigurationRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xbc\x01\n\x18GetConfigurationResponse\x12I\n\x05items\x18\x01 \x03(\x0b\x32:.dapr.proto.runtime.v1.GetConfigurationResponse.ItemsEntry\x1aU\n\nItemsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.dapr.proto.common.v1.ConfigurationItem:\x02\x38\x01\"\xc8\x01\n\x1dSubscribeConfigurationRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\x0c\n\x04keys\x18\x02 \x03(\t\x12T\n\x08metadata\x18\x03 \x03(\x0b\x32\x42.dapr.proto.runtime.v1.SubscribeConfigurationRequest.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"A\n\x1fUnsubscribeConfigurationRequest\x12\x12\n\nstore_name\x18\x01 \x01(\t\x12\n\n\x02id\x18\x02 \x01(\t\"\xd4\x01\n\x1eSubscribeConfigurationResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12O\n\x05items\x18\x02 \x03(\x0b\x32@.dapr.proto.runtime.v1.SubscribeConfigurationResponse.ItemsEntry\x1aU\n\nItemsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x36\n\x05value\x18\x02 \x01(\x0b\x32\'.dapr.proto.common.v1.ConfigurationItem:\x02\x38\x01\"?\n UnsubscribeConfigurationResponse\x12\n\n\x02ok\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"\x9b\x01\n\x0eTryLockRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\x1f\n\x0bresource_id\x18\x02 \x01(\tR\nresourceId\x12\x1d\n\nlock_owner\x18\x03 \x01(\tR\tlockOwner\x12*\n\x11\x65xpiry_in_seconds\x18\x04 \x01(\x05R\x0f\x65xpiryInSeconds\"\"\n\x0fTryLockResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"n\n\rUnlockRequest\x12\x1d\n\nstore_name\x18\x01 \x01(\tR\tstoreName\x12\x1f\n\x0bresource_id\x18\x02 \x01(\tR\nresourceId\x12\x1d\n\nlock_owner\x18\x03 \x01(\tR\tlockOwner\"\xae\x01\n\x0eUnlockResponse\x12<\n\x06status\x18\x01 \x01(\x0e\x32,.dapr.proto.runtime.v1.UnlockResponse.Status\"^\n\x06Status\x12\x0b\n\x07SUCCESS\x10\x00\x12\x17\n\x13LOCK_DOES_NOT_EXIST\x10\x01\x12\x1a\n\x16LOCK_BELONGS_TO_OTHERS\x10\x02\x12\x12\n\x0eINTERNAL_ERROR\x10\x03\"\xb0\x01\n\x13SubtleGetKeyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x44\n\x06\x66ormat\x18\x03 \x01(\x0e\x32\x34.dapr.proto.runtime.v1.SubtleGetKeyRequest.KeyFormat\"\x1e\n\tKeyFormat\x12\x07\n\x03PEM\x10\x00\x12\x08\n\x04JSON\x10\x01\"C\n\x14SubtleGetKeyResponse\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1d\n\npublic_key\x18\x02 \x01(\tR\tpublicKey\"\xb6\x01\n\x14SubtleEncryptRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x11\n\tplaintext\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x06 \x01(\x0cR\x0e\x61ssociatedData\"8\n\x15SubtleEncryptResponse\x12\x12\n\nciphertext\x18\x01 \x01(\x0c\x12\x0b\n\x03tag\x18\x02 \x01(\x0c\"\xc4\x01\n\x14SubtleDecryptRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x12\n\nciphertext\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\x0b\n\x03tag\x18\x06 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x07 \x01(\x0cR\x0e\x61ssociatedData\"*\n\x15SubtleDecryptResponse\x12\x11\n\tplaintext\x18\x01 \x01(\x0c\"\xc8\x01\n\x14SubtleWrapKeyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12#\n\rplaintext_key\x18\x02 \x01(\x0cR\x0cplaintextKey\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x06 \x01(\x0cR\x0e\x61ssociatedData\"E\n\x15SubtleWrapKeyResponse\x12\x1f\n\x0bwrapped_key\x18\x01 \x01(\x0cR\nwrappedKey\x12\x0b\n\x03tag\x18\x02 \x01(\x0c\"\xd3\x01\n\x16SubtleUnwrapKeyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x1f\n\x0bwrapped_key\x18\x02 \x01(\x0cR\nwrappedKey\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\r\n\x05nonce\x18\x05 \x01(\x0c\x12\x0b\n\x03tag\x18\x06 \x01(\x0c\x12\'\n\x0f\x61ssociated_data\x18\x07 \x01(\x0cR\x0e\x61ssociatedData\">\n\x17SubtleUnwrapKeyResponse\x12#\n\rplaintext_key\x18\x01 \x01(\x0cR\x0cplaintextKey\"x\n\x11SubtleSignRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x0e\n\x06\x64igest\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\"\'\n\x12SubtleSignResponse\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"\x8d\x01\n\x13SubtleVerifyRequest\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x0e\n\x06\x64igest\x18\x02 \x01(\x0c\x12\x11\n\talgorithm\x18\x03 \x01(\t\x12\x19\n\x08key_name\x18\x04 \x01(\tR\x07keyName\x12\x11\n\tsignature\x18\x05 \x01(\x0c\"%\n\x14SubtleVerifyResponse\x12\r\n\x05valid\x18\x01 \x01(\x08\"\x85\x01\n\x0e\x45ncryptRequest\x12=\n\x07options\x18\x01 \x01(\x0b\x32,.dapr.proto.runtime.v1.EncryptRequestOptions\x12\x34\n\x07payload\x18\x02 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"\xfe\x01\n\x15\x45ncryptRequestOptions\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x19\n\x08key_name\x18\x02 \x01(\tR\x07keyName\x12\x1a\n\x12key_wrap_algorithm\x18\x03 \x01(\t\x12\x1e\n\x16\x64\x61ta_encryption_cipher\x18\n \x01(\t\x12\x37\n\x18omit_decryption_key_name\x18\x0b \x01(\x08R\x15omitDecryptionKeyName\x12.\n\x13\x64\x65\x63ryption_key_name\x18\x0c \x01(\tR\x11\x64\x65\x63ryptionKeyName\"G\n\x0f\x45ncryptResponse\x12\x34\n\x07payload\x18\x01 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"\x85\x01\n\x0e\x44\x65\x63ryptRequest\x12=\n\x07options\x18\x01 \x01(\x0b\x32,.dapr.proto.runtime.v1.DecryptRequestOptions\x12\x34\n\x07payload\x18\x02 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"Y\n\x15\x44\x65\x63ryptRequestOptions\x12%\n\x0e\x63omponent_name\x18\x01 \x01(\tR\rcomponentName\x12\x19\n\x08key_name\x18\x0c \x01(\tR\x07keyName\"G\n\x0f\x44\x65\x63ryptResponse\x12\x34\n\x07payload\x18\x01 \x01(\x0b\x32#.dapr.proto.common.v1.StreamPayload\"d\n\x12GetWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"\x84\x03\n\x13GetWorkflowResponse\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12#\n\rworkflow_name\x18\x02 \x01(\tR\x0cworkflowName\x12\x39\n\ncreated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x42\n\x0flast_updated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\rlastUpdatedAt\x12%\n\x0eruntime_status\x18\x05 \x01(\tR\rruntimeStatus\x12N\n\nproperties\x18\x06 \x03(\x0b\x32:.dapr.proto.runtime.v1.GetWorkflowResponse.PropertiesEntry\x1a\x31\n\x0fPropertiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x95\x02\n\x14StartWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\x12#\n\rworkflow_name\x18\x03 \x01(\tR\x0cworkflowName\x12I\n\x07options\x18\x04 \x03(\x0b\x32\x38.dapr.proto.runtime.v1.StartWorkflowRequest.OptionsEntry\x12\r\n\x05input\x18\x05 \x01(\x0c\x1a.\n\x0cOptionsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"8\n\x15StartWorkflowResponse\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\"j\n\x18TerminateWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"f\n\x14PauseWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"g\n\x15ResumeWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"\x9e\x01\n\x19RaiseEventWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\x12\x1d\n\nevent_name\x18\x03 \x01(\tR\teventName\x12\x12\n\nevent_data\x18\x04 \x01(\x0c\"f\n\x14PurgeWorkflowRequest\x12\x1f\n\x0binstance_id\x18\x01 \x01(\tR\ninstanceID\x12-\n\x12workflow_component\x18\x02 \x01(\tR\x11workflowComponent\"\x11\n\x0fShutdownRequest\"\xe8\x01\n\x03Job\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n\x08schedule\x18\x02 \x01(\tH\x00R\x08schedule\x88\x01\x01\x12\x1d\n\x07repeats\x18\x03 \x01(\rH\x01R\x07repeats\x88\x01\x01\x12\x1e\n\x08\x64ue_time\x18\x04 \x01(\tH\x02R\x07\x64ueTime\x88\x01\x01\x12\x15\n\x03ttl\x18\x05 \x01(\tH\x03R\x03ttl\x88\x01\x01\x12(\n\x04\x64\x61ta\x18\x06 \x01(\x0b\x32\x14.google.protobuf.AnyR\x04\x64\x61taB\x0b\n\t_scheduleB\n\n\x08_repeatsB\x0b\n\t_due_timeB\x06\n\x04_ttl\"=\n\x12ScheduleJobRequest\x12\'\n\x03job\x18\x01 \x01(\x0b\x32\x1a.dapr.proto.runtime.v1.Job\"\x15\n\x13ScheduleJobResponse\"\x1d\n\rGetJobRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"9\n\x0eGetJobResponse\x12\'\n\x03job\x18\x01 \x01(\x0b\x32\x1a.dapr.proto.runtime.v1.Job\" \n\x10\x44\x65leteJobRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"\x13\n\x11\x44\x65leteJobResponse\"\xf9\x03\n\x19\x43onversationAlpha1Request\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\tcontextID\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x38\n\x06inputs\x18\x03 \x03(\x0b\x32(.dapr.proto.runtime.v1.ConversationInput\x12T\n\nparameters\x18\x04 \x03(\x0b\x32@.dapr.proto.runtime.v1.ConversationAlpha1Request.ParametersEntry\x12P\n\x08metadata\x18\x05 \x03(\x0b\x32>.dapr.proto.runtime.v1.ConversationAlpha1Request.MetadataEntry\x12\x15\n\x08scrubPII\x18\x06 \x01(\x08H\x01\x88\x01\x01\x12\x18\n\x0btemperature\x18\x07 \x01(\x01H\x02\x88\x01\x01\x1aG\n\x0fParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any:\x02\x38\x01\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\x0c\n\n_contextIDB\x0b\n\t_scrubPIIB\x0e\n\x0c_temperature\"d\n\x11\x43onversationInput\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x11\n\x04role\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x15\n\x08scrubPII\x18\x03 \x01(\x08H\x01\x88\x01\x01\x42\x07\n\x05_roleB\x0b\n\t_scrubPII\"\xc8\x01\n\x18\x43onversationAlpha1Result\x12\x0e\n\x06result\x18\x01 \x01(\t\x12S\n\nparameters\x18\x02 \x03(\x0b\x32?.dapr.proto.runtime.v1.ConversationAlpha1Result.ParametersEntry\x1aG\n\x0fParametersEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12#\n\x05value\x18\x02 \x01(\x0b\x32\x14.google.protobuf.Any:\x02\x38\x01\"\x84\x01\n\x1a\x43onversationAlpha1Response\x12\x16\n\tcontextID\x18\x01 \x01(\tH\x00\x88\x01\x01\x12@\n\x07outputs\x18\x02 \x03(\x0b\x32/.dapr.proto.runtime.v1.ConversationAlpha1ResultB\x0c\n\n_contextID*W\n\x16PubsubSubscriptionType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0f\n\x0b\x44\x45\x43LARATIVE\x10\x01\x12\x10\n\x0cPROGRAMMATIC\x10\x02\x12\r\n\tSTREAMING\x10\x03\x32\xb5\x31\n\x04\x44\x61pr\x12\x64\n\rInvokeService\x12+.dapr.proto.runtime.v1.InvokeServiceRequest\x1a$.dapr.proto.common.v1.InvokeResponse\"\x00\x12]\n\x08GetState\x12&.dapr.proto.runtime.v1.GetStateRequest\x1a\'.dapr.proto.runtime.v1.GetStateResponse\"\x00\x12i\n\x0cGetBulkState\x12*.dapr.proto.runtime.v1.GetBulkStateRequest\x1a+.dapr.proto.runtime.v1.GetBulkStateResponse\"\x00\x12N\n\tSaveState\x12\'.dapr.proto.runtime.v1.SaveStateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12i\n\x10QueryStateAlpha1\x12(.dapr.proto.runtime.v1.QueryStateRequest\x1a).dapr.proto.runtime.v1.QueryStateResponse\"\x00\x12R\n\x0b\x44\x65leteState\x12).dapr.proto.runtime.v1.DeleteStateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Z\n\x0f\x44\x65leteBulkState\x12-.dapr.proto.runtime.v1.DeleteBulkStateRequest\x1a\x16.google.protobuf.Empty\"\x00\x12j\n\x17\x45xecuteStateTransaction\x12\x35.dapr.proto.runtime.v1.ExecuteStateTransactionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12T\n\x0cPublishEvent\x12*.dapr.proto.runtime.v1.PublishEventRequest\x1a\x16.google.protobuf.Empty\"\x00\x12q\n\x16\x42ulkPublishEventAlpha1\x12).dapr.proto.runtime.v1.BulkPublishRequest\x1a*.dapr.proto.runtime.v1.BulkPublishResponse\"\x00\x12\x97\x01\n\x1aSubscribeTopicEventsAlpha1\x12\x38.dapr.proto.runtime.v1.SubscribeTopicEventsRequestAlpha1\x1a\x39.dapr.proto.runtime.v1.SubscribeTopicEventsResponseAlpha1\"\x00(\x01\x30\x01\x12l\n\rInvokeBinding\x12+.dapr.proto.runtime.v1.InvokeBindingRequest\x1a,.dapr.proto.runtime.v1.InvokeBindingResponse\"\x00\x12`\n\tGetSecret\x12\'.dapr.proto.runtime.v1.GetSecretRequest\x1a(.dapr.proto.runtime.v1.GetSecretResponse\"\x00\x12l\n\rGetBulkSecret\x12+.dapr.proto.runtime.v1.GetBulkSecretRequest\x1a,.dapr.proto.runtime.v1.GetBulkSecretResponse\"\x00\x12`\n\x12RegisterActorTimer\x12\x30.dapr.proto.runtime.v1.RegisterActorTimerRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x64\n\x14UnregisterActorTimer\x12\x32.dapr.proto.runtime.v1.UnregisterActorTimerRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x66\n\x15RegisterActorReminder\x12\x33.dapr.proto.runtime.v1.RegisterActorReminderRequest\x1a\x16.google.protobuf.Empty\"\x00\x12j\n\x17UnregisterActorReminder\x12\x35.dapr.proto.runtime.v1.UnregisterActorReminderRequest\x1a\x16.google.protobuf.Empty\"\x00\x12l\n\rGetActorState\x12+.dapr.proto.runtime.v1.GetActorStateRequest\x1a,.dapr.proto.runtime.v1.GetActorStateResponse\"\x00\x12t\n\x1c\x45xecuteActorStateTransaction\x12:.dapr.proto.runtime.v1.ExecuteActorStateTransactionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x66\n\x0bInvokeActor\x12).dapr.proto.runtime.v1.InvokeActorRequest\x1a*.dapr.proto.runtime.v1.InvokeActorResponse\"\x00\x12{\n\x16GetConfigurationAlpha1\x12..dapr.proto.runtime.v1.GetConfigurationRequest\x1a/.dapr.proto.runtime.v1.GetConfigurationResponse\"\x00\x12u\n\x10GetConfiguration\x12..dapr.proto.runtime.v1.GetConfigurationRequest\x1a/.dapr.proto.runtime.v1.GetConfigurationResponse\"\x00\x12\x8f\x01\n\x1cSubscribeConfigurationAlpha1\x12\x34.dapr.proto.runtime.v1.SubscribeConfigurationRequest\x1a\x35.dapr.proto.runtime.v1.SubscribeConfigurationResponse\"\x00\x30\x01\x12\x89\x01\n\x16SubscribeConfiguration\x12\x34.dapr.proto.runtime.v1.SubscribeConfigurationRequest\x1a\x35.dapr.proto.runtime.v1.SubscribeConfigurationResponse\"\x00\x30\x01\x12\x93\x01\n\x1eUnsubscribeConfigurationAlpha1\x12\x36.dapr.proto.runtime.v1.UnsubscribeConfigurationRequest\x1a\x37.dapr.proto.runtime.v1.UnsubscribeConfigurationResponse\"\x00\x12\x8d\x01\n\x18UnsubscribeConfiguration\x12\x36.dapr.proto.runtime.v1.UnsubscribeConfigurationRequest\x1a\x37.dapr.proto.runtime.v1.UnsubscribeConfigurationResponse\"\x00\x12`\n\rTryLockAlpha1\x12%.dapr.proto.runtime.v1.TryLockRequest\x1a&.dapr.proto.runtime.v1.TryLockResponse\"\x00\x12]\n\x0cUnlockAlpha1\x12$.dapr.proto.runtime.v1.UnlockRequest\x1a%.dapr.proto.runtime.v1.UnlockResponse\"\x00\x12\x62\n\rEncryptAlpha1\x12%.dapr.proto.runtime.v1.EncryptRequest\x1a&.dapr.proto.runtime.v1.EncryptResponse(\x01\x30\x01\x12\x62\n\rDecryptAlpha1\x12%.dapr.proto.runtime.v1.DecryptRequest\x1a&.dapr.proto.runtime.v1.DecryptResponse(\x01\x30\x01\x12\x66\n\x0bGetMetadata\x12).dapr.proto.runtime.v1.GetMetadataRequest\x1a*.dapr.proto.runtime.v1.GetMetadataResponse\"\x00\x12R\n\x0bSetMetadata\x12).dapr.proto.runtime.v1.SetMetadataRequest\x1a\x16.google.protobuf.Empty\"\x00\x12m\n\x12SubtleGetKeyAlpha1\x12*.dapr.proto.runtime.v1.SubtleGetKeyRequest\x1a+.dapr.proto.runtime.v1.SubtleGetKeyResponse\x12p\n\x13SubtleEncryptAlpha1\x12+.dapr.proto.runtime.v1.SubtleEncryptRequest\x1a,.dapr.proto.runtime.v1.SubtleEncryptResponse\x12p\n\x13SubtleDecryptAlpha1\x12+.dapr.proto.runtime.v1.SubtleDecryptRequest\x1a,.dapr.proto.runtime.v1.SubtleDecryptResponse\x12p\n\x13SubtleWrapKeyAlpha1\x12+.dapr.proto.runtime.v1.SubtleWrapKeyRequest\x1a,.dapr.proto.runtime.v1.SubtleWrapKeyResponse\x12v\n\x15SubtleUnwrapKeyAlpha1\x12-.dapr.proto.runtime.v1.SubtleUnwrapKeyRequest\x1a..dapr.proto.runtime.v1.SubtleUnwrapKeyResponse\x12g\n\x10SubtleSignAlpha1\x12(.dapr.proto.runtime.v1.SubtleSignRequest\x1a).dapr.proto.runtime.v1.SubtleSignResponse\x12m\n\x12SubtleVerifyAlpha1\x12*.dapr.proto.runtime.v1.SubtleVerifyRequest\x1a+.dapr.proto.runtime.v1.SubtleVerifyResponse\x12r\n\x13StartWorkflowAlpha1\x12+.dapr.proto.runtime.v1.StartWorkflowRequest\x1a,.dapr.proto.runtime.v1.StartWorkflowResponse\"\x00\x12l\n\x11GetWorkflowAlpha1\x12).dapr.proto.runtime.v1.GetWorkflowRequest\x1a*.dapr.proto.runtime.v1.GetWorkflowResponse\"\x00\x12\\\n\x13PurgeWorkflowAlpha1\x12+.dapr.proto.runtime.v1.PurgeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x64\n\x17TerminateWorkflowAlpha1\x12/.dapr.proto.runtime.v1.TerminateWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\\\n\x13PauseWorkflowAlpha1\x12+.dapr.proto.runtime.v1.PauseWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12^\n\x14ResumeWorkflowAlpha1\x12,.dapr.proto.runtime.v1.ResumeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x66\n\x18RaiseEventWorkflowAlpha1\x12\x30.dapr.proto.runtime.v1.RaiseEventWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12q\n\x12StartWorkflowBeta1\x12+.dapr.proto.runtime.v1.StartWorkflowRequest\x1a,.dapr.proto.runtime.v1.StartWorkflowResponse\"\x00\x12k\n\x10GetWorkflowBeta1\x12).dapr.proto.runtime.v1.GetWorkflowRequest\x1a*.dapr.proto.runtime.v1.GetWorkflowResponse\"\x00\x12[\n\x12PurgeWorkflowBeta1\x12+.dapr.proto.runtime.v1.PurgeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x63\n\x16TerminateWorkflowBeta1\x12/.dapr.proto.runtime.v1.TerminateWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12[\n\x12PauseWorkflowBeta1\x12+.dapr.proto.runtime.v1.PauseWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12]\n\x13ResumeWorkflowBeta1\x12,.dapr.proto.runtime.v1.ResumeWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x65\n\x17RaiseEventWorkflowBeta1\x12\x30.dapr.proto.runtime.v1.RaiseEventWorkflowRequest\x1a\x16.google.protobuf.Empty\"\x00\x12L\n\x08Shutdown\x12&.dapr.proto.runtime.v1.ShutdownRequest\x1a\x16.google.protobuf.Empty\"\x00\x12l\n\x11ScheduleJobAlpha1\x12).dapr.proto.runtime.v1.ScheduleJobRequest\x1a*.dapr.proto.runtime.v1.ScheduleJobResponse\"\x00\x12]\n\x0cGetJobAlpha1\x12$.dapr.proto.runtime.v1.GetJobRequest\x1a%.dapr.proto.runtime.v1.GetJobResponse\"\x00\x12\x66\n\x0f\x44\x65leteJobAlpha1\x12\'.dapr.proto.runtime.v1.DeleteJobRequest\x1a(.dapr.proto.runtime.v1.DeleteJobResponse\"\x00\x12w\n\x0e\x43onverseAlpha1\x12\x30.dapr.proto.runtime.v1.ConversationAlpha1Request\x1a\x31.dapr.proto.runtime.v1.ConversationAlpha1Response\"\x00\x42i\n\nio.dapr.v1B\nDaprProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\xaa\x02\x1b\x44\x61pr.Client.Autogen.Grpc.v1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'dapr.proto.runtime.v1.dapr_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'\n\nio.dapr.v1B\nDaprProtosZ1github.com/dapr/dapr/pkg/proto/runtime/v1;runtime\252\002\033Dapr.Client.Autogen.Grpc.v1' + _GETSTATEREQUEST_METADATAENTRY._options = None + _GETSTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' + _GETBULKSTATEREQUEST_METADATAENTRY._options = None + _GETBULKSTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' + _BULKSTATEITEM_METADATAENTRY._options = None + _BULKSTATEITEM_METADATAENTRY._serialized_options = b'8\001' + _GETSTATERESPONSE_METADATAENTRY._options = None + _GETSTATERESPONSE_METADATAENTRY._serialized_options = b'8\001' + _DELETESTATEREQUEST_METADATAENTRY._options = None + _DELETESTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' + _QUERYSTATEREQUEST_METADATAENTRY._options = None + _QUERYSTATEREQUEST_METADATAENTRY._serialized_options = b'8\001' + _QUERYSTATERESPONSE_METADATAENTRY._options = None + _QUERYSTATERESPONSE_METADATAENTRY._serialized_options = b'8\001' + _PUBLISHEVENTREQUEST_METADATAENTRY._options = None + _PUBLISHEVENTREQUEST_METADATAENTRY._serialized_options = b'8\001' + _BULKPUBLISHREQUEST_METADATAENTRY._options = None + _BULKPUBLISHREQUEST_METADATAENTRY._serialized_options = b'8\001' + _BULKPUBLISHREQUESTENTRY_METADATAENTRY._options = None + _BULKPUBLISHREQUESTENTRY_METADATAENTRY._serialized_options = b'8\001' + _SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY._options = None + _SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY._serialized_options = b'8\001' + _INVOKEBINDINGREQUEST_METADATAENTRY._options = None + _INVOKEBINDINGREQUEST_METADATAENTRY._serialized_options = b'8\001' + _INVOKEBINDINGRESPONSE_METADATAENTRY._options = None + _INVOKEBINDINGRESPONSE_METADATAENTRY._serialized_options = b'8\001' + _GETSECRETREQUEST_METADATAENTRY._options = None + _GETSECRETREQUEST_METADATAENTRY._serialized_options = b'8\001' + _GETSECRETRESPONSE_DATAENTRY._options = None + _GETSECRETRESPONSE_DATAENTRY._serialized_options = b'8\001' + _GETBULKSECRETREQUEST_METADATAENTRY._options = None + _GETBULKSECRETREQUEST_METADATAENTRY._serialized_options = b'8\001' + _SECRETRESPONSE_SECRETSENTRY._options = None + _SECRETRESPONSE_SECRETSENTRY._serialized_options = b'8\001' + _GETBULKSECRETRESPONSE_DATAENTRY._options = None + _GETBULKSECRETRESPONSE_DATAENTRY._serialized_options = b'8\001' + _EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY._options = None + _EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY._serialized_options = b'8\001' + _GETACTORSTATERESPONSE_METADATAENTRY._options = None + _GETACTORSTATERESPONSE_METADATAENTRY._serialized_options = b'8\001' + _TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY._options = None + _TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY._serialized_options = b'8\001' + _INVOKEACTORREQUEST_METADATAENTRY._options = None + _INVOKEACTORREQUEST_METADATAENTRY._serialized_options = b'8\001' + _GETMETADATARESPONSE_EXTENDEDMETADATAENTRY._options = None + _GETMETADATARESPONSE_EXTENDEDMETADATAENTRY._serialized_options = b'8\001' + _GETMETADATARESPONSE.fields_by_name['active_actors_count']._options = None + _GETMETADATARESPONSE.fields_by_name['active_actors_count']._serialized_options = b'\030\001' + _PUBSUBSUBSCRIPTION_METADATAENTRY._options = None + _PUBSUBSUBSCRIPTION_METADATAENTRY._serialized_options = b'8\001' + _GETCONFIGURATIONREQUEST_METADATAENTRY._options = None + _GETCONFIGURATIONREQUEST_METADATAENTRY._serialized_options = b'8\001' + _GETCONFIGURATIONRESPONSE_ITEMSENTRY._options = None + _GETCONFIGURATIONRESPONSE_ITEMSENTRY._serialized_options = b'8\001' + _SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY._options = None + _SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY._serialized_options = b'8\001' + _SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY._options = None + _SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY._serialized_options = b'8\001' + _GETWORKFLOWRESPONSE_PROPERTIESENTRY._options = None + _GETWORKFLOWRESPONSE_PROPERTIESENTRY._serialized_options = b'8\001' + _STARTWORKFLOWREQUEST_OPTIONSENTRY._options = None + _STARTWORKFLOWREQUEST_OPTIONSENTRY._serialized_options = b'8\001' + _CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY._options = None + _CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY._serialized_options = b'8\001' + _CONVERSATIONALPHA1REQUEST_METADATAENTRY._options = None + _CONVERSATIONALPHA1REQUEST_METADATAENTRY._serialized_options = b'8\001' + _CONVERSATIONALPHA1RESULT_PARAMETERSENTRY._options = None + _CONVERSATIONALPHA1RESULT_PARAMETERSENTRY._serialized_options = b'8\001' + _globals['_PUBSUBSUBSCRIPTIONTYPE']._serialized_start=16034 + _globals['_PUBSUBSUBSCRIPTIONTYPE']._serialized_end=16121 + _globals['_INVOKESERVICEREQUEST']._serialized_start=224 + _globals['_INVOKESERVICEREQUEST']._serialized_end=312 + _globals['_GETSTATEREQUEST']._serialized_start=315 + _globals['_GETSTATEREQUEST']._serialized_end=560 + _globals['_GETSTATEREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_GETSTATEREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_GETBULKSTATEREQUEST']._serialized_start=563 + _globals['_GETBULKSTATEREQUEST']._serialized_end=764 + _globals['_GETBULKSTATEREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_GETBULKSTATEREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_GETBULKSTATERESPONSE']._serialized_start=766 + _globals['_GETBULKSTATERESPONSE']._serialized_end=841 + _globals['_BULKSTATEITEM']._serialized_start=844 + _globals['_BULKSTATEITEM']._serialized_end=1034 + _globals['_BULKSTATEITEM_METADATAENTRY']._serialized_start=513 + _globals['_BULKSTATEITEM_METADATAENTRY']._serialized_end=560 + _globals['_GETSTATERESPONSE']._serialized_start=1037 + _globals['_GETSTATERESPONSE']._serialized_end=1205 + _globals['_GETSTATERESPONSE_METADATAENTRY']._serialized_start=513 + _globals['_GETSTATERESPONSE_METADATAENTRY']._serialized_end=560 + _globals['_DELETESTATEREQUEST']._serialized_start=1208 + _globals['_DELETESTATEREQUEST']._serialized_end=1480 + _globals['_DELETESTATEREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_DELETESTATEREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_DELETEBULKSTATEREQUEST']._serialized_start=1482 + _globals['_DELETEBULKSTATEREQUEST']._serialized_end=1575 + _globals['_SAVESTATEREQUEST']._serialized_start=1577 + _globals['_SAVESTATEREQUEST']._serialized_end=1664 + _globals['_QUERYSTATEREQUEST']._serialized_start=1667 + _globals['_QUERYSTATEREQUEST']._serialized_end=1855 + _globals['_QUERYSTATEREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_QUERYSTATEREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_QUERYSTATEITEM']._serialized_start=1857 + _globals['_QUERYSTATEITEM']._serialized_end=1929 + _globals['_QUERYSTATERESPONSE']._serialized_start=1932 + _globals['_QUERYSTATERESPONSE']._serialized_end=2147 + _globals['_QUERYSTATERESPONSE_METADATAENTRY']._serialized_start=513 + _globals['_QUERYSTATERESPONSE_METADATAENTRY']._serialized_end=560 + _globals['_PUBLISHEVENTREQUEST']._serialized_start=2150 + _globals['_PUBLISHEVENTREQUEST']._serialized_end=2373 + _globals['_PUBLISHEVENTREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_PUBLISHEVENTREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_BULKPUBLISHREQUEST']._serialized_start=2376 + _globals['_BULKPUBLISHREQUEST']._serialized_end=2621 + _globals['_BULKPUBLISHREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_BULKPUBLISHREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_BULKPUBLISHREQUESTENTRY']._serialized_start=2624 + _globals['_BULKPUBLISHREQUESTENTRY']._serialized_end=2833 + _globals['_BULKPUBLISHREQUESTENTRY_METADATAENTRY']._serialized_start=513 + _globals['_BULKPUBLISHREQUESTENTRY_METADATAENTRY']._serialized_end=560 + _globals['_BULKPUBLISHRESPONSE']._serialized_start=2835 + _globals['_BULKPUBLISHRESPONSE']._serialized_end=2934 + _globals['_BULKPUBLISHRESPONSEFAILEDENTRY']._serialized_start=2936 + _globals['_BULKPUBLISHRESPONSEFAILEDENTRY']._serialized_end=3001 + _globals['_SUBSCRIBETOPICEVENTSREQUESTALPHA1']._serialized_start=3004 + _globals['_SUBSCRIBETOPICEVENTSREQUESTALPHA1']._serialized_end=3264 + _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1']._serialized_start=3267 + _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1']._serialized_end=3545 + _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY']._serialized_start=513 + _globals['_SUBSCRIBETOPICEVENTSREQUESTINITIALALPHA1_METADATAENTRY']._serialized_end=560 + _globals['_SUBSCRIBETOPICEVENTSREQUESTPROCESSEDALPHA1']._serialized_start=3547 + _globals['_SUBSCRIBETOPICEVENTSREQUESTPROCESSEDALPHA1']._serialized_end=3662 + _globals['_SUBSCRIBETOPICEVENTSRESPONSEALPHA1']._serialized_start=3665 + _globals['_SUBSCRIBETOPICEVENTSRESPONSEALPHA1']._serialized_end=3902 + _globals['_SUBSCRIBETOPICEVENTSRESPONSEINITIALALPHA1']._serialized_start=3904 + _globals['_SUBSCRIBETOPICEVENTSRESPONSEINITIALALPHA1']._serialized_end=3947 + _globals['_INVOKEBINDINGREQUEST']._serialized_start=3950 + _globals['_INVOKEBINDINGREQUEST']._serialized_end=4145 + _globals['_INVOKEBINDINGREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_INVOKEBINDINGREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_INVOKEBINDINGRESPONSE']._serialized_start=4148 + _globals['_INVOKEBINDINGRESPONSE']._serialized_end=4312 + _globals['_INVOKEBINDINGRESPONSE_METADATAENTRY']._serialized_start=513 + _globals['_INVOKEBINDINGRESPONSE_METADATAENTRY']._serialized_end=560 + _globals['_GETSECRETREQUEST']._serialized_start=4315 + _globals['_GETSECRETREQUEST']._serialized_end=4499 + _globals['_GETSECRETREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_GETSECRETREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_GETSECRETRESPONSE']._serialized_start=4502 + _globals['_GETSECRETRESPONSE']._serialized_end=4632 + _globals['_GETSECRETRESPONSE_DATAENTRY']._serialized_start=4589 + _globals['_GETSECRETRESPONSE_DATAENTRY']._serialized_end=4632 + _globals['_GETBULKSECRETREQUEST']._serialized_start=4635 + _globals['_GETBULKSECRETREQUEST']._serialized_end=4814 + _globals['_GETBULKSECRETREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_GETBULKSECRETREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_SECRETRESPONSE']._serialized_start=4817 + _globals['_SECRETRESPONSE']._serialized_end=4950 + _globals['_SECRETRESPONSE_SECRETSENTRY']._serialized_start=4904 + _globals['_SECRETRESPONSE_SECRETSENTRY']._serialized_end=4950 + _globals['_GETBULKSECRETRESPONSE']._serialized_start=4953 + _globals['_GETBULKSECRETRESPONSE']._serialized_end=5130 + _globals['_GETBULKSECRETRESPONSE_DATAENTRY']._serialized_start=5048 + _globals['_GETBULKSECRETRESPONSE_DATAENTRY']._serialized_end=5130 + _globals['_TRANSACTIONALSTATEOPERATION']._serialized_start=5132 + _globals['_TRANSACTIONALSTATEOPERATION']._serialized_end=5234 + _globals['_EXECUTESTATETRANSACTIONREQUEST']._serialized_start=5237 + _globals['_EXECUTESTATETRANSACTIONREQUEST']._serialized_end=5496 + _globals['_EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_EXECUTESTATETRANSACTIONREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_REGISTERACTORTIMERREQUEST']._serialized_start=5499 + _globals['_REGISTERACTORTIMERREQUEST']._serialized_end=5686 + _globals['_UNREGISTERACTORTIMERREQUEST']._serialized_start=5688 + _globals['_UNREGISTERACTORTIMERREQUEST']._serialized_end=5789 + _globals['_REGISTERACTORREMINDERREQUEST']._serialized_start=5792 + _globals['_REGISTERACTORREMINDERREQUEST']._serialized_end=5964 + _globals['_UNREGISTERACTORREMINDERREQUEST']._serialized_start=5966 + _globals['_UNREGISTERACTORREMINDERREQUEST']._serialized_end=6070 + _globals['_GETACTORSTATEREQUEST']._serialized_start=6072 + _globals['_GETACTORSTATEREQUEST']._serialized_end=6165 + _globals['_GETACTORSTATERESPONSE']._serialized_start=6168 + _globals['_GETACTORSTATERESPONSE']._serialized_end=6332 + _globals['_GETACTORSTATERESPONSE_METADATAENTRY']._serialized_start=513 + _globals['_GETACTORSTATERESPONSE_METADATAENTRY']._serialized_end=560 + _globals['_EXECUTEACTORSTATETRANSACTIONREQUEST']._serialized_start=6335 + _globals['_EXECUTEACTORSTATETRANSACTIONREQUEST']._serialized_end=6507 + _globals['_TRANSACTIONALACTORSTATEOPERATION']._serialized_start=6510 + _globals['_TRANSACTIONALACTORSTATEOPERATION']._serialized_end=6755 + _globals['_TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY']._serialized_start=513 + _globals['_TRANSACTIONALACTORSTATEOPERATION_METADATAENTRY']._serialized_end=560 + _globals['_INVOKEACTORREQUEST']._serialized_start=6758 + _globals['_INVOKEACTORREQUEST']._serialized_end=6990 + _globals['_INVOKEACTORREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_INVOKEACTORREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_INVOKEACTORRESPONSE']._serialized_start=6992 + _globals['_INVOKEACTORRESPONSE']._serialized_end=7027 + _globals['_GETMETADATAREQUEST']._serialized_start=7029 + _globals['_GETMETADATAREQUEST']._serialized_end=7049 + _globals['_GETMETADATARESPONSE']._serialized_start=7052 + _globals['_GETMETADATARESPONSE']._serialized_end=7847 + _globals['_GETMETADATARESPONSE_EXTENDEDMETADATAENTRY']._serialized_start=7792 + _globals['_GETMETADATARESPONSE_EXTENDEDMETADATAENTRY']._serialized_end=7847 + _globals['_ACTORRUNTIME']._serialized_start=7850 + _globals['_ACTORRUNTIME']._serialized_end=8166 + _globals['_ACTORRUNTIME_ACTORRUNTIMESTATUS']._serialized_start=8101 + _globals['_ACTORRUNTIME_ACTORRUNTIMESTATUS']._serialized_end=8166 + _globals['_ACTIVEACTORSCOUNT']._serialized_start=8168 + _globals['_ACTIVEACTORSCOUNT']._serialized_end=8216 + _globals['_REGISTEREDCOMPONENTS']._serialized_start=8218 + _globals['_REGISTEREDCOMPONENTS']._serialized_end=8307 + _globals['_METADATAHTTPENDPOINT']._serialized_start=8309 + _globals['_METADATAHTTPENDPOINT']._serialized_end=8351 + _globals['_APPCONNECTIONPROPERTIES']._serialized_start=8354 + _globals['_APPCONNECTIONPROPERTIES']._serialized_end=8563 + _globals['_APPCONNECTIONHEALTHPROPERTIES']._serialized_start=8566 + _globals['_APPCONNECTIONHEALTHPROPERTIES']._serialized_end=8786 + _globals['_PUBSUBSUBSCRIPTION']._serialized_start=8789 + _globals['_PUBSUBSUBSCRIPTION']._serialized_end=9179 + _globals['_PUBSUBSUBSCRIPTION_METADATAENTRY']._serialized_start=513 + _globals['_PUBSUBSUBSCRIPTION_METADATAENTRY']._serialized_end=560 + _globals['_PUBSUBSUBSCRIPTIONRULES']._serialized_start=9181 + _globals['_PUBSUBSUBSCRIPTIONRULES']._serialized_end=9268 + _globals['_PUBSUBSUBSCRIPTIONRULE']._serialized_start=9270 + _globals['_PUBSUBSUBSCRIPTIONRULE']._serialized_end=9323 + _globals['_SETMETADATAREQUEST']._serialized_start=9325 + _globals['_SETMETADATAREQUEST']._serialized_end=9373 + _globals['_GETCONFIGURATIONREQUEST']._serialized_start=9376 + _globals['_GETCONFIGURATIONREQUEST']._serialized_end=9564 + _globals['_GETCONFIGURATIONREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_GETCONFIGURATIONREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_GETCONFIGURATIONRESPONSE']._serialized_start=9567 + _globals['_GETCONFIGURATIONRESPONSE']._serialized_end=9755 + _globals['_GETCONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_start=9670 + _globals['_GETCONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_end=9755 + _globals['_SUBSCRIBECONFIGURATIONREQUEST']._serialized_start=9758 + _globals['_SUBSCRIBECONFIGURATIONREQUEST']._serialized_end=9958 + _globals['_SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY']._serialized_start=513 + _globals['_SUBSCRIBECONFIGURATIONREQUEST_METADATAENTRY']._serialized_end=560 + _globals['_UNSUBSCRIBECONFIGURATIONREQUEST']._serialized_start=9960 + _globals['_UNSUBSCRIBECONFIGURATIONREQUEST']._serialized_end=10025 + _globals['_SUBSCRIBECONFIGURATIONRESPONSE']._serialized_start=10028 + _globals['_SUBSCRIBECONFIGURATIONRESPONSE']._serialized_end=10240 + _globals['_SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_start=9670 + _globals['_SUBSCRIBECONFIGURATIONRESPONSE_ITEMSENTRY']._serialized_end=9755 + _globals['_UNSUBSCRIBECONFIGURATIONRESPONSE']._serialized_start=10242 + _globals['_UNSUBSCRIBECONFIGURATIONRESPONSE']._serialized_end=10305 + _globals['_TRYLOCKREQUEST']._serialized_start=10308 + _globals['_TRYLOCKREQUEST']._serialized_end=10463 + _globals['_TRYLOCKRESPONSE']._serialized_start=10465 + _globals['_TRYLOCKRESPONSE']._serialized_end=10499 + _globals['_UNLOCKREQUEST']._serialized_start=10501 + _globals['_UNLOCKREQUEST']._serialized_end=10611 + _globals['_UNLOCKRESPONSE']._serialized_start=10614 + _globals['_UNLOCKRESPONSE']._serialized_end=10788 + _globals['_UNLOCKRESPONSE_STATUS']._serialized_start=10694 + _globals['_UNLOCKRESPONSE_STATUS']._serialized_end=10788 + _globals['_SUBTLEGETKEYREQUEST']._serialized_start=10791 + _globals['_SUBTLEGETKEYREQUEST']._serialized_end=10967 + _globals['_SUBTLEGETKEYREQUEST_KEYFORMAT']._serialized_start=10937 + _globals['_SUBTLEGETKEYREQUEST_KEYFORMAT']._serialized_end=10967 + _globals['_SUBTLEGETKEYRESPONSE']._serialized_start=10969 + _globals['_SUBTLEGETKEYRESPONSE']._serialized_end=11036 + _globals['_SUBTLEENCRYPTREQUEST']._serialized_start=11039 + _globals['_SUBTLEENCRYPTREQUEST']._serialized_end=11221 + _globals['_SUBTLEENCRYPTRESPONSE']._serialized_start=11223 + _globals['_SUBTLEENCRYPTRESPONSE']._serialized_end=11279 + _globals['_SUBTLEDECRYPTREQUEST']._serialized_start=11282 + _globals['_SUBTLEDECRYPTREQUEST']._serialized_end=11478 + _globals['_SUBTLEDECRYPTRESPONSE']._serialized_start=11480 + _globals['_SUBTLEDECRYPTRESPONSE']._serialized_end=11522 + _globals['_SUBTLEWRAPKEYREQUEST']._serialized_start=11525 + _globals['_SUBTLEWRAPKEYREQUEST']._serialized_end=11725 + _globals['_SUBTLEWRAPKEYRESPONSE']._serialized_start=11727 + _globals['_SUBTLEWRAPKEYRESPONSE']._serialized_end=11796 + _globals['_SUBTLEUNWRAPKEYREQUEST']._serialized_start=11799 + _globals['_SUBTLEUNWRAPKEYREQUEST']._serialized_end=12010 + _globals['_SUBTLEUNWRAPKEYRESPONSE']._serialized_start=12012 + _globals['_SUBTLEUNWRAPKEYRESPONSE']._serialized_end=12074 + _globals['_SUBTLESIGNREQUEST']._serialized_start=12076 + _globals['_SUBTLESIGNREQUEST']._serialized_end=12196 + _globals['_SUBTLESIGNRESPONSE']._serialized_start=12198 + _globals['_SUBTLESIGNRESPONSE']._serialized_end=12237 + _globals['_SUBTLEVERIFYREQUEST']._serialized_start=12240 + _globals['_SUBTLEVERIFYREQUEST']._serialized_end=12381 + _globals['_SUBTLEVERIFYRESPONSE']._serialized_start=12383 + _globals['_SUBTLEVERIFYRESPONSE']._serialized_end=12420 + _globals['_ENCRYPTREQUEST']._serialized_start=12423 + _globals['_ENCRYPTREQUEST']._serialized_end=12556 + _globals['_ENCRYPTREQUESTOPTIONS']._serialized_start=12559 + _globals['_ENCRYPTREQUESTOPTIONS']._serialized_end=12813 + _globals['_ENCRYPTRESPONSE']._serialized_start=12815 + _globals['_ENCRYPTRESPONSE']._serialized_end=12886 + _globals['_DECRYPTREQUEST']._serialized_start=12889 + _globals['_DECRYPTREQUEST']._serialized_end=13022 + _globals['_DECRYPTREQUESTOPTIONS']._serialized_start=13024 + _globals['_DECRYPTREQUESTOPTIONS']._serialized_end=13113 + _globals['_DECRYPTRESPONSE']._serialized_start=13115 + _globals['_DECRYPTRESPONSE']._serialized_end=13186 + _globals['_GETWORKFLOWREQUEST']._serialized_start=13188 + _globals['_GETWORKFLOWREQUEST']._serialized_end=13288 + _globals['_GETWORKFLOWRESPONSE']._serialized_start=13291 + _globals['_GETWORKFLOWRESPONSE']._serialized_end=13679 + _globals['_GETWORKFLOWRESPONSE_PROPERTIESENTRY']._serialized_start=13630 + _globals['_GETWORKFLOWRESPONSE_PROPERTIESENTRY']._serialized_end=13679 + _globals['_STARTWORKFLOWREQUEST']._serialized_start=13682 + _globals['_STARTWORKFLOWREQUEST']._serialized_end=13959 + _globals['_STARTWORKFLOWREQUEST_OPTIONSENTRY']._serialized_start=13913 + _globals['_STARTWORKFLOWREQUEST_OPTIONSENTRY']._serialized_end=13959 + _globals['_STARTWORKFLOWRESPONSE']._serialized_start=13961 + _globals['_STARTWORKFLOWRESPONSE']._serialized_end=14017 + _globals['_TERMINATEWORKFLOWREQUEST']._serialized_start=14019 + _globals['_TERMINATEWORKFLOWREQUEST']._serialized_end=14125 + _globals['_PAUSEWORKFLOWREQUEST']._serialized_start=14127 + _globals['_PAUSEWORKFLOWREQUEST']._serialized_end=14229 + _globals['_RESUMEWORKFLOWREQUEST']._serialized_start=14231 + _globals['_RESUMEWORKFLOWREQUEST']._serialized_end=14334 + _globals['_RAISEEVENTWORKFLOWREQUEST']._serialized_start=14337 + _globals['_RAISEEVENTWORKFLOWREQUEST']._serialized_end=14495 + _globals['_PURGEWORKFLOWREQUEST']._serialized_start=14497 + _globals['_PURGEWORKFLOWREQUEST']._serialized_end=14599 + _globals['_SHUTDOWNREQUEST']._serialized_start=14601 + _globals['_SHUTDOWNREQUEST']._serialized_end=14618 + _globals['_JOB']._serialized_start=14621 + _globals['_JOB']._serialized_end=14853 + _globals['_SCHEDULEJOBREQUEST']._serialized_start=14855 + _globals['_SCHEDULEJOBREQUEST']._serialized_end=14916 + _globals['_SCHEDULEJOBRESPONSE']._serialized_start=14918 + _globals['_SCHEDULEJOBRESPONSE']._serialized_end=14939 + _globals['_GETJOBREQUEST']._serialized_start=14941 + _globals['_GETJOBREQUEST']._serialized_end=14970 + _globals['_GETJOBRESPONSE']._serialized_start=14972 + _globals['_GETJOBRESPONSE']._serialized_end=15029 + _globals['_DELETEJOBREQUEST']._serialized_start=15031 + _globals['_DELETEJOBREQUEST']._serialized_end=15063 + _globals['_DELETEJOBRESPONSE']._serialized_start=15065 + _globals['_DELETEJOBRESPONSE']._serialized_end=15084 + _globals['_CONVERSATIONALPHA1REQUEST']._serialized_start=15087 + _globals['_CONVERSATIONALPHA1REQUEST']._serialized_end=15592 + _globals['_CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY']._serialized_start=15429 + _globals['_CONVERSATIONALPHA1REQUEST_PARAMETERSENTRY']._serialized_end=15500 + _globals['_CONVERSATIONALPHA1REQUEST_METADATAENTRY']._serialized_start=513 + _globals['_CONVERSATIONALPHA1REQUEST_METADATAENTRY']._serialized_end=560 + _globals['_CONVERSATIONINPUT']._serialized_start=15594 + _globals['_CONVERSATIONINPUT']._serialized_end=15694 + _globals['_CONVERSATIONALPHA1RESULT']._serialized_start=15697 + _globals['_CONVERSATIONALPHA1RESULT']._serialized_end=15897 + _globals['_CONVERSATIONALPHA1RESULT_PARAMETERSENTRY']._serialized_start=15429 + _globals['_CONVERSATIONALPHA1RESULT_PARAMETERSENTRY']._serialized_end=15500 + _globals['_CONVERSATIONALPHA1RESPONSE']._serialized_start=15900 + _globals['_CONVERSATIONALPHA1RESPONSE']._serialized_end=16032 + _globals['_DAPR']._serialized_start=16124 + _globals['_DAPR']._serialized_end=22449 +# @@protoc_insertion_point(module_scope) diff --git a/dapr/proto/runtime/v1/dapr_pb2.pyi b/dapr/proto/runtime/v1/dapr_pb2.pyi index 11f695a50..ab2785ae4 100644 --- a/dapr/proto/runtime/v1/dapr_pb2.pyi +++ b/dapr/proto/runtime/v1/dapr_pb2.pyi @@ -1,3442 +1,3442 @@ -""" -@generated by mypy-protobuf. Do not edit manually! -isort:skip_file - -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import builtins -import collections.abc -import dapr.proto.common.v1.common_pb2 -import dapr.proto.runtime.v1.appcallback_pb2 -import google.protobuf.any_pb2 -import google.protobuf.descriptor -import google.protobuf.internal.containers -import google.protobuf.internal.enum_type_wrapper -import google.protobuf.message -import google.protobuf.timestamp_pb2 -import sys -import typing - -if sys.version_info >= (3, 10): - import typing as typing_extensions -else: - import typing_extensions - -DESCRIPTOR: google.protobuf.descriptor.FileDescriptor - -class _PubsubSubscriptionType: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - -class _PubsubSubscriptionTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_PubsubSubscriptionType.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - UNKNOWN: _PubsubSubscriptionType.ValueType # 0 - """UNKNOWN is the default value for the subscription type.""" - DECLARATIVE: _PubsubSubscriptionType.ValueType # 1 - """Declarative subscription (k8s CRD)""" - PROGRAMMATIC: _PubsubSubscriptionType.ValueType # 2 - """Programmatically created subscription""" - STREAMING: _PubsubSubscriptionType.ValueType # 3 - """Bidirectional Streaming subscription""" - -class PubsubSubscriptionType(_PubsubSubscriptionType, metaclass=_PubsubSubscriptionTypeEnumTypeWrapper): - """PubsubSubscriptionType indicates the type of subscription""" - -UNKNOWN: PubsubSubscriptionType.ValueType # 0 -"""UNKNOWN is the default value for the subscription type.""" -DECLARATIVE: PubsubSubscriptionType.ValueType # 1 -"""Declarative subscription (k8s CRD)""" -PROGRAMMATIC: PubsubSubscriptionType.ValueType # 2 -"""Programmatically created subscription""" -STREAMING: PubsubSubscriptionType.ValueType # 3 -"""Bidirectional Streaming subscription""" -global___PubsubSubscriptionType = PubsubSubscriptionType - -@typing_extensions.final -class InvokeServiceRequest(google.protobuf.message.Message): - """InvokeServiceRequest represents the request message for Service invocation.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - MESSAGE_FIELD_NUMBER: builtins.int - id: builtins.str - """Required. Callee's app id.""" - @property - def message(self) -> dapr.proto.common.v1.common_pb2.InvokeRequest: - """Required. message which will be delivered to callee.""" - def __init__( - self, - *, - id: builtins.str = ..., - message: dapr.proto.common.v1.common_pb2.InvokeRequest | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["message", b"message"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "message", b"message"]) -> None: ... - -global___InvokeServiceRequest = InvokeServiceRequest - -@typing_extensions.final -class GetStateRequest(google.protobuf.message.Message): - """GetStateRequest is the message to get key-value states from specific state store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - KEY_FIELD_NUMBER: builtins.int - CONSISTENCY_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store.""" - key: builtins.str - """The key of the desired state""" - consistency: dapr.proto.common.v1.common_pb2.StateOptions.StateConsistency.ValueType - """The read consistency of the state store.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to state store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - key: builtins.str = ..., - consistency: dapr.proto.common.v1.common_pb2.StateOptions.StateConsistency.ValueType = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["consistency", b"consistency", "key", b"key", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... - -global___GetStateRequest = GetStateRequest - -@typing_extensions.final -class GetBulkStateRequest(google.protobuf.message.Message): - """GetBulkStateRequest is the message to get a list of key-value states from specific state store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - KEYS_FIELD_NUMBER: builtins.int - PARALLELISM_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store.""" - @property - def keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """The keys to get.""" - parallelism: builtins.int - """The number of parallel operations executed on the state store for a get operation.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to state store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - keys: collections.abc.Iterable[builtins.str] | None = ..., - parallelism: builtins.int = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "metadata", b"metadata", "parallelism", b"parallelism", "store_name", b"store_name"]) -> None: ... - -global___GetBulkStateRequest = GetBulkStateRequest - -@typing_extensions.final -class GetBulkStateResponse(google.protobuf.message.Message): - """GetBulkStateResponse is the response conveying the list of state values.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ITEMS_FIELD_NUMBER: builtins.int - @property - def items(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BulkStateItem]: - """The list of items containing the keys to get values for.""" - def __init__( - self, - *, - items: collections.abc.Iterable[global___BulkStateItem] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["items", b"items"]) -> None: ... - -global___GetBulkStateResponse = GetBulkStateResponse - -@typing_extensions.final -class BulkStateItem(google.protobuf.message.Message): - """BulkStateItem is the response item for a bulk get operation. - Return values include the item key, data and etag. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - KEY_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - ERROR_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - key: builtins.str - """state item key""" - data: builtins.bytes - """The byte array data""" - etag: builtins.str - """The entity tag which represents the specific version of data. - ETag format is defined by the corresponding data store. - """ - error: builtins.str - """The error that was returned from the state store in case of a failed get operation.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to app.""" - def __init__( - self, - *, - key: builtins.str = ..., - data: builtins.bytes = ..., - etag: builtins.str = ..., - error: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "error", b"error", "etag", b"etag", "key", b"key", "metadata", b"metadata"]) -> None: ... - -global___BulkStateItem = BulkStateItem - -@typing_extensions.final -class GetStateResponse(google.protobuf.message.Message): - """GetStateResponse is the response conveying the state value and etag.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - DATA_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - data: builtins.bytes - """The byte array data""" - etag: builtins.str - """The entity tag which represents the specific version of data. - ETag format is defined by the corresponding data store. - """ - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to app.""" - def __init__( - self, - *, - data: builtins.bytes = ..., - etag: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "etag", b"etag", "metadata", b"metadata"]) -> None: ... - -global___GetStateResponse = GetStateResponse - -@typing_extensions.final -class DeleteStateRequest(google.protobuf.message.Message): - """DeleteStateRequest is the message to delete key-value states in the specific state store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - KEY_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - OPTIONS_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store.""" - key: builtins.str - """The key of the desired state""" - @property - def etag(self) -> dapr.proto.common.v1.common_pb2.Etag: - """The entity tag which represents the specific version of data. - The exact ETag format is defined by the corresponding data store. - """ - @property - def options(self) -> dapr.proto.common.v1.common_pb2.StateOptions: - """State operation options which includes concurrency/ - consistency/retry_policy. - """ - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to state store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - key: builtins.str = ..., - etag: dapr.proto.common.v1.common_pb2.Etag | None = ..., - options: dapr.proto.common.v1.common_pb2.StateOptions | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["etag", b"etag", "options", b"options"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["etag", b"etag", "key", b"key", "metadata", b"metadata", "options", b"options", "store_name", b"store_name"]) -> None: ... - -global___DeleteStateRequest = DeleteStateRequest - -@typing_extensions.final -class DeleteBulkStateRequest(google.protobuf.message.Message): - """DeleteBulkStateRequest is the message to delete a list of key-value states from specific state store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STORE_NAME_FIELD_NUMBER: builtins.int - STATES_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store.""" - @property - def states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[dapr.proto.common.v1.common_pb2.StateItem]: - """The array of the state key values.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - states: collections.abc.Iterable[dapr.proto.common.v1.common_pb2.StateItem] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["states", b"states", "store_name", b"store_name"]) -> None: ... - -global___DeleteBulkStateRequest = DeleteBulkStateRequest - -@typing_extensions.final -class SaveStateRequest(google.protobuf.message.Message): - """SaveStateRequest is the message to save multiple states into state store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STORE_NAME_FIELD_NUMBER: builtins.int - STATES_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store.""" - @property - def states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[dapr.proto.common.v1.common_pb2.StateItem]: - """The array of the state key values.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - states: collections.abc.Iterable[dapr.proto.common.v1.common_pb2.StateItem] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["states", b"states", "store_name", b"store_name"]) -> None: ... - -global___SaveStateRequest = SaveStateRequest - -@typing_extensions.final -class QueryStateRequest(google.protobuf.message.Message): - """QueryStateRequest is the message to query state store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - QUERY_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of state store.""" - query: builtins.str - """The query in JSON format.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to state store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - query: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "query", b"query", "store_name", b"store_name"]) -> None: ... - -global___QueryStateRequest = QueryStateRequest - -@typing_extensions.final -class QueryStateItem(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - ETAG_FIELD_NUMBER: builtins.int - ERROR_FIELD_NUMBER: builtins.int - key: builtins.str - """The object key.""" - data: builtins.bytes - """The object value.""" - etag: builtins.str - """The entity tag which represents the specific version of data. - ETag format is defined by the corresponding data store. - """ - error: builtins.str - """The error message indicating an error in processing of the query result.""" - def __init__( - self, - *, - key: builtins.str = ..., - data: builtins.bytes = ..., - etag: builtins.str = ..., - error: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "error", b"error", "etag", b"etag", "key", b"key"]) -> None: ... - -global___QueryStateItem = QueryStateItem - -@typing_extensions.final -class QueryStateResponse(google.protobuf.message.Message): - """QueryStateResponse is the response conveying the query results.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - RESULTS_FIELD_NUMBER: builtins.int - TOKEN_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - @property - def results(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___QueryStateItem]: - """An array of query results.""" - token: builtins.str - """Pagination token.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to app.""" - def __init__( - self, - *, - results: collections.abc.Iterable[global___QueryStateItem] | None = ..., - token: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "results", b"results", "token", b"token"]) -> None: ... - -global___QueryStateResponse = QueryStateResponse - -@typing_extensions.final -class PublishEventRequest(google.protobuf.message.Message): - """PublishEventRequest is the message to publish event data to pubsub topic""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - PUBSUB_NAME_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - DATA_CONTENT_TYPE_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - pubsub_name: builtins.str - """The name of the pubsub component""" - topic: builtins.str - """The pubsub topic""" - data: builtins.bytes - """The data which will be published to topic.""" - data_content_type: builtins.str - """The content type for the data (optional).""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata passing to pub components - - metadata property: - - key : the key of the message. - """ - def __init__( - self, - *, - pubsub_name: builtins.str = ..., - topic: builtins.str = ..., - data: builtins.bytes = ..., - data_content_type: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "data_content_type", b"data_content_type", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "topic", b"topic"]) -> None: ... - -global___PublishEventRequest = PublishEventRequest - -@typing_extensions.final -class BulkPublishRequest(google.protobuf.message.Message): - """BulkPublishRequest is the message to bulk publish events to pubsub topic""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - PUBSUB_NAME_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - ENTRIES_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - pubsub_name: builtins.str - """The name of the pubsub component""" - topic: builtins.str - """The pubsub topic""" - @property - def entries(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BulkPublishRequestEntry]: - """The entries which contain the individual events and associated details to be published""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The request level metadata passing to to the pubsub components""" - def __init__( - self, - *, - pubsub_name: builtins.str = ..., - topic: builtins.str = ..., - entries: collections.abc.Iterable[global___BulkPublishRequestEntry] | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["entries", b"entries", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "topic", b"topic"]) -> None: ... - -global___BulkPublishRequest = BulkPublishRequest - -@typing_extensions.final -class BulkPublishRequestEntry(google.protobuf.message.Message): - """BulkPublishRequestEntry is the message containing the event to be bulk published""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ENTRY_ID_FIELD_NUMBER: builtins.int - EVENT_FIELD_NUMBER: builtins.int - CONTENT_TYPE_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - entry_id: builtins.str - """The request scoped unique ID referring to this message. Used to map status in response""" - event: builtins.bytes - """The event which will be pulished to the topic""" - content_type: builtins.str - """The content type for the event""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The event level metadata passing to the pubsub component""" - def __init__( - self, - *, - entry_id: builtins.str = ..., - event: builtins.bytes = ..., - content_type: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "entry_id", b"entry_id", "event", b"event", "metadata", b"metadata"]) -> None: ... - -global___BulkPublishRequestEntry = BulkPublishRequestEntry - -@typing_extensions.final -class BulkPublishResponse(google.protobuf.message.Message): - """BulkPublishResponse is the message returned from a BulkPublishEvent call""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - FAILEDENTRIES_FIELD_NUMBER: builtins.int - @property - def failedEntries(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BulkPublishResponseFailedEntry]: - """The entries for different events that failed publish in the BulkPublishEvent call""" - def __init__( - self, - *, - failedEntries: collections.abc.Iterable[global___BulkPublishResponseFailedEntry] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["failedEntries", b"failedEntries"]) -> None: ... - -global___BulkPublishResponse = BulkPublishResponse - -@typing_extensions.final -class BulkPublishResponseFailedEntry(google.protobuf.message.Message): - """BulkPublishResponseFailedEntry is the message containing the entryID and error of a failed event in BulkPublishEvent call""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ENTRY_ID_FIELD_NUMBER: builtins.int - ERROR_FIELD_NUMBER: builtins.int - entry_id: builtins.str - """The response scoped unique ID referring to this message""" - error: builtins.str - """The error message if any on failure""" - def __init__( - self, - *, - entry_id: builtins.str = ..., - error: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["entry_id", b"entry_id", "error", b"error"]) -> None: ... - -global___BulkPublishResponseFailedEntry = BulkPublishResponseFailedEntry - -@typing_extensions.final -class SubscribeTopicEventsRequestAlpha1(google.protobuf.message.Message): - """SubscribeTopicEventsRequestAlpha1 is a message containing the details for - subscribing to a topic via streaming. - The first message must always be the initial request. All subsequent - messages must be event processed responses. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INITIAL_REQUEST_FIELD_NUMBER: builtins.int - EVENT_PROCESSED_FIELD_NUMBER: builtins.int - @property - def initial_request(self) -> global___SubscribeTopicEventsRequestInitialAlpha1: ... - @property - def event_processed(self) -> global___SubscribeTopicEventsRequestProcessedAlpha1: ... - def __init__( - self, - *, - initial_request: global___SubscribeTopicEventsRequestInitialAlpha1 | None = ..., - event_processed: global___SubscribeTopicEventsRequestProcessedAlpha1 | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["event_processed", b"event_processed", "initial_request", b"initial_request", "subscribe_topic_events_request_type", b"subscribe_topic_events_request_type"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["event_processed", b"event_processed", "initial_request", b"initial_request", "subscribe_topic_events_request_type", b"subscribe_topic_events_request_type"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["subscribe_topic_events_request_type", b"subscribe_topic_events_request_type"]) -> typing_extensions.Literal["initial_request", "event_processed"] | None: ... - -global___SubscribeTopicEventsRequestAlpha1 = SubscribeTopicEventsRequestAlpha1 - -@typing_extensions.final -class SubscribeTopicEventsRequestInitialAlpha1(google.protobuf.message.Message): - """SubscribeTopicEventsRequestInitialAlpha1 is the initial message containing - the details for subscribing to a topic via streaming. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - PUBSUB_NAME_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - DEAD_LETTER_TOPIC_FIELD_NUMBER: builtins.int - pubsub_name: builtins.str - """The name of the pubsub component""" - topic: builtins.str - """The pubsub topic""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata passing to pub components - - metadata property: - - key : the key of the message. - """ - dead_letter_topic: builtins.str - """dead_letter_topic is the topic to which messages that fail to be processed - are sent. - """ - def __init__( - self, - *, - pubsub_name: builtins.str = ..., - topic: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - dead_letter_topic: builtins.str | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_dead_letter_topic", b"_dead_letter_topic", "dead_letter_topic", b"dead_letter_topic"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_dead_letter_topic", b"_dead_letter_topic", "dead_letter_topic", b"dead_letter_topic", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "topic", b"topic"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["_dead_letter_topic", b"_dead_letter_topic"]) -> typing_extensions.Literal["dead_letter_topic"] | None: ... - -global___SubscribeTopicEventsRequestInitialAlpha1 = SubscribeTopicEventsRequestInitialAlpha1 - -@typing_extensions.final -class SubscribeTopicEventsRequestProcessedAlpha1(google.protobuf.message.Message): - """SubscribeTopicEventsRequestProcessedAlpha1 is the message containing the - subscription to a topic. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ID_FIELD_NUMBER: builtins.int - STATUS_FIELD_NUMBER: builtins.int - id: builtins.str - """id is the unique identifier for the subscription request.""" - @property - def status(self) -> dapr.proto.runtime.v1.appcallback_pb2.TopicEventResponse: - """status is the result of the subscription request.""" - def __init__( - self, - *, - id: builtins.str = ..., - status: dapr.proto.runtime.v1.appcallback_pb2.TopicEventResponse | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["status", b"status"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "status", b"status"]) -> None: ... - -global___SubscribeTopicEventsRequestProcessedAlpha1 = SubscribeTopicEventsRequestProcessedAlpha1 - -@typing_extensions.final -class SubscribeTopicEventsResponseAlpha1(google.protobuf.message.Message): - """SubscribeTopicEventsResponseAlpha1 is a message returned from daprd - when subscribing to a topic via streaming. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INITIAL_RESPONSE_FIELD_NUMBER: builtins.int - EVENT_MESSAGE_FIELD_NUMBER: builtins.int - @property - def initial_response(self) -> global___SubscribeTopicEventsResponseInitialAlpha1: ... - @property - def event_message(self) -> dapr.proto.runtime.v1.appcallback_pb2.TopicEventRequest: ... - def __init__( - self, - *, - initial_response: global___SubscribeTopicEventsResponseInitialAlpha1 | None = ..., - event_message: dapr.proto.runtime.v1.appcallback_pb2.TopicEventRequest | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["event_message", b"event_message", "initial_response", b"initial_response", "subscribe_topic_events_response_type", b"subscribe_topic_events_response_type"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["event_message", b"event_message", "initial_response", b"initial_response", "subscribe_topic_events_response_type", b"subscribe_topic_events_response_type"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["subscribe_topic_events_response_type", b"subscribe_topic_events_response_type"]) -> typing_extensions.Literal["initial_response", "event_message"] | None: ... - -global___SubscribeTopicEventsResponseAlpha1 = SubscribeTopicEventsResponseAlpha1 - -@typing_extensions.final -class SubscribeTopicEventsResponseInitialAlpha1(google.protobuf.message.Message): - """SubscribeTopicEventsResponseInitialAlpha1 is the initial response from daprd - when subscribing to a topic. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___SubscribeTopicEventsResponseInitialAlpha1 = SubscribeTopicEventsResponseInitialAlpha1 - -@typing_extensions.final -class InvokeBindingRequest(google.protobuf.message.Message): - """InvokeBindingRequest is the message to send data to output bindings""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - NAME_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - OPERATION_FIELD_NUMBER: builtins.int - name: builtins.str - """The name of the output binding to invoke.""" - data: builtins.bytes - """The data which will be sent to output binding.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata passing to output binding components - - Common metadata property: - - ttlInSeconds : the time to live in seconds for the message. - - If set in the binding definition will cause all messages to - have a default time to live. The message ttl overrides any value - in the binding definition. - """ - operation: builtins.str - """The name of the operation type for the binding to invoke""" - def __init__( - self, - *, - name: builtins.str = ..., - data: builtins.bytes = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - operation: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata", "name", b"name", "operation", b"operation"]) -> None: ... - -global___InvokeBindingRequest = InvokeBindingRequest - -@typing_extensions.final -class InvokeBindingResponse(google.protobuf.message.Message): - """InvokeBindingResponse is the message returned from an output binding invocation""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - DATA_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - data: builtins.bytes - """The data which will be sent to output binding.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata returned from an external system""" - def __init__( - self, - *, - data: builtins.bytes = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata"]) -> None: ... - -global___InvokeBindingResponse = InvokeBindingResponse - -@typing_extensions.final -class GetSecretRequest(google.protobuf.message.Message): - """GetSecretRequest is the message to get secret from secret store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - KEY_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of secret store.""" - key: builtins.str - """The name of secret key.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to secret store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - key: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... - -global___GetSecretRequest = GetSecretRequest - -@typing_extensions.final -class GetSecretResponse(google.protobuf.message.Message): - """GetSecretResponse is the response message to convey the requested secret.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class DataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - DATA_FIELD_NUMBER: builtins.int - @property - def data(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """data is the secret value. Some secret store, such as kubernetes secret - store, can save multiple secrets for single secret key. - """ - def __init__( - self, - *, - data: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data"]) -> None: ... - -global___GetSecretResponse = GetSecretResponse - -@typing_extensions.final -class GetBulkSecretRequest(google.protobuf.message.Message): - """GetBulkSecretRequest is the message to get the secrets from secret store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of secret store.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to secret store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "store_name", b"store_name"]) -> None: ... - -global___GetBulkSecretRequest = GetBulkSecretRequest - -@typing_extensions.final -class SecretResponse(google.protobuf.message.Message): - """SecretResponse is a map of decrypted string/string values""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class SecretsEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - SECRETS_FIELD_NUMBER: builtins.int - @property - def secrets(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... - def __init__( - self, - *, - secrets: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["secrets", b"secrets"]) -> None: ... - -global___SecretResponse = SecretResponse - -@typing_extensions.final -class GetBulkSecretResponse(google.protobuf.message.Message): - """GetBulkSecretResponse is the response message to convey the requested secrets.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class DataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - @property - def value(self) -> global___SecretResponse: ... - def __init__( - self, - *, - key: builtins.str = ..., - value: global___SecretResponse | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - DATA_FIELD_NUMBER: builtins.int - @property - def data(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___SecretResponse]: - """data hold the secret values. Some secret store, such as kubernetes secret - store, can save multiple secrets for single secret key. - """ - def __init__( - self, - *, - data: collections.abc.Mapping[builtins.str, global___SecretResponse] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data"]) -> None: ... - -global___GetBulkSecretResponse = GetBulkSecretResponse - -@typing_extensions.final -class TransactionalStateOperation(google.protobuf.message.Message): - """TransactionalStateOperation is the message to execute a specified operation with a key-value pair.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - OPERATIONTYPE_FIELD_NUMBER: builtins.int - REQUEST_FIELD_NUMBER: builtins.int - operationType: builtins.str - """The type of operation to be executed""" - @property - def request(self) -> dapr.proto.common.v1.common_pb2.StateItem: - """State values to be operated on""" - def __init__( - self, - *, - operationType: builtins.str = ..., - request: dapr.proto.common.v1.common_pb2.StateItem | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["request", b"request"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["operationType", b"operationType", "request", b"request"]) -> None: ... - -global___TransactionalStateOperation = TransactionalStateOperation - -@typing_extensions.final -class ExecuteStateTransactionRequest(google.protobuf.message.Message): - """ExecuteStateTransactionRequest is the message to execute multiple operations on a specified store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORENAME_FIELD_NUMBER: builtins.int - OPERATIONS_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - storeName: builtins.str - """Required. name of state store.""" - @property - def operations(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TransactionalStateOperation]: - """Required. transactional operation list.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata used for transactional operations.""" - def __init__( - self, - *, - storeName: builtins.str = ..., - operations: collections.abc.Iterable[global___TransactionalStateOperation] | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "operations", b"operations", "storeName", b"storeName"]) -> None: ... - -global___ExecuteStateTransactionRequest = ExecuteStateTransactionRequest - -@typing_extensions.final -class RegisterActorTimerRequest(google.protobuf.message.Message): - """RegisterActorTimerRequest is the message to register a timer for an actor of a given type and id.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - DUE_TIME_FIELD_NUMBER: builtins.int - PERIOD_FIELD_NUMBER: builtins.int - CALLBACK_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - TTL_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - name: builtins.str - due_time: builtins.str - period: builtins.str - callback: builtins.str - data: builtins.bytes - ttl: builtins.str - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - name: builtins.str = ..., - due_time: builtins.str = ..., - period: builtins.str = ..., - callback: builtins.str = ..., - data: builtins.bytes = ..., - ttl: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "callback", b"callback", "data", b"data", "due_time", b"due_time", "name", b"name", "period", b"period", "ttl", b"ttl"]) -> None: ... - -global___RegisterActorTimerRequest = RegisterActorTimerRequest - -@typing_extensions.final -class UnregisterActorTimerRequest(google.protobuf.message.Message): - """UnregisterActorTimerRequest is the message to unregister an actor timer""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - name: builtins.str - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "name", b"name"]) -> None: ... - -global___UnregisterActorTimerRequest = UnregisterActorTimerRequest - -@typing_extensions.final -class RegisterActorReminderRequest(google.protobuf.message.Message): - """RegisterActorReminderRequest is the message to register a reminder for an actor of a given type and id.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - DUE_TIME_FIELD_NUMBER: builtins.int - PERIOD_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - TTL_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - name: builtins.str - due_time: builtins.str - period: builtins.str - data: builtins.bytes - ttl: builtins.str - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - name: builtins.str = ..., - due_time: builtins.str = ..., - period: builtins.str = ..., - data: builtins.bytes = ..., - ttl: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "data", b"data", "due_time", b"due_time", "name", b"name", "period", b"period", "ttl", b"ttl"]) -> None: ... - -global___RegisterActorReminderRequest = RegisterActorReminderRequest - -@typing_extensions.final -class UnregisterActorReminderRequest(google.protobuf.message.Message): - """UnregisterActorReminderRequest is the message to unregister an actor reminder.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - name: builtins.str - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "name", b"name"]) -> None: ... - -global___UnregisterActorReminderRequest = UnregisterActorReminderRequest - -@typing_extensions.final -class GetActorStateRequest(google.protobuf.message.Message): - """GetActorStateRequest is the message to get key-value states from specific actor.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - KEY_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - key: builtins.str - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - key: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "key", b"key"]) -> None: ... - -global___GetActorStateRequest = GetActorStateRequest - -@typing_extensions.final -class GetActorStateResponse(google.protobuf.message.Message): - """GetActorStateResponse is the response conveying the actor's state value.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - DATA_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - data: builtins.bytes - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to app.""" - def __init__( - self, - *, - data: builtins.bytes = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata"]) -> None: ... - -global___GetActorStateResponse = GetActorStateResponse - -@typing_extensions.final -class ExecuteActorStateTransactionRequest(google.protobuf.message.Message): - """ExecuteActorStateTransactionRequest is the message to execute multiple operations on a specified actor.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - OPERATIONS_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - @property - def operations(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TransactionalActorStateOperation]: ... - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - operations: collections.abc.Iterable[global___TransactionalActorStateOperation] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "operations", b"operations"]) -> None: ... - -global___ExecuteActorStateTransactionRequest = ExecuteActorStateTransactionRequest - -@typing_extensions.final -class TransactionalActorStateOperation(google.protobuf.message.Message): - """TransactionalActorStateOperation is the message to execute a specified operation with a key-value pair.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - OPERATIONTYPE_FIELD_NUMBER: builtins.int - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - operationType: builtins.str - key: builtins.str - @property - def value(self) -> google.protobuf.any_pb2.Any: ... - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata used for transactional operations. - - Common metadata property: - - ttlInSeconds : the time to live in seconds for the stored value. - """ - def __init__( - self, - *, - operationType: builtins.str = ..., - key: builtins.str = ..., - value: google.protobuf.any_pb2.Any | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "metadata", b"metadata", "operationType", b"operationType", "value", b"value"]) -> None: ... - -global___TransactionalActorStateOperation = TransactionalActorStateOperation - -@typing_extensions.final -class InvokeActorRequest(google.protobuf.message.Message): - """InvokeActorRequest is the message to call an actor.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ACTOR_TYPE_FIELD_NUMBER: builtins.int - ACTOR_ID_FIELD_NUMBER: builtins.int - METHOD_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - actor_type: builtins.str - actor_id: builtins.str - method: builtins.str - data: builtins.bytes - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... - def __init__( - self, - *, - actor_type: builtins.str = ..., - actor_id: builtins.str = ..., - method: builtins.str = ..., - data: builtins.bytes = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "data", b"data", "metadata", b"metadata", "method", b"method"]) -> None: ... - -global___InvokeActorRequest = InvokeActorRequest - -@typing_extensions.final -class InvokeActorResponse(google.protobuf.message.Message): - """InvokeActorResponse is the method that returns an actor invocation response.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - DATA_FIELD_NUMBER: builtins.int - data: builtins.bytes - def __init__( - self, - *, - data: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["data", b"data"]) -> None: ... - -global___InvokeActorResponse = InvokeActorResponse - -@typing_extensions.final -class GetMetadataRequest(google.protobuf.message.Message): - """GetMetadataRequest is the message for the GetMetadata request. - Empty - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___GetMetadataRequest = GetMetadataRequest - -@typing_extensions.final -class GetMetadataResponse(google.protobuf.message.Message): - """GetMetadataResponse is a message that is returned on GetMetadata rpc call.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class ExtendedMetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ID_FIELD_NUMBER: builtins.int - ACTIVE_ACTORS_COUNT_FIELD_NUMBER: builtins.int - REGISTERED_COMPONENTS_FIELD_NUMBER: builtins.int - EXTENDED_METADATA_FIELD_NUMBER: builtins.int - SUBSCRIPTIONS_FIELD_NUMBER: builtins.int - HTTP_ENDPOINTS_FIELD_NUMBER: builtins.int - APP_CONNECTION_PROPERTIES_FIELD_NUMBER: builtins.int - RUNTIME_VERSION_FIELD_NUMBER: builtins.int - ENABLED_FEATURES_FIELD_NUMBER: builtins.int - ACTOR_RUNTIME_FIELD_NUMBER: builtins.int - id: builtins.str - @property - def active_actors_count(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ActiveActorsCount]: - """Deprecated alias for actor_runtime.active_actors.""" - @property - def registered_components(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___RegisteredComponents]: ... - @property - def extended_metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... - @property - def subscriptions(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PubsubSubscription]: ... - @property - def http_endpoints(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___MetadataHTTPEndpoint]: ... - @property - def app_connection_properties(self) -> global___AppConnectionProperties: ... - runtime_version: builtins.str - @property - def enabled_features(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - @property - def actor_runtime(self) -> global___ActorRuntime: - """TODO: Cassie: probably add scheduler runtime status""" - def __init__( - self, - *, - id: builtins.str = ..., - active_actors_count: collections.abc.Iterable[global___ActiveActorsCount] | None = ..., - registered_components: collections.abc.Iterable[global___RegisteredComponents] | None = ..., - extended_metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - subscriptions: collections.abc.Iterable[global___PubsubSubscription] | None = ..., - http_endpoints: collections.abc.Iterable[global___MetadataHTTPEndpoint] | None = ..., - app_connection_properties: global___AppConnectionProperties | None = ..., - runtime_version: builtins.str = ..., - enabled_features: collections.abc.Iterable[builtins.str] | None = ..., - actor_runtime: global___ActorRuntime | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["actor_runtime", b"actor_runtime", "app_connection_properties", b"app_connection_properties"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["active_actors_count", b"active_actors_count", "actor_runtime", b"actor_runtime", "app_connection_properties", b"app_connection_properties", "enabled_features", b"enabled_features", "extended_metadata", b"extended_metadata", "http_endpoints", b"http_endpoints", "id", b"id", "registered_components", b"registered_components", "runtime_version", b"runtime_version", "subscriptions", b"subscriptions"]) -> None: ... - -global___GetMetadataResponse = GetMetadataResponse - -@typing_extensions.final -class ActorRuntime(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _ActorRuntimeStatus: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _ActorRuntimeStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ActorRuntime._ActorRuntimeStatus.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - INITIALIZING: ActorRuntime._ActorRuntimeStatus.ValueType # 0 - """Indicates that the actor runtime is still being initialized.""" - DISABLED: ActorRuntime._ActorRuntimeStatus.ValueType # 1 - """Indicates that the actor runtime is disabled. - This normally happens when Dapr is started without "placement-host-address" - """ - RUNNING: ActorRuntime._ActorRuntimeStatus.ValueType # 2 - """Indicates the actor runtime is running, either as an actor host or client.""" - - class ActorRuntimeStatus(_ActorRuntimeStatus, metaclass=_ActorRuntimeStatusEnumTypeWrapper): ... - INITIALIZING: ActorRuntime.ActorRuntimeStatus.ValueType # 0 - """Indicates that the actor runtime is still being initialized.""" - DISABLED: ActorRuntime.ActorRuntimeStatus.ValueType # 1 - """Indicates that the actor runtime is disabled. - This normally happens when Dapr is started without "placement-host-address" - """ - RUNNING: ActorRuntime.ActorRuntimeStatus.ValueType # 2 - """Indicates the actor runtime is running, either as an actor host or client.""" - - RUNTIME_STATUS_FIELD_NUMBER: builtins.int - ACTIVE_ACTORS_FIELD_NUMBER: builtins.int - HOST_READY_FIELD_NUMBER: builtins.int - PLACEMENT_FIELD_NUMBER: builtins.int - runtime_status: global___ActorRuntime.ActorRuntimeStatus.ValueType - """Contains an enum indicating whether the actor runtime has been initialized.""" - @property - def active_actors(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ActiveActorsCount]: - """Count of active actors per type.""" - host_ready: builtins.bool - """Indicates whether the actor runtime is ready to host actors.""" - placement: builtins.str - """Custom message from the placement provider.""" - def __init__( - self, - *, - runtime_status: global___ActorRuntime.ActorRuntimeStatus.ValueType = ..., - active_actors: collections.abc.Iterable[global___ActiveActorsCount] | None = ..., - host_ready: builtins.bool = ..., - placement: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["active_actors", b"active_actors", "host_ready", b"host_ready", "placement", b"placement", "runtime_status", b"runtime_status"]) -> None: ... - -global___ActorRuntime = ActorRuntime - -@typing_extensions.final -class ActiveActorsCount(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - TYPE_FIELD_NUMBER: builtins.int - COUNT_FIELD_NUMBER: builtins.int - type: builtins.str - count: builtins.int - def __init__( - self, - *, - type: builtins.str = ..., - count: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["count", b"count", "type", b"type"]) -> None: ... - -global___ActiveActorsCount = ActiveActorsCount - -@typing_extensions.final -class RegisteredComponents(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - VERSION_FIELD_NUMBER: builtins.int - CAPABILITIES_FIELD_NUMBER: builtins.int - name: builtins.str - type: builtins.str - version: builtins.str - @property - def capabilities(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... - def __init__( - self, - *, - name: builtins.str = ..., - type: builtins.str = ..., - version: builtins.str = ..., - capabilities: collections.abc.Iterable[builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["capabilities", b"capabilities", "name", b"name", "type", b"type", "version", b"version"]) -> None: ... - -global___RegisteredComponents = RegisteredComponents - -@typing_extensions.final -class MetadataHTTPEndpoint(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - name: builtins.str - def __init__( - self, - *, - name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ... - -global___MetadataHTTPEndpoint = MetadataHTTPEndpoint - -@typing_extensions.final -class AppConnectionProperties(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PORT_FIELD_NUMBER: builtins.int - PROTOCOL_FIELD_NUMBER: builtins.int - CHANNEL_ADDRESS_FIELD_NUMBER: builtins.int - MAX_CONCURRENCY_FIELD_NUMBER: builtins.int - HEALTH_FIELD_NUMBER: builtins.int - port: builtins.int - protocol: builtins.str - channel_address: builtins.str - max_concurrency: builtins.int - @property - def health(self) -> global___AppConnectionHealthProperties: ... - def __init__( - self, - *, - port: builtins.int = ..., - protocol: builtins.str = ..., - channel_address: builtins.str = ..., - max_concurrency: builtins.int = ..., - health: global___AppConnectionHealthProperties | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["health", b"health"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["channel_address", b"channel_address", "health", b"health", "max_concurrency", b"max_concurrency", "port", b"port", "protocol", b"protocol"]) -> None: ... - -global___AppConnectionProperties = AppConnectionProperties - -@typing_extensions.final -class AppConnectionHealthProperties(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - HEALTH_CHECK_PATH_FIELD_NUMBER: builtins.int - HEALTH_PROBE_INTERVAL_FIELD_NUMBER: builtins.int - HEALTH_PROBE_TIMEOUT_FIELD_NUMBER: builtins.int - HEALTH_THRESHOLD_FIELD_NUMBER: builtins.int - health_check_path: builtins.str - health_probe_interval: builtins.str - health_probe_timeout: builtins.str - health_threshold: builtins.int - def __init__( - self, - *, - health_check_path: builtins.str = ..., - health_probe_interval: builtins.str = ..., - health_probe_timeout: builtins.str = ..., - health_threshold: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["health_check_path", b"health_check_path", "health_probe_interval", b"health_probe_interval", "health_probe_timeout", b"health_probe_timeout", "health_threshold", b"health_threshold"]) -> None: ... - -global___AppConnectionHealthProperties = AppConnectionHealthProperties - -@typing_extensions.final -class PubsubSubscription(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - PUBSUB_NAME_FIELD_NUMBER: builtins.int - TOPIC_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - RULES_FIELD_NUMBER: builtins.int - DEAD_LETTER_TOPIC_FIELD_NUMBER: builtins.int - TYPE_FIELD_NUMBER: builtins.int - pubsub_name: builtins.str - topic: builtins.str - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... - @property - def rules(self) -> global___PubsubSubscriptionRules: ... - dead_letter_topic: builtins.str - type: global___PubsubSubscriptionType.ValueType - def __init__( - self, - *, - pubsub_name: builtins.str = ..., - topic: builtins.str = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - rules: global___PubsubSubscriptionRules | None = ..., - dead_letter_topic: builtins.str = ..., - type: global___PubsubSubscriptionType.ValueType = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["rules", b"rules"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["dead_letter_topic", b"dead_letter_topic", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "rules", b"rules", "topic", b"topic", "type", b"type"]) -> None: ... - -global___PubsubSubscription = PubsubSubscription - -@typing_extensions.final -class PubsubSubscriptionRules(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - RULES_FIELD_NUMBER: builtins.int - @property - def rules(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PubsubSubscriptionRule]: ... - def __init__( - self, - *, - rules: collections.abc.Iterable[global___PubsubSubscriptionRule] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["rules", b"rules"]) -> None: ... - -global___PubsubSubscriptionRules = PubsubSubscriptionRules - -@typing_extensions.final -class PubsubSubscriptionRule(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MATCH_FIELD_NUMBER: builtins.int - PATH_FIELD_NUMBER: builtins.int - match: builtins.str - path: builtins.str - def __init__( - self, - *, - match: builtins.str = ..., - path: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["match", b"match", "path", b"path"]) -> None: ... - -global___PubsubSubscriptionRule = PubsubSubscriptionRule - -@typing_extensions.final -class SetMetadataRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - -global___SetMetadataRequest = SetMetadataRequest - -@typing_extensions.final -class GetConfigurationRequest(google.protobuf.message.Message): - """GetConfigurationRequest is the message to get a list of key-value configuration from specified configuration store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - KEYS_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """Required. The name of configuration store.""" - @property - def keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Optional. The key of the configuration item to fetch. - If set, only query for the specified configuration items. - Empty list means fetch all. - """ - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Optional. The metadata which will be sent to configuration store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - keys: collections.abc.Iterable[builtins.str] | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... - -global___GetConfigurationRequest = GetConfigurationRequest - -@typing_extensions.final -class GetConfigurationResponse(google.protobuf.message.Message): - """GetConfigurationResponse is the response conveying the list of configuration values. - It should be the FULL configuration of specified application which contains all of its configuration items. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class ItemsEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - @property - def value(self) -> dapr.proto.common.v1.common_pb2.ConfigurationItem: ... - def __init__( - self, - *, - key: builtins.str = ..., - value: dapr.proto.common.v1.common_pb2.ConfigurationItem | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ITEMS_FIELD_NUMBER: builtins.int - @property - def items(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem]: ... - def __init__( - self, - *, - items: collections.abc.Mapping[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["items", b"items"]) -> None: ... - -global___GetConfigurationResponse = GetConfigurationResponse - -@typing_extensions.final -class SubscribeConfigurationRequest(google.protobuf.message.Message): - """SubscribeConfigurationRequest is the message to get a list of key-value configuration from specified configuration store.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - STORE_NAME_FIELD_NUMBER: builtins.int - KEYS_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of configuration store.""" - @property - def keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: - """Optional. The key of the configuration item to fetch. - If set, only query for the specified configuration items. - Empty list means fetch all. - """ - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata which will be sent to configuration store components.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - keys: collections.abc.Iterable[builtins.str] | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... - -global___SubscribeConfigurationRequest = SubscribeConfigurationRequest - -@typing_extensions.final -class UnsubscribeConfigurationRequest(google.protobuf.message.Message): - """UnSubscribeConfigurationRequest is the message to stop watching the key-value configuration.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STORE_NAME_FIELD_NUMBER: builtins.int - ID_FIELD_NUMBER: builtins.int - store_name: builtins.str - """The name of configuration store.""" - id: builtins.str - """The id to unsubscribe.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - id: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "store_name", b"store_name"]) -> None: ... - -global___UnsubscribeConfigurationRequest = UnsubscribeConfigurationRequest - -@typing_extensions.final -class SubscribeConfigurationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class ItemsEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - @property - def value(self) -> dapr.proto.common.v1.common_pb2.ConfigurationItem: ... - def __init__( - self, - *, - key: builtins.str = ..., - value: dapr.proto.common.v1.common_pb2.ConfigurationItem | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - ID_FIELD_NUMBER: builtins.int - ITEMS_FIELD_NUMBER: builtins.int - id: builtins.str - """Subscribe id, used to stop subscription.""" - @property - def items(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem]: - """The list of items containing configuration values""" - def __init__( - self, - *, - id: builtins.str = ..., - items: collections.abc.Mapping[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "items", b"items"]) -> None: ... - -global___SubscribeConfigurationResponse = SubscribeConfigurationResponse - -@typing_extensions.final -class UnsubscribeConfigurationResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - OK_FIELD_NUMBER: builtins.int - MESSAGE_FIELD_NUMBER: builtins.int - ok: builtins.bool - message: builtins.str - def __init__( - self, - *, - ok: builtins.bool = ..., - message: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["message", b"message", "ok", b"ok"]) -> None: ... - -global___UnsubscribeConfigurationResponse = UnsubscribeConfigurationResponse - -@typing_extensions.final -class TryLockRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STORE_NAME_FIELD_NUMBER: builtins.int - RESOURCE_ID_FIELD_NUMBER: builtins.int - LOCK_OWNER_FIELD_NUMBER: builtins.int - EXPIRY_IN_SECONDS_FIELD_NUMBER: builtins.int - store_name: builtins.str - """Required. The lock store name,e.g. `redis`.""" - resource_id: builtins.str - """Required. resource_id is the lock key. e.g. `order_id_111` - It stands for "which resource I want to protect" - """ - lock_owner: builtins.str - """Required. lock_owner indicate the identifier of lock owner. - You can generate a uuid as lock_owner.For example,in golang: - - req.LockOwner = uuid.New().String() - - This field is per request,not per process,so it is different for each request, - which aims to prevent multi-thread in the same process trying the same lock concurrently. - - The reason why we don't make it automatically generated is: - 1. If it is automatically generated,there must be a 'my_lock_owner_id' field in the response. - This name is so weird that we think it is inappropriate to put it into the api spec - 2. If we change the field 'my_lock_owner_id' in the response to 'lock_owner',which means the current lock owner of this lock, - we find that in some lock services users can't get the current lock owner.Actually users don't need it at all. - 3. When reentrant lock is needed,the existing lock_owner is required to identify client and check "whether this client can reenter this lock". - So this field in the request shouldn't be removed. - """ - expiry_in_seconds: builtins.int - """Required. The time before expiry.The time unit is second.""" - def __init__( - self, - *, - store_name: builtins.str = ..., - resource_id: builtins.str = ..., - lock_owner: builtins.str = ..., - expiry_in_seconds: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["expiry_in_seconds", b"expiry_in_seconds", "lock_owner", b"lock_owner", "resource_id", b"resource_id", "store_name", b"store_name"]) -> None: ... - -global___TryLockRequest = TryLockRequest - -@typing_extensions.final -class TryLockResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SUCCESS_FIELD_NUMBER: builtins.int - success: builtins.bool - def __init__( - self, - *, - success: builtins.bool = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["success", b"success"]) -> None: ... - -global___TryLockResponse = TryLockResponse - -@typing_extensions.final -class UnlockRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - STORE_NAME_FIELD_NUMBER: builtins.int - RESOURCE_ID_FIELD_NUMBER: builtins.int - LOCK_OWNER_FIELD_NUMBER: builtins.int - store_name: builtins.str - resource_id: builtins.str - """resource_id is the lock key.""" - lock_owner: builtins.str - def __init__( - self, - *, - store_name: builtins.str = ..., - resource_id: builtins.str = ..., - lock_owner: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["lock_owner", b"lock_owner", "resource_id", b"resource_id", "store_name", b"store_name"]) -> None: ... - -global___UnlockRequest = UnlockRequest - -@typing_extensions.final -class UnlockResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _Status: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _StatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[UnlockResponse._Status.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - SUCCESS: UnlockResponse._Status.ValueType # 0 - LOCK_DOES_NOT_EXIST: UnlockResponse._Status.ValueType # 1 - LOCK_BELONGS_TO_OTHERS: UnlockResponse._Status.ValueType # 2 - INTERNAL_ERROR: UnlockResponse._Status.ValueType # 3 - - class Status(_Status, metaclass=_StatusEnumTypeWrapper): ... - SUCCESS: UnlockResponse.Status.ValueType # 0 - LOCK_DOES_NOT_EXIST: UnlockResponse.Status.ValueType # 1 - LOCK_BELONGS_TO_OTHERS: UnlockResponse.Status.ValueType # 2 - INTERNAL_ERROR: UnlockResponse.Status.ValueType # 3 - - STATUS_FIELD_NUMBER: builtins.int - status: global___UnlockResponse.Status.ValueType - def __init__( - self, - *, - status: global___UnlockResponse.Status.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["status", b"status"]) -> None: ... - -global___UnlockResponse = UnlockResponse - -@typing_extensions.final -class SubtleGetKeyRequest(google.protobuf.message.Message): - """SubtleGetKeyRequest is the request object for SubtleGetKeyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - class _KeyFormat: - ValueType = typing.NewType("ValueType", builtins.int) - V: typing_extensions.TypeAlias = ValueType - - class _KeyFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SubtleGetKeyRequest._KeyFormat.ValueType], builtins.type): - DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor - PEM: SubtleGetKeyRequest._KeyFormat.ValueType # 0 - """PEM (PKIX) (default)""" - JSON: SubtleGetKeyRequest._KeyFormat.ValueType # 1 - """JSON (JSON Web Key) as string""" - - class KeyFormat(_KeyFormat, metaclass=_KeyFormatEnumTypeWrapper): ... - PEM: SubtleGetKeyRequest.KeyFormat.ValueType # 0 - """PEM (PKIX) (default)""" - JSON: SubtleGetKeyRequest.KeyFormat.ValueType # 1 - """JSON (JSON Web Key) as string""" - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - NAME_FIELD_NUMBER: builtins.int - FORMAT_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - name: builtins.str - """Name (or name/version) of the key to use in the key vault""" - format: global___SubtleGetKeyRequest.KeyFormat.ValueType - """Response format""" - def __init__( - self, - *, - component_name: builtins.str = ..., - name: builtins.str = ..., - format: global___SubtleGetKeyRequest.KeyFormat.ValueType = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["component_name", b"component_name", "format", b"format", "name", b"name"]) -> None: ... - -global___SubtleGetKeyRequest = SubtleGetKeyRequest - -@typing_extensions.final -class SubtleGetKeyResponse(google.protobuf.message.Message): - """SubtleGetKeyResponse is the response for SubtleGetKeyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - PUBLIC_KEY_FIELD_NUMBER: builtins.int - name: builtins.str - """Name (or name/version) of the key. - This is returned as response too in case there is a version. - """ - public_key: builtins.str - """Public key, encoded in the requested format""" - def __init__( - self, - *, - name: builtins.str = ..., - public_key: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "public_key", b"public_key"]) -> None: ... - -global___SubtleGetKeyResponse = SubtleGetKeyResponse - -@typing_extensions.final -class SubtleEncryptRequest(google.protobuf.message.Message): - """SubtleEncryptRequest is the request for SubtleEncryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - PLAINTEXT_FIELD_NUMBER: builtins.int - ALGORITHM_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - NONCE_FIELD_NUMBER: builtins.int - ASSOCIATED_DATA_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - plaintext: builtins.bytes - """Message to encrypt.""" - algorithm: builtins.str - """Algorithm to use, as in the JWA standard.""" - key_name: builtins.str - """Name (or name/version) of the key.""" - nonce: builtins.bytes - """Nonce / initialization vector. - Ignored with asymmetric ciphers. - """ - associated_data: builtins.bytes - """Associated Data when using AEAD ciphers (optional).""" - def __init__( - self, - *, - component_name: builtins.str = ..., - plaintext: builtins.bytes = ..., - algorithm: builtins.str = ..., - key_name: builtins.str = ..., - nonce: builtins.bytes = ..., - associated_data: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "plaintext", b"plaintext"]) -> None: ... - -global___SubtleEncryptRequest = SubtleEncryptRequest - -@typing_extensions.final -class SubtleEncryptResponse(google.protobuf.message.Message): - """SubtleEncryptResponse is the response for SubtleEncryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CIPHERTEXT_FIELD_NUMBER: builtins.int - TAG_FIELD_NUMBER: builtins.int - ciphertext: builtins.bytes - """Encrypted ciphertext.""" - tag: builtins.bytes - """Authentication tag. - This is nil when not using an authenticated cipher. - """ - def __init__( - self, - *, - ciphertext: builtins.bytes = ..., - tag: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["ciphertext", b"ciphertext", "tag", b"tag"]) -> None: ... - -global___SubtleEncryptResponse = SubtleEncryptResponse - -@typing_extensions.final -class SubtleDecryptRequest(google.protobuf.message.Message): - """SubtleDecryptRequest is the request for SubtleDecryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - CIPHERTEXT_FIELD_NUMBER: builtins.int - ALGORITHM_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - NONCE_FIELD_NUMBER: builtins.int - TAG_FIELD_NUMBER: builtins.int - ASSOCIATED_DATA_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - ciphertext: builtins.bytes - """Message to decrypt.""" - algorithm: builtins.str - """Algorithm to use, as in the JWA standard.""" - key_name: builtins.str - """Name (or name/version) of the key.""" - nonce: builtins.bytes - """Nonce / initialization vector. - Ignored with asymmetric ciphers. - """ - tag: builtins.bytes - """Authentication tag. - This is nil when not using an authenticated cipher. - """ - associated_data: builtins.bytes - """Associated Data when using AEAD ciphers (optional).""" - def __init__( - self, - *, - component_name: builtins.str = ..., - ciphertext: builtins.bytes = ..., - algorithm: builtins.str = ..., - key_name: builtins.str = ..., - nonce: builtins.bytes = ..., - tag: builtins.bytes = ..., - associated_data: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "ciphertext", b"ciphertext", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "tag", b"tag"]) -> None: ... - -global___SubtleDecryptRequest = SubtleDecryptRequest - -@typing_extensions.final -class SubtleDecryptResponse(google.protobuf.message.Message): - """SubtleDecryptResponse is the response for SubtleDecryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PLAINTEXT_FIELD_NUMBER: builtins.int - plaintext: builtins.bytes - """Decrypted plaintext.""" - def __init__( - self, - *, - plaintext: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["plaintext", b"plaintext"]) -> None: ... - -global___SubtleDecryptResponse = SubtleDecryptResponse - -@typing_extensions.final -class SubtleWrapKeyRequest(google.protobuf.message.Message): - """SubtleWrapKeyRequest is the request for SubtleWrapKeyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - PLAINTEXT_KEY_FIELD_NUMBER: builtins.int - ALGORITHM_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - NONCE_FIELD_NUMBER: builtins.int - ASSOCIATED_DATA_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - plaintext_key: builtins.bytes - """Key to wrap""" - algorithm: builtins.str - """Algorithm to use, as in the JWA standard.""" - key_name: builtins.str - """Name (or name/version) of the key.""" - nonce: builtins.bytes - """Nonce / initialization vector. - Ignored with asymmetric ciphers. - """ - associated_data: builtins.bytes - """Associated Data when using AEAD ciphers (optional).""" - def __init__( - self, - *, - component_name: builtins.str = ..., - plaintext_key: builtins.bytes = ..., - algorithm: builtins.str = ..., - key_name: builtins.str = ..., - nonce: builtins.bytes = ..., - associated_data: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "plaintext_key", b"plaintext_key"]) -> None: ... - -global___SubtleWrapKeyRequest = SubtleWrapKeyRequest - -@typing_extensions.final -class SubtleWrapKeyResponse(google.protobuf.message.Message): - """SubtleWrapKeyResponse is the response for SubtleWrapKeyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - WRAPPED_KEY_FIELD_NUMBER: builtins.int - TAG_FIELD_NUMBER: builtins.int - wrapped_key: builtins.bytes - """Wrapped key.""" - tag: builtins.bytes - """Authentication tag. - This is nil when not using an authenticated cipher. - """ - def __init__( - self, - *, - wrapped_key: builtins.bytes = ..., - tag: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["tag", b"tag", "wrapped_key", b"wrapped_key"]) -> None: ... - -global___SubtleWrapKeyResponse = SubtleWrapKeyResponse - -@typing_extensions.final -class SubtleUnwrapKeyRequest(google.protobuf.message.Message): - """SubtleUnwrapKeyRequest is the request for SubtleUnwrapKeyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - WRAPPED_KEY_FIELD_NUMBER: builtins.int - ALGORITHM_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - NONCE_FIELD_NUMBER: builtins.int - TAG_FIELD_NUMBER: builtins.int - ASSOCIATED_DATA_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - wrapped_key: builtins.bytes - """Wrapped key.""" - algorithm: builtins.str - """Algorithm to use, as in the JWA standard.""" - key_name: builtins.str - """Name (or name/version) of the key.""" - nonce: builtins.bytes - """Nonce / initialization vector. - Ignored with asymmetric ciphers. - """ - tag: builtins.bytes - """Authentication tag. - This is nil when not using an authenticated cipher. - """ - associated_data: builtins.bytes - """Associated Data when using AEAD ciphers (optional).""" - def __init__( - self, - *, - component_name: builtins.str = ..., - wrapped_key: builtins.bytes = ..., - algorithm: builtins.str = ..., - key_name: builtins.str = ..., - nonce: builtins.bytes = ..., - tag: builtins.bytes = ..., - associated_data: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "tag", b"tag", "wrapped_key", b"wrapped_key"]) -> None: ... - -global___SubtleUnwrapKeyRequest = SubtleUnwrapKeyRequest - -@typing_extensions.final -class SubtleUnwrapKeyResponse(google.protobuf.message.Message): - """SubtleUnwrapKeyResponse is the response for SubtleUnwrapKeyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PLAINTEXT_KEY_FIELD_NUMBER: builtins.int - plaintext_key: builtins.bytes - """Key in plaintext""" - def __init__( - self, - *, - plaintext_key: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["plaintext_key", b"plaintext_key"]) -> None: ... - -global___SubtleUnwrapKeyResponse = SubtleUnwrapKeyResponse - -@typing_extensions.final -class SubtleSignRequest(google.protobuf.message.Message): - """SubtleSignRequest is the request for SubtleSignAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - DIGEST_FIELD_NUMBER: builtins.int - ALGORITHM_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - digest: builtins.bytes - """Digest to sign.""" - algorithm: builtins.str - """Algorithm to use, as in the JWA standard.""" - key_name: builtins.str - """Name (or name/version) of the key.""" - def __init__( - self, - *, - component_name: builtins.str = ..., - digest: builtins.bytes = ..., - algorithm: builtins.str = ..., - key_name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "component_name", b"component_name", "digest", b"digest", "key_name", b"key_name"]) -> None: ... - -global___SubtleSignRequest = SubtleSignRequest - -@typing_extensions.final -class SubtleSignResponse(google.protobuf.message.Message): - """SubtleSignResponse is the response for SubtleSignAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - SIGNATURE_FIELD_NUMBER: builtins.int - signature: builtins.bytes - """The signature that was computed""" - def __init__( - self, - *, - signature: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["signature", b"signature"]) -> None: ... - -global___SubtleSignResponse = SubtleSignResponse - -@typing_extensions.final -class SubtleVerifyRequest(google.protobuf.message.Message): - """SubtleVerifyRequest is the request for SubtleVerifyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - DIGEST_FIELD_NUMBER: builtins.int - ALGORITHM_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - SIGNATURE_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - digest: builtins.bytes - """Digest of the message.""" - algorithm: builtins.str - """Algorithm to use, as in the JWA standard.""" - key_name: builtins.str - """Name (or name/version) of the key.""" - signature: builtins.bytes - """Signature to verify.""" - def __init__( - self, - *, - component_name: builtins.str = ..., - digest: builtins.bytes = ..., - algorithm: builtins.str = ..., - key_name: builtins.str = ..., - signature: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "component_name", b"component_name", "digest", b"digest", "key_name", b"key_name", "signature", b"signature"]) -> None: ... - -global___SubtleVerifyRequest = SubtleVerifyRequest - -@typing_extensions.final -class SubtleVerifyResponse(google.protobuf.message.Message): - """SubtleVerifyResponse is the response for SubtleVerifyAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - VALID_FIELD_NUMBER: builtins.int - valid: builtins.bool - """True if the signature is valid.""" - def __init__( - self, - *, - valid: builtins.bool = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["valid", b"valid"]) -> None: ... - -global___SubtleVerifyResponse = SubtleVerifyResponse - -@typing_extensions.final -class EncryptRequest(google.protobuf.message.Message): - """EncryptRequest is the request for EncryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - OPTIONS_FIELD_NUMBER: builtins.int - PAYLOAD_FIELD_NUMBER: builtins.int - @property - def options(self) -> global___EncryptRequestOptions: - """Request details. Must be present in the first message only.""" - @property - def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: - """Chunk of data of arbitrary size.""" - def __init__( - self, - *, - options: global___EncryptRequestOptions | None = ..., - payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> None: ... - -global___EncryptRequest = EncryptRequest - -@typing_extensions.final -class EncryptRequestOptions(google.protobuf.message.Message): - """EncryptRequestOptions contains options for the first message in the EncryptAlpha1 request.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - KEY_WRAP_ALGORITHM_FIELD_NUMBER: builtins.int - DATA_ENCRYPTION_CIPHER_FIELD_NUMBER: builtins.int - OMIT_DECRYPTION_KEY_NAME_FIELD_NUMBER: builtins.int - DECRYPTION_KEY_NAME_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component. Required.""" - key_name: builtins.str - """Name (or name/version) of the key. Required.""" - key_wrap_algorithm: builtins.str - """Key wrapping algorithm to use. Required. - Supported options include: A256KW (alias: AES), A128CBC, A192CBC, A256CBC, RSA-OAEP-256 (alias: RSA). - """ - data_encryption_cipher: builtins.str - """Cipher used to encrypt data (optional): "aes-gcm" (default) or "chacha20-poly1305" """ - omit_decryption_key_name: builtins.bool - """If true, the encrypted document does not contain a key reference. - In that case, calls to the Decrypt method must provide a key reference (name or name/version). - Defaults to false. - """ - decryption_key_name: builtins.str - """Key reference to embed in the encrypted document (name or name/version). - This is helpful if the reference of the key used to decrypt the document is different from the one used to encrypt it. - If unset, uses the reference of the key used to encrypt the document (this is the default behavior). - This option is ignored if omit_decryption_key_name is true. - """ - def __init__( - self, - *, - component_name: builtins.str = ..., - key_name: builtins.str = ..., - key_wrap_algorithm: builtins.str = ..., - data_encryption_cipher: builtins.str = ..., - omit_decryption_key_name: builtins.bool = ..., - decryption_key_name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["component_name", b"component_name", "data_encryption_cipher", b"data_encryption_cipher", "decryption_key_name", b"decryption_key_name", "key_name", b"key_name", "key_wrap_algorithm", b"key_wrap_algorithm", "omit_decryption_key_name", b"omit_decryption_key_name"]) -> None: ... - -global___EncryptRequestOptions = EncryptRequestOptions - -@typing_extensions.final -class EncryptResponse(google.protobuf.message.Message): - """EncryptResponse is the response for EncryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PAYLOAD_FIELD_NUMBER: builtins.int - @property - def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: - """Chunk of data.""" - def __init__( - self, - *, - payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> None: ... - -global___EncryptResponse = EncryptResponse - -@typing_extensions.final -class DecryptRequest(google.protobuf.message.Message): - """DecryptRequest is the request for DecryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - OPTIONS_FIELD_NUMBER: builtins.int - PAYLOAD_FIELD_NUMBER: builtins.int - @property - def options(self) -> global___DecryptRequestOptions: - """Request details. Must be present in the first message only.""" - @property - def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: - """Chunk of data of arbitrary size.""" - def __init__( - self, - *, - options: global___DecryptRequestOptions | None = ..., - payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> None: ... - -global___DecryptRequest = DecryptRequest - -@typing_extensions.final -class DecryptRequestOptions(google.protobuf.message.Message): - """DecryptRequestOptions contains options for the first message in the DecryptAlpha1 request.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - COMPONENT_NAME_FIELD_NUMBER: builtins.int - KEY_NAME_FIELD_NUMBER: builtins.int - component_name: builtins.str - """Name of the component""" - key_name: builtins.str - """Name (or name/version) of the key to decrypt the message. - Overrides any key reference included in the message if present. - This is required if the message doesn't include a key reference (i.e. was created with omit_decryption_key_name set to true). - """ - def __init__( - self, - *, - component_name: builtins.str = ..., - key_name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["component_name", b"component_name", "key_name", b"key_name"]) -> None: ... - -global___DecryptRequestOptions = DecryptRequestOptions - -@typing_extensions.final -class DecryptResponse(google.protobuf.message.Message): - """DecryptResponse is the response for DecryptAlpha1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - PAYLOAD_FIELD_NUMBER: builtins.int - @property - def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: - """Chunk of data.""" - def __init__( - self, - *, - payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> None: ... - -global___DecryptResponse = DecryptResponse - -@typing_extensions.final -class GetWorkflowRequest(google.protobuf.message.Message): - """GetWorkflowRequest is the request for GetWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance to query.""" - workflow_component: builtins.str - """Name of the workflow component.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... - -global___GetWorkflowRequest = GetWorkflowRequest - -@typing_extensions.final -class GetWorkflowResponse(google.protobuf.message.Message): - """GetWorkflowResponse is the response for GetWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class PropertiesEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_NAME_FIELD_NUMBER: builtins.int - CREATED_AT_FIELD_NUMBER: builtins.int - LAST_UPDATED_AT_FIELD_NUMBER: builtins.int - RUNTIME_STATUS_FIELD_NUMBER: builtins.int - PROPERTIES_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance.""" - workflow_name: builtins.str - """Name of the workflow.""" - @property - def created_at(self) -> google.protobuf.timestamp_pb2.Timestamp: - """The time at which the workflow instance was created.""" - @property - def last_updated_at(self) -> google.protobuf.timestamp_pb2.Timestamp: - """The last time at which the workflow instance had its state changed.""" - runtime_status: builtins.str - """The current status of the workflow instance, for example, "PENDING", "RUNNING", "SUSPENDED", "COMPLETED", "FAILED", and "TERMINATED".""" - @property - def properties(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Additional component-specific properties of the workflow instance.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_name: builtins.str = ..., - created_at: google.protobuf.timestamp_pb2.Timestamp | None = ..., - last_updated_at: google.protobuf.timestamp_pb2.Timestamp | None = ..., - runtime_status: builtins.str = ..., - properties: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["created_at", b"created_at", "last_updated_at", b"last_updated_at"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["created_at", b"created_at", "instance_id", b"instance_id", "last_updated_at", b"last_updated_at", "properties", b"properties", "runtime_status", b"runtime_status", "workflow_name", b"workflow_name"]) -> None: ... - -global___GetWorkflowResponse = GetWorkflowResponse - -@typing_extensions.final -class StartWorkflowRequest(google.protobuf.message.Message): - """StartWorkflowRequest is the request for StartWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class OptionsEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - WORKFLOW_NAME_FIELD_NUMBER: builtins.int - OPTIONS_FIELD_NUMBER: builtins.int - INPUT_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """The ID to assign to the started workflow instance. If empty, a random ID is generated.""" - workflow_component: builtins.str - """Name of the workflow component.""" - workflow_name: builtins.str - """Name of the workflow.""" - @property - def options(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """Additional component-specific options for starting the workflow instance.""" - input: builtins.bytes - """Input data for the workflow instance.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - workflow_name: builtins.str = ..., - options: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - input: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["input", b"input", "instance_id", b"instance_id", "options", b"options", "workflow_component", b"workflow_component", "workflow_name", b"workflow_name"]) -> None: ... - -global___StartWorkflowRequest = StartWorkflowRequest - -@typing_extensions.final -class StartWorkflowResponse(google.protobuf.message.Message): - """StartWorkflowResponse is the response for StartWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the started workflow instance.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id"]) -> None: ... - -global___StartWorkflowResponse = StartWorkflowResponse - -@typing_extensions.final -class TerminateWorkflowRequest(google.protobuf.message.Message): - """TerminateWorkflowRequest is the request for TerminateWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance to terminate.""" - workflow_component: builtins.str - """Name of the workflow component.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... - -global___TerminateWorkflowRequest = TerminateWorkflowRequest - -@typing_extensions.final -class PauseWorkflowRequest(google.protobuf.message.Message): - """PauseWorkflowRequest is the request for PauseWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance to pause.""" - workflow_component: builtins.str - """Name of the workflow component.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... - -global___PauseWorkflowRequest = PauseWorkflowRequest - -@typing_extensions.final -class ResumeWorkflowRequest(google.protobuf.message.Message): - """ResumeWorkflowRequest is the request for ResumeWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance to resume.""" - workflow_component: builtins.str - """Name of the workflow component.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... - -global___ResumeWorkflowRequest = ResumeWorkflowRequest - -@typing_extensions.final -class RaiseEventWorkflowRequest(google.protobuf.message.Message): - """RaiseEventWorkflowRequest is the request for RaiseEventWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - EVENT_NAME_FIELD_NUMBER: builtins.int - EVENT_DATA_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance to raise an event for.""" - workflow_component: builtins.str - """Name of the workflow component.""" - event_name: builtins.str - """Name of the event.""" - event_data: builtins.bytes - """Data associated with the event.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - event_name: builtins.str = ..., - event_data: builtins.bytes = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["event_data", b"event_data", "event_name", b"event_name", "instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... - -global___RaiseEventWorkflowRequest = RaiseEventWorkflowRequest - -@typing_extensions.final -class PurgeWorkflowRequest(google.protobuf.message.Message): - """PurgeWorkflowRequest is the request for PurgeWorkflowBeta1.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - INSTANCE_ID_FIELD_NUMBER: builtins.int - WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int - instance_id: builtins.str - """ID of the workflow instance to purge.""" - workflow_component: builtins.str - """Name of the workflow component.""" - def __init__( - self, - *, - instance_id: builtins.str = ..., - workflow_component: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... - -global___PurgeWorkflowRequest = PurgeWorkflowRequest - -@typing_extensions.final -class ShutdownRequest(google.protobuf.message.Message): - """ShutdownRequest is the request for Shutdown. - Empty - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___ShutdownRequest = ShutdownRequest - -@typing_extensions.final -class Job(google.protobuf.message.Message): - """Job is the definition of a job. At least one of schedule or due_time must be - provided but can also be provided together. - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - SCHEDULE_FIELD_NUMBER: builtins.int - REPEATS_FIELD_NUMBER: builtins.int - DUE_TIME_FIELD_NUMBER: builtins.int - TTL_FIELD_NUMBER: builtins.int - DATA_FIELD_NUMBER: builtins.int - name: builtins.str - """The unique name for the job.""" - schedule: builtins.str - """schedule is an optional schedule at which the job is to be run. - Accepts both systemd timer style cron expressions, as well as human - readable '@' prefixed period strings as defined below. - - Systemd timer style cron accepts 6 fields: - seconds | minutes | hours | day of month | month | day of week - 0-59 | 0-59 | 0-23 | 1-31 | 1-12/jan-dec | 0-6/sun-sat - - "0 30 * * * *" - every hour on the half hour - "0 15 3 * * *" - every day at 03:15 - - Period string expressions: - Entry | Description | Equivalent To - ----- | ----------- | ------------- - @every `` | Run every `` (e.g. '@every 1h30m') | N/A - @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * - @monthly | Run once a month, midnight, first of month | 0 0 0 1 * * - @weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0 - @daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * - @hourly | Run once an hour, beginning of hour | 0 0 * * * * - """ - repeats: builtins.int - """repeats is the optional number of times in which the job should be - triggered. If not set, the job will run indefinitely or until expiration. - """ - due_time: builtins.str - """due_time is the optional time at which the job should be active, or the - "one shot" time if other scheduling type fields are not provided. Accepts - a "point in time" string in the format of RFC3339, Go duration string - (calculated from job creation time), or non-repeating ISO8601. - """ - ttl: builtins.str - """ttl is the optional time to live or expiration of the job. Accepts a - "point in time" string in the format of RFC3339, Go duration string - (calculated from job creation time), or non-repeating ISO8601. - """ - @property - def data(self) -> google.protobuf.any_pb2.Any: - """payload is the serialized job payload that will be sent to the recipient - when the job is triggered. - """ - def __init__( - self, - *, - name: builtins.str = ..., - schedule: builtins.str | None = ..., - repeats: builtins.int | None = ..., - due_time: builtins.str | None = ..., - ttl: builtins.str | None = ..., - data: google.protobuf.any_pb2.Any | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_due_time", b"_due_time", "_repeats", b"_repeats", "_schedule", b"_schedule", "_ttl", b"_ttl", "data", b"data", "due_time", b"due_time", "repeats", b"repeats", "schedule", b"schedule", "ttl", b"ttl"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_due_time", b"_due_time", "_repeats", b"_repeats", "_schedule", b"_schedule", "_ttl", b"_ttl", "data", b"data", "due_time", b"due_time", "name", b"name", "repeats", b"repeats", "schedule", b"schedule", "ttl", b"ttl"]) -> None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_due_time", b"_due_time"]) -> typing_extensions.Literal["due_time"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_repeats", b"_repeats"]) -> typing_extensions.Literal["repeats"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_schedule", b"_schedule"]) -> typing_extensions.Literal["schedule"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_ttl", b"_ttl"]) -> typing_extensions.Literal["ttl"] | None: ... - -global___Job = Job - -@typing_extensions.final -class ScheduleJobRequest(google.protobuf.message.Message): - """ScheduleJobRequest is the message to create/schedule the job.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_FIELD_NUMBER: builtins.int - @property - def job(self) -> global___Job: - """The job details.""" - def __init__( - self, - *, - job: global___Job | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["job", b"job"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["job", b"job"]) -> None: ... - -global___ScheduleJobRequest = ScheduleJobRequest - -@typing_extensions.final -class ScheduleJobResponse(google.protobuf.message.Message): - """ScheduleJobResponse is the message response to create/schedule the job. - Empty - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___ScheduleJobResponse = ScheduleJobResponse - -@typing_extensions.final -class GetJobRequest(google.protobuf.message.Message): - """GetJobRequest is the message to retrieve a job.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - name: builtins.str - """The name of the job.""" - def __init__( - self, - *, - name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ... - -global___GetJobRequest = GetJobRequest - -@typing_extensions.final -class GetJobResponse(google.protobuf.message.Message): - """GetJobResponse is the message's response for a job retrieved.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - JOB_FIELD_NUMBER: builtins.int - @property - def job(self) -> global___Job: - """The job details.""" - def __init__( - self, - *, - job: global___Job | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["job", b"job"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["job", b"job"]) -> None: ... - -global___GetJobResponse = GetJobResponse - -@typing_extensions.final -class DeleteJobRequest(google.protobuf.message.Message): - """DeleteJobRequest is the message to delete the job by name.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - NAME_FIELD_NUMBER: builtins.int - name: builtins.str - """The name of the job.""" - def __init__( - self, - *, - name: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ... - -global___DeleteJobRequest = DeleteJobRequest - -@typing_extensions.final -class DeleteJobResponse(google.protobuf.message.Message): - """DeleteJobResponse is the message response to delete the job by name. - Empty - """ - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - def __init__( - self, - ) -> None: ... - -global___DeleteJobResponse = DeleteJobResponse - -@typing_extensions.final -class ConversationAlpha1Request(google.protobuf.message.Message): - """ConversationAlpha1Request is the request object for Conversation.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class ParametersEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - @property - def value(self) -> google.protobuf.any_pb2.Any: ... - def __init__( - self, - *, - key: builtins.str = ..., - value: google.protobuf.any_pb2.Any | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - @typing_extensions.final - class MetadataEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - value: builtins.str - def __init__( - self, - *, - key: builtins.str = ..., - value: builtins.str = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - NAME_FIELD_NUMBER: builtins.int - CONTEXTID_FIELD_NUMBER: builtins.int - INPUTS_FIELD_NUMBER: builtins.int - PARAMETERS_FIELD_NUMBER: builtins.int - METADATA_FIELD_NUMBER: builtins.int - SCRUBPII_FIELD_NUMBER: builtins.int - TEMPERATURE_FIELD_NUMBER: builtins.int - name: builtins.str - """The name of Coverstaion component""" - contextID: builtins.str - """The ID of an existing chat (like in ChatGPT)""" - @property - def inputs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ConversationInput]: - """Inputs for the conversation, support multiple input in one time.""" - @property - def parameters(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, google.protobuf.any_pb2.Any]: - """Parameters for all custom fields.""" - @property - def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: - """The metadata passing to conversation components.""" - scrubPII: builtins.bool - """Scrub PII data that comes back from the LLM""" - temperature: builtins.float - """Temperature for the LLM to optimize for creativity or predictability""" - def __init__( - self, - *, - name: builtins.str = ..., - contextID: builtins.str | None = ..., - inputs: collections.abc.Iterable[global___ConversationInput] | None = ..., - parameters: collections.abc.Mapping[builtins.str, google.protobuf.any_pb2.Any] | None = ..., - metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., - scrubPII: builtins.bool | None = ..., - temperature: builtins.float | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "_scrubPII", b"_scrubPII", "_temperature", b"_temperature", "contextID", b"contextID", "scrubPII", b"scrubPII", "temperature", b"temperature"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "_scrubPII", b"_scrubPII", "_temperature", b"_temperature", "contextID", b"contextID", "inputs", b"inputs", "metadata", b"metadata", "name", b"name", "parameters", b"parameters", "scrubPII", b"scrubPII", "temperature", b"temperature"]) -> None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_contextID", b"_contextID"]) -> typing_extensions.Literal["contextID"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_scrubPII", b"_scrubPII"]) -> typing_extensions.Literal["scrubPII"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_temperature", b"_temperature"]) -> typing_extensions.Literal["temperature"] | None: ... - -global___ConversationAlpha1Request = ConversationAlpha1Request - -@typing_extensions.final -class ConversationInput(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - MESSAGE_FIELD_NUMBER: builtins.int - ROLE_FIELD_NUMBER: builtins.int - SCRUBPII_FIELD_NUMBER: builtins.int - message: builtins.str - """The message to send to the llm""" - role: builtins.str - """The role to set for the message""" - scrubPII: builtins.bool - """Scrub PII data that goes into the LLM""" - def __init__( - self, - *, - message: builtins.str = ..., - role: builtins.str | None = ..., - scrubPII: builtins.bool | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_role", b"_role", "_scrubPII", b"_scrubPII", "role", b"role", "scrubPII", b"scrubPII"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_role", b"_role", "_scrubPII", b"_scrubPII", "message", b"message", "role", b"role", "scrubPII", b"scrubPII"]) -> None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_role", b"_role"]) -> typing_extensions.Literal["role"] | None: ... - @typing.overload - def WhichOneof(self, oneof_group: typing_extensions.Literal["_scrubPII", b"_scrubPII"]) -> typing_extensions.Literal["scrubPII"] | None: ... - -global___ConversationInput = ConversationInput - -@typing_extensions.final -class ConversationAlpha1Result(google.protobuf.message.Message): - """ConversationAlpha1Result is the result for one input.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - @typing_extensions.final - class ParametersEntry(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - KEY_FIELD_NUMBER: builtins.int - VALUE_FIELD_NUMBER: builtins.int - key: builtins.str - @property - def value(self) -> google.protobuf.any_pb2.Any: ... - def __init__( - self, - *, - key: builtins.str = ..., - value: google.protobuf.any_pb2.Any | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... - - RESULT_FIELD_NUMBER: builtins.int - PARAMETERS_FIELD_NUMBER: builtins.int - result: builtins.str - """Result for the one conversation input.""" - @property - def parameters(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, google.protobuf.any_pb2.Any]: - """Parameters for all custom fields.""" - def __init__( - self, - *, - result: builtins.str = ..., - parameters: collections.abc.Mapping[builtins.str, google.protobuf.any_pb2.Any] | None = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["parameters", b"parameters", "result", b"result"]) -> None: ... - -global___ConversationAlpha1Result = ConversationAlpha1Result - -@typing_extensions.final -class ConversationAlpha1Response(google.protobuf.message.Message): - """ConversationAlpha1Response is the response for Conversation.""" - - DESCRIPTOR: google.protobuf.descriptor.Descriptor - - CONTEXTID_FIELD_NUMBER: builtins.int - OUTPUTS_FIELD_NUMBER: builtins.int - contextID: builtins.str - """The ID of an existing chat (like in ChatGPT)""" - @property - def outputs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ConversationAlpha1Result]: - """An array of results.""" - def __init__( - self, - *, - contextID: builtins.str | None = ..., - outputs: collections.abc.Iterable[global___ConversationAlpha1Result] | None = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "contextID", b"contextID"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "contextID", b"contextID", "outputs", b"outputs"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["_contextID", b"_contextID"]) -> typing_extensions.Literal["contextID"] | None: ... - -global___ConversationAlpha1Response = ConversationAlpha1Response +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file + +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import builtins +import collections.abc +import dapr.proto.common.v1.common_pb2 +import dapr.proto.runtime.v1.appcallback_pb2 +import google.protobuf.any_pb2 +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import google.protobuf.timestamp_pb2 +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _PubsubSubscriptionType: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _PubsubSubscriptionTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_PubsubSubscriptionType.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + UNKNOWN: _PubsubSubscriptionType.ValueType # 0 + """UNKNOWN is the default value for the subscription type.""" + DECLARATIVE: _PubsubSubscriptionType.ValueType # 1 + """Declarative subscription (k8s CRD)""" + PROGRAMMATIC: _PubsubSubscriptionType.ValueType # 2 + """Programmatically created subscription""" + STREAMING: _PubsubSubscriptionType.ValueType # 3 + """Bidirectional Streaming subscription""" + +class PubsubSubscriptionType(_PubsubSubscriptionType, metaclass=_PubsubSubscriptionTypeEnumTypeWrapper): + """PubsubSubscriptionType indicates the type of subscription""" + +UNKNOWN: PubsubSubscriptionType.ValueType # 0 +"""UNKNOWN is the default value for the subscription type.""" +DECLARATIVE: PubsubSubscriptionType.ValueType # 1 +"""Declarative subscription (k8s CRD)""" +PROGRAMMATIC: PubsubSubscriptionType.ValueType # 2 +"""Programmatically created subscription""" +STREAMING: PubsubSubscriptionType.ValueType # 3 +"""Bidirectional Streaming subscription""" +global___PubsubSubscriptionType = PubsubSubscriptionType + +@typing_extensions.final +class InvokeServiceRequest(google.protobuf.message.Message): + """InvokeServiceRequest represents the request message for Service invocation.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ID_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + id: builtins.str + """Required. Callee's app id.""" + @property + def message(self) -> dapr.proto.common.v1.common_pb2.InvokeRequest: + """Required. message which will be delivered to callee.""" + def __init__( + self, + *, + id: builtins.str = ..., + message: dapr.proto.common.v1.common_pb2.InvokeRequest | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["message", b"message"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "message", b"message"]) -> None: ... + +global___InvokeServiceRequest = InvokeServiceRequest + +@typing_extensions.final +class GetStateRequest(google.protobuf.message.Message): + """GetStateRequest is the message to get key-value states from specific state store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + KEY_FIELD_NUMBER: builtins.int + CONSISTENCY_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store.""" + key: builtins.str + """The key of the desired state""" + consistency: dapr.proto.common.v1.common_pb2.StateOptions.StateConsistency.ValueType + """The read consistency of the state store.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to state store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + key: builtins.str = ..., + consistency: dapr.proto.common.v1.common_pb2.StateOptions.StateConsistency.ValueType = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["consistency", b"consistency", "key", b"key", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... + +global___GetStateRequest = GetStateRequest + +@typing_extensions.final +class GetBulkStateRequest(google.protobuf.message.Message): + """GetBulkStateRequest is the message to get a list of key-value states from specific state store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + KEYS_FIELD_NUMBER: builtins.int + PARALLELISM_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store.""" + @property + def keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """The keys to get.""" + parallelism: builtins.int + """The number of parallel operations executed on the state store for a get operation.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to state store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + keys: collections.abc.Iterable[builtins.str] | None = ..., + parallelism: builtins.int = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "metadata", b"metadata", "parallelism", b"parallelism", "store_name", b"store_name"]) -> None: ... + +global___GetBulkStateRequest = GetBulkStateRequest + +@typing_extensions.final +class GetBulkStateResponse(google.protobuf.message.Message): + """GetBulkStateResponse is the response conveying the list of state values.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ITEMS_FIELD_NUMBER: builtins.int + @property + def items(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BulkStateItem]: + """The list of items containing the keys to get values for.""" + def __init__( + self, + *, + items: collections.abc.Iterable[global___BulkStateItem] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["items", b"items"]) -> None: ... + +global___GetBulkStateResponse = GetBulkStateResponse + +@typing_extensions.final +class BulkStateItem(google.protobuf.message.Message): + """BulkStateItem is the response item for a bulk get operation. + Return values include the item key, data and etag. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + KEY_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + ETAG_FIELD_NUMBER: builtins.int + ERROR_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + key: builtins.str + """state item key""" + data: builtins.bytes + """The byte array data""" + etag: builtins.str + """The entity tag which represents the specific version of data. + ETag format is defined by the corresponding data store. + """ + error: builtins.str + """The error that was returned from the state store in case of a failed get operation.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to app.""" + def __init__( + self, + *, + key: builtins.str = ..., + data: builtins.bytes = ..., + etag: builtins.str = ..., + error: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "error", b"error", "etag", b"etag", "key", b"key", "metadata", b"metadata"]) -> None: ... + +global___BulkStateItem = BulkStateItem + +@typing_extensions.final +class GetStateResponse(google.protobuf.message.Message): + """GetStateResponse is the response conveying the state value and etag.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + DATA_FIELD_NUMBER: builtins.int + ETAG_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + data: builtins.bytes + """The byte array data""" + etag: builtins.str + """The entity tag which represents the specific version of data. + ETag format is defined by the corresponding data store. + """ + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to app.""" + def __init__( + self, + *, + data: builtins.bytes = ..., + etag: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "etag", b"etag", "metadata", b"metadata"]) -> None: ... + +global___GetStateResponse = GetStateResponse + +@typing_extensions.final +class DeleteStateRequest(google.protobuf.message.Message): + """DeleteStateRequest is the message to delete key-value states in the specific state store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + KEY_FIELD_NUMBER: builtins.int + ETAG_FIELD_NUMBER: builtins.int + OPTIONS_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store.""" + key: builtins.str + """The key of the desired state""" + @property + def etag(self) -> dapr.proto.common.v1.common_pb2.Etag: + """The entity tag which represents the specific version of data. + The exact ETag format is defined by the corresponding data store. + """ + @property + def options(self) -> dapr.proto.common.v1.common_pb2.StateOptions: + """State operation options which includes concurrency/ + consistency/retry_policy. + """ + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to state store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + key: builtins.str = ..., + etag: dapr.proto.common.v1.common_pb2.Etag | None = ..., + options: dapr.proto.common.v1.common_pb2.StateOptions | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["etag", b"etag", "options", b"options"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["etag", b"etag", "key", b"key", "metadata", b"metadata", "options", b"options", "store_name", b"store_name"]) -> None: ... + +global___DeleteStateRequest = DeleteStateRequest + +@typing_extensions.final +class DeleteBulkStateRequest(google.protobuf.message.Message): + """DeleteBulkStateRequest is the message to delete a list of key-value states from specific state store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STORE_NAME_FIELD_NUMBER: builtins.int + STATES_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store.""" + @property + def states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[dapr.proto.common.v1.common_pb2.StateItem]: + """The array of the state key values.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + states: collections.abc.Iterable[dapr.proto.common.v1.common_pb2.StateItem] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["states", b"states", "store_name", b"store_name"]) -> None: ... + +global___DeleteBulkStateRequest = DeleteBulkStateRequest + +@typing_extensions.final +class SaveStateRequest(google.protobuf.message.Message): + """SaveStateRequest is the message to save multiple states into state store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STORE_NAME_FIELD_NUMBER: builtins.int + STATES_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store.""" + @property + def states(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[dapr.proto.common.v1.common_pb2.StateItem]: + """The array of the state key values.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + states: collections.abc.Iterable[dapr.proto.common.v1.common_pb2.StateItem] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["states", b"states", "store_name", b"store_name"]) -> None: ... + +global___SaveStateRequest = SaveStateRequest + +@typing_extensions.final +class QueryStateRequest(google.protobuf.message.Message): + """QueryStateRequest is the message to query state store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + QUERY_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of state store.""" + query: builtins.str + """The query in JSON format.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to state store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + query: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "query", b"query", "store_name", b"store_name"]) -> None: ... + +global___QueryStateRequest = QueryStateRequest + +@typing_extensions.final +class QueryStateItem(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + ETAG_FIELD_NUMBER: builtins.int + ERROR_FIELD_NUMBER: builtins.int + key: builtins.str + """The object key.""" + data: builtins.bytes + """The object value.""" + etag: builtins.str + """The entity tag which represents the specific version of data. + ETag format is defined by the corresponding data store. + """ + error: builtins.str + """The error message indicating an error in processing of the query result.""" + def __init__( + self, + *, + key: builtins.str = ..., + data: builtins.bytes = ..., + etag: builtins.str = ..., + error: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "error", b"error", "etag", b"etag", "key", b"key"]) -> None: ... + +global___QueryStateItem = QueryStateItem + +@typing_extensions.final +class QueryStateResponse(google.protobuf.message.Message): + """QueryStateResponse is the response conveying the query results.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + RESULTS_FIELD_NUMBER: builtins.int + TOKEN_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + @property + def results(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___QueryStateItem]: + """An array of query results.""" + token: builtins.str + """Pagination token.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to app.""" + def __init__( + self, + *, + results: collections.abc.Iterable[global___QueryStateItem] | None = ..., + token: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "results", b"results", "token", b"token"]) -> None: ... + +global___QueryStateResponse = QueryStateResponse + +@typing_extensions.final +class PublishEventRequest(google.protobuf.message.Message): + """PublishEventRequest is the message to publish event data to pubsub topic""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + PUBSUB_NAME_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + DATA_CONTENT_TYPE_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + pubsub_name: builtins.str + """The name of the pubsub component""" + topic: builtins.str + """The pubsub topic""" + data: builtins.bytes + """The data which will be published to topic.""" + data_content_type: builtins.str + """The content type for the data (optional).""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata passing to pub components + + metadata property: + - key : the key of the message. + """ + def __init__( + self, + *, + pubsub_name: builtins.str = ..., + topic: builtins.str = ..., + data: builtins.bytes = ..., + data_content_type: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "data_content_type", b"data_content_type", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "topic", b"topic"]) -> None: ... + +global___PublishEventRequest = PublishEventRequest + +@typing_extensions.final +class BulkPublishRequest(google.protobuf.message.Message): + """BulkPublishRequest is the message to bulk publish events to pubsub topic""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + PUBSUB_NAME_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + ENTRIES_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + pubsub_name: builtins.str + """The name of the pubsub component""" + topic: builtins.str + """The pubsub topic""" + @property + def entries(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BulkPublishRequestEntry]: + """The entries which contain the individual events and associated details to be published""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The request level metadata passing to to the pubsub components""" + def __init__( + self, + *, + pubsub_name: builtins.str = ..., + topic: builtins.str = ..., + entries: collections.abc.Iterable[global___BulkPublishRequestEntry] | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["entries", b"entries", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "topic", b"topic"]) -> None: ... + +global___BulkPublishRequest = BulkPublishRequest + +@typing_extensions.final +class BulkPublishRequestEntry(google.protobuf.message.Message): + """BulkPublishRequestEntry is the message containing the event to be bulk published""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ENTRY_ID_FIELD_NUMBER: builtins.int + EVENT_FIELD_NUMBER: builtins.int + CONTENT_TYPE_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + entry_id: builtins.str + """The request scoped unique ID referring to this message. Used to map status in response""" + event: builtins.bytes + """The event which will be pulished to the topic""" + content_type: builtins.str + """The content type for the event""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The event level metadata passing to the pubsub component""" + def __init__( + self, + *, + entry_id: builtins.str = ..., + event: builtins.bytes = ..., + content_type: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["content_type", b"content_type", "entry_id", b"entry_id", "event", b"event", "metadata", b"metadata"]) -> None: ... + +global___BulkPublishRequestEntry = BulkPublishRequestEntry + +@typing_extensions.final +class BulkPublishResponse(google.protobuf.message.Message): + """BulkPublishResponse is the message returned from a BulkPublishEvent call""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + FAILEDENTRIES_FIELD_NUMBER: builtins.int + @property + def failedEntries(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___BulkPublishResponseFailedEntry]: + """The entries for different events that failed publish in the BulkPublishEvent call""" + def __init__( + self, + *, + failedEntries: collections.abc.Iterable[global___BulkPublishResponseFailedEntry] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["failedEntries", b"failedEntries"]) -> None: ... + +global___BulkPublishResponse = BulkPublishResponse + +@typing_extensions.final +class BulkPublishResponseFailedEntry(google.protobuf.message.Message): + """BulkPublishResponseFailedEntry is the message containing the entryID and error of a failed event in BulkPublishEvent call""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ENTRY_ID_FIELD_NUMBER: builtins.int + ERROR_FIELD_NUMBER: builtins.int + entry_id: builtins.str + """The response scoped unique ID referring to this message""" + error: builtins.str + """The error message if any on failure""" + def __init__( + self, + *, + entry_id: builtins.str = ..., + error: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["entry_id", b"entry_id", "error", b"error"]) -> None: ... + +global___BulkPublishResponseFailedEntry = BulkPublishResponseFailedEntry + +@typing_extensions.final +class SubscribeTopicEventsRequestAlpha1(google.protobuf.message.Message): + """SubscribeTopicEventsRequestAlpha1 is a message containing the details for + subscribing to a topic via streaming. + The first message must always be the initial request. All subsequent + messages must be event processed responses. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INITIAL_REQUEST_FIELD_NUMBER: builtins.int + EVENT_PROCESSED_FIELD_NUMBER: builtins.int + @property + def initial_request(self) -> global___SubscribeTopicEventsRequestInitialAlpha1: ... + @property + def event_processed(self) -> global___SubscribeTopicEventsRequestProcessedAlpha1: ... + def __init__( + self, + *, + initial_request: global___SubscribeTopicEventsRequestInitialAlpha1 | None = ..., + event_processed: global___SubscribeTopicEventsRequestProcessedAlpha1 | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["event_processed", b"event_processed", "initial_request", b"initial_request", "subscribe_topic_events_request_type", b"subscribe_topic_events_request_type"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["event_processed", b"event_processed", "initial_request", b"initial_request", "subscribe_topic_events_request_type", b"subscribe_topic_events_request_type"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["subscribe_topic_events_request_type", b"subscribe_topic_events_request_type"]) -> typing_extensions.Literal["initial_request", "event_processed"] | None: ... + +global___SubscribeTopicEventsRequestAlpha1 = SubscribeTopicEventsRequestAlpha1 + +@typing_extensions.final +class SubscribeTopicEventsRequestInitialAlpha1(google.protobuf.message.Message): + """SubscribeTopicEventsRequestInitialAlpha1 is the initial message containing + the details for subscribing to a topic via streaming. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + PUBSUB_NAME_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + DEAD_LETTER_TOPIC_FIELD_NUMBER: builtins.int + pubsub_name: builtins.str + """The name of the pubsub component""" + topic: builtins.str + """The pubsub topic""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata passing to pub components + + metadata property: + - key : the key of the message. + """ + dead_letter_topic: builtins.str + """dead_letter_topic is the topic to which messages that fail to be processed + are sent. + """ + def __init__( + self, + *, + pubsub_name: builtins.str = ..., + topic: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + dead_letter_topic: builtins.str | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_dead_letter_topic", b"_dead_letter_topic", "dead_letter_topic", b"dead_letter_topic"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_dead_letter_topic", b"_dead_letter_topic", "dead_letter_topic", b"dead_letter_topic", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "topic", b"topic"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["_dead_letter_topic", b"_dead_letter_topic"]) -> typing_extensions.Literal["dead_letter_topic"] | None: ... + +global___SubscribeTopicEventsRequestInitialAlpha1 = SubscribeTopicEventsRequestInitialAlpha1 + +@typing_extensions.final +class SubscribeTopicEventsRequestProcessedAlpha1(google.protobuf.message.Message): + """SubscribeTopicEventsRequestProcessedAlpha1 is the message containing the + subscription to a topic. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ID_FIELD_NUMBER: builtins.int + STATUS_FIELD_NUMBER: builtins.int + id: builtins.str + """id is the unique identifier for the subscription request.""" + @property + def status(self) -> dapr.proto.runtime.v1.appcallback_pb2.TopicEventResponse: + """status is the result of the subscription request.""" + def __init__( + self, + *, + id: builtins.str = ..., + status: dapr.proto.runtime.v1.appcallback_pb2.TopicEventResponse | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["status", b"status"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "status", b"status"]) -> None: ... + +global___SubscribeTopicEventsRequestProcessedAlpha1 = SubscribeTopicEventsRequestProcessedAlpha1 + +@typing_extensions.final +class SubscribeTopicEventsResponseAlpha1(google.protobuf.message.Message): + """SubscribeTopicEventsResponseAlpha1 is a message returned from daprd + when subscribing to a topic via streaming. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INITIAL_RESPONSE_FIELD_NUMBER: builtins.int + EVENT_MESSAGE_FIELD_NUMBER: builtins.int + @property + def initial_response(self) -> global___SubscribeTopicEventsResponseInitialAlpha1: ... + @property + def event_message(self) -> dapr.proto.runtime.v1.appcallback_pb2.TopicEventRequest: ... + def __init__( + self, + *, + initial_response: global___SubscribeTopicEventsResponseInitialAlpha1 | None = ..., + event_message: dapr.proto.runtime.v1.appcallback_pb2.TopicEventRequest | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["event_message", b"event_message", "initial_response", b"initial_response", "subscribe_topic_events_response_type", b"subscribe_topic_events_response_type"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["event_message", b"event_message", "initial_response", b"initial_response", "subscribe_topic_events_response_type", b"subscribe_topic_events_response_type"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["subscribe_topic_events_response_type", b"subscribe_topic_events_response_type"]) -> typing_extensions.Literal["initial_response", "event_message"] | None: ... + +global___SubscribeTopicEventsResponseAlpha1 = SubscribeTopicEventsResponseAlpha1 + +@typing_extensions.final +class SubscribeTopicEventsResponseInitialAlpha1(google.protobuf.message.Message): + """SubscribeTopicEventsResponseInitialAlpha1 is the initial response from daprd + when subscribing to a topic. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___SubscribeTopicEventsResponseInitialAlpha1 = SubscribeTopicEventsResponseInitialAlpha1 + +@typing_extensions.final +class InvokeBindingRequest(google.protobuf.message.Message): + """InvokeBindingRequest is the message to send data to output bindings""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + NAME_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + OPERATION_FIELD_NUMBER: builtins.int + name: builtins.str + """The name of the output binding to invoke.""" + data: builtins.bytes + """The data which will be sent to output binding.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata passing to output binding components + + Common metadata property: + - ttlInSeconds : the time to live in seconds for the message. + + If set in the binding definition will cause all messages to + have a default time to live. The message ttl overrides any value + in the binding definition. + """ + operation: builtins.str + """The name of the operation type for the binding to invoke""" + def __init__( + self, + *, + name: builtins.str = ..., + data: builtins.bytes = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + operation: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata", "name", b"name", "operation", b"operation"]) -> None: ... + +global___InvokeBindingRequest = InvokeBindingRequest + +@typing_extensions.final +class InvokeBindingResponse(google.protobuf.message.Message): + """InvokeBindingResponse is the message returned from an output binding invocation""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + DATA_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + data: builtins.bytes + """The data which will be sent to output binding.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata returned from an external system""" + def __init__( + self, + *, + data: builtins.bytes = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata"]) -> None: ... + +global___InvokeBindingResponse = InvokeBindingResponse + +@typing_extensions.final +class GetSecretRequest(google.protobuf.message.Message): + """GetSecretRequest is the message to get secret from secret store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + KEY_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of secret store.""" + key: builtins.str + """The name of secret key.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to secret store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + key: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... + +global___GetSecretRequest = GetSecretRequest + +@typing_extensions.final +class GetSecretResponse(google.protobuf.message.Message): + """GetSecretResponse is the response message to convey the requested secret.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class DataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + DATA_FIELD_NUMBER: builtins.int + @property + def data(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """data is the secret value. Some secret store, such as kubernetes secret + store, can save multiple secrets for single secret key. + """ + def __init__( + self, + *, + data: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data"]) -> None: ... + +global___GetSecretResponse = GetSecretResponse + +@typing_extensions.final +class GetBulkSecretRequest(google.protobuf.message.Message): + """GetBulkSecretRequest is the message to get the secrets from secret store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of secret store.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to secret store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "store_name", b"store_name"]) -> None: ... + +global___GetBulkSecretRequest = GetBulkSecretRequest + +@typing_extensions.final +class SecretResponse(google.protobuf.message.Message): + """SecretResponse is a map of decrypted string/string values""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class SecretsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + SECRETS_FIELD_NUMBER: builtins.int + @property + def secrets(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... + def __init__( + self, + *, + secrets: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["secrets", b"secrets"]) -> None: ... + +global___SecretResponse = SecretResponse + +@typing_extensions.final +class GetBulkSecretResponse(google.protobuf.message.Message): + """GetBulkSecretResponse is the response message to convey the requested secrets.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class DataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___SecretResponse: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___SecretResponse | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + DATA_FIELD_NUMBER: builtins.int + @property + def data(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, global___SecretResponse]: + """data hold the secret values. Some secret store, such as kubernetes secret + store, can save multiple secrets for single secret key. + """ + def __init__( + self, + *, + data: collections.abc.Mapping[builtins.str, global___SecretResponse] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data"]) -> None: ... + +global___GetBulkSecretResponse = GetBulkSecretResponse + +@typing_extensions.final +class TransactionalStateOperation(google.protobuf.message.Message): + """TransactionalStateOperation is the message to execute a specified operation with a key-value pair.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + OPERATIONTYPE_FIELD_NUMBER: builtins.int + REQUEST_FIELD_NUMBER: builtins.int + operationType: builtins.str + """The type of operation to be executed""" + @property + def request(self) -> dapr.proto.common.v1.common_pb2.StateItem: + """State values to be operated on""" + def __init__( + self, + *, + operationType: builtins.str = ..., + request: dapr.proto.common.v1.common_pb2.StateItem | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["request", b"request"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["operationType", b"operationType", "request", b"request"]) -> None: ... + +global___TransactionalStateOperation = TransactionalStateOperation + +@typing_extensions.final +class ExecuteStateTransactionRequest(google.protobuf.message.Message): + """ExecuteStateTransactionRequest is the message to execute multiple operations on a specified store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORENAME_FIELD_NUMBER: builtins.int + OPERATIONS_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + storeName: builtins.str + """Required. name of state store.""" + @property + def operations(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TransactionalStateOperation]: + """Required. transactional operation list.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata used for transactional operations.""" + def __init__( + self, + *, + storeName: builtins.str = ..., + operations: collections.abc.Iterable[global___TransactionalStateOperation] | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["metadata", b"metadata", "operations", b"operations", "storeName", b"storeName"]) -> None: ... + +global___ExecuteStateTransactionRequest = ExecuteStateTransactionRequest + +@typing_extensions.final +class RegisterActorTimerRequest(google.protobuf.message.Message): + """RegisterActorTimerRequest is the message to register a timer for an actor of a given type and id.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + DUE_TIME_FIELD_NUMBER: builtins.int + PERIOD_FIELD_NUMBER: builtins.int + CALLBACK_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + TTL_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + name: builtins.str + due_time: builtins.str + period: builtins.str + callback: builtins.str + data: builtins.bytes + ttl: builtins.str + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + name: builtins.str = ..., + due_time: builtins.str = ..., + period: builtins.str = ..., + callback: builtins.str = ..., + data: builtins.bytes = ..., + ttl: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "callback", b"callback", "data", b"data", "due_time", b"due_time", "name", b"name", "period", b"period", "ttl", b"ttl"]) -> None: ... + +global___RegisterActorTimerRequest = RegisterActorTimerRequest + +@typing_extensions.final +class UnregisterActorTimerRequest(google.protobuf.message.Message): + """UnregisterActorTimerRequest is the message to unregister an actor timer""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + name: builtins.str + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "name", b"name"]) -> None: ... + +global___UnregisterActorTimerRequest = UnregisterActorTimerRequest + +@typing_extensions.final +class RegisterActorReminderRequest(google.protobuf.message.Message): + """RegisterActorReminderRequest is the message to register a reminder for an actor of a given type and id.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + DUE_TIME_FIELD_NUMBER: builtins.int + PERIOD_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + TTL_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + name: builtins.str + due_time: builtins.str + period: builtins.str + data: builtins.bytes + ttl: builtins.str + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + name: builtins.str = ..., + due_time: builtins.str = ..., + period: builtins.str = ..., + data: builtins.bytes = ..., + ttl: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "data", b"data", "due_time", b"due_time", "name", b"name", "period", b"period", "ttl", b"ttl"]) -> None: ... + +global___RegisterActorReminderRequest = RegisterActorReminderRequest + +@typing_extensions.final +class UnregisterActorReminderRequest(google.protobuf.message.Message): + """UnregisterActorReminderRequest is the message to unregister an actor reminder.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + name: builtins.str + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "name", b"name"]) -> None: ... + +global___UnregisterActorReminderRequest = UnregisterActorReminderRequest + +@typing_extensions.final +class GetActorStateRequest(google.protobuf.message.Message): + """GetActorStateRequest is the message to get key-value states from specific actor.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + KEY_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + key: builtins.str + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + key: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "key", b"key"]) -> None: ... + +global___GetActorStateRequest = GetActorStateRequest + +@typing_extensions.final +class GetActorStateResponse(google.protobuf.message.Message): + """GetActorStateResponse is the response conveying the actor's state value.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + DATA_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + data: builtins.bytes + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to app.""" + def __init__( + self, + *, + data: builtins.bytes = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data", "metadata", b"metadata"]) -> None: ... + +global___GetActorStateResponse = GetActorStateResponse + +@typing_extensions.final +class ExecuteActorStateTransactionRequest(google.protobuf.message.Message): + """ExecuteActorStateTransactionRequest is the message to execute multiple operations on a specified actor.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + OPERATIONS_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + @property + def operations(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___TransactionalActorStateOperation]: ... + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + operations: collections.abc.Iterable[global___TransactionalActorStateOperation] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "operations", b"operations"]) -> None: ... + +global___ExecuteActorStateTransactionRequest = ExecuteActorStateTransactionRequest + +@typing_extensions.final +class TransactionalActorStateOperation(google.protobuf.message.Message): + """TransactionalActorStateOperation is the message to execute a specified operation with a key-value pair.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + OPERATIONTYPE_FIELD_NUMBER: builtins.int + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + operationType: builtins.str + key: builtins.str + @property + def value(self) -> google.protobuf.any_pb2.Any: ... + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata used for transactional operations. + + Common metadata property: + - ttlInSeconds : the time to live in seconds for the stored value. + """ + def __init__( + self, + *, + operationType: builtins.str = ..., + key: builtins.str = ..., + value: google.protobuf.any_pb2.Any | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "metadata", b"metadata", "operationType", b"operationType", "value", b"value"]) -> None: ... + +global___TransactionalActorStateOperation = TransactionalActorStateOperation + +@typing_extensions.final +class InvokeActorRequest(google.protobuf.message.Message): + """InvokeActorRequest is the message to call an actor.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ACTOR_TYPE_FIELD_NUMBER: builtins.int + ACTOR_ID_FIELD_NUMBER: builtins.int + METHOD_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + actor_type: builtins.str + actor_id: builtins.str + method: builtins.str + data: builtins.bytes + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... + def __init__( + self, + *, + actor_type: builtins.str = ..., + actor_id: builtins.str = ..., + method: builtins.str = ..., + data: builtins.bytes = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["actor_id", b"actor_id", "actor_type", b"actor_type", "data", b"data", "metadata", b"metadata", "method", b"method"]) -> None: ... + +global___InvokeActorRequest = InvokeActorRequest + +@typing_extensions.final +class InvokeActorResponse(google.protobuf.message.Message): + """InvokeActorResponse is the method that returns an actor invocation response.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DATA_FIELD_NUMBER: builtins.int + data: builtins.bytes + def __init__( + self, + *, + data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["data", b"data"]) -> None: ... + +global___InvokeActorResponse = InvokeActorResponse + +@typing_extensions.final +class GetMetadataRequest(google.protobuf.message.Message): + """GetMetadataRequest is the message for the GetMetadata request. + Empty + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___GetMetadataRequest = GetMetadataRequest + +@typing_extensions.final +class GetMetadataResponse(google.protobuf.message.Message): + """GetMetadataResponse is a message that is returned on GetMetadata rpc call.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ExtendedMetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ID_FIELD_NUMBER: builtins.int + ACTIVE_ACTORS_COUNT_FIELD_NUMBER: builtins.int + REGISTERED_COMPONENTS_FIELD_NUMBER: builtins.int + EXTENDED_METADATA_FIELD_NUMBER: builtins.int + SUBSCRIPTIONS_FIELD_NUMBER: builtins.int + HTTP_ENDPOINTS_FIELD_NUMBER: builtins.int + APP_CONNECTION_PROPERTIES_FIELD_NUMBER: builtins.int + RUNTIME_VERSION_FIELD_NUMBER: builtins.int + ENABLED_FEATURES_FIELD_NUMBER: builtins.int + ACTOR_RUNTIME_FIELD_NUMBER: builtins.int + id: builtins.str + @property + def active_actors_count(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ActiveActorsCount]: + """Deprecated alias for actor_runtime.active_actors.""" + @property + def registered_components(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___RegisteredComponents]: ... + @property + def extended_metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... + @property + def subscriptions(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PubsubSubscription]: ... + @property + def http_endpoints(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___MetadataHTTPEndpoint]: ... + @property + def app_connection_properties(self) -> global___AppConnectionProperties: ... + runtime_version: builtins.str + @property + def enabled_features(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + @property + def actor_runtime(self) -> global___ActorRuntime: + """TODO: Cassie: probably add scheduler runtime status""" + def __init__( + self, + *, + id: builtins.str = ..., + active_actors_count: collections.abc.Iterable[global___ActiveActorsCount] | None = ..., + registered_components: collections.abc.Iterable[global___RegisteredComponents] | None = ..., + extended_metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + subscriptions: collections.abc.Iterable[global___PubsubSubscription] | None = ..., + http_endpoints: collections.abc.Iterable[global___MetadataHTTPEndpoint] | None = ..., + app_connection_properties: global___AppConnectionProperties | None = ..., + runtime_version: builtins.str = ..., + enabled_features: collections.abc.Iterable[builtins.str] | None = ..., + actor_runtime: global___ActorRuntime | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["actor_runtime", b"actor_runtime", "app_connection_properties", b"app_connection_properties"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["active_actors_count", b"active_actors_count", "actor_runtime", b"actor_runtime", "app_connection_properties", b"app_connection_properties", "enabled_features", b"enabled_features", "extended_metadata", b"extended_metadata", "http_endpoints", b"http_endpoints", "id", b"id", "registered_components", b"registered_components", "runtime_version", b"runtime_version", "subscriptions", b"subscriptions"]) -> None: ... + +global___GetMetadataResponse = GetMetadataResponse + +@typing_extensions.final +class ActorRuntime(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _ActorRuntimeStatus: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _ActorRuntimeStatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ActorRuntime._ActorRuntimeStatus.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + INITIALIZING: ActorRuntime._ActorRuntimeStatus.ValueType # 0 + """Indicates that the actor runtime is still being initialized.""" + DISABLED: ActorRuntime._ActorRuntimeStatus.ValueType # 1 + """Indicates that the actor runtime is disabled. + This normally happens when Dapr is started without "placement-host-address" + """ + RUNNING: ActorRuntime._ActorRuntimeStatus.ValueType # 2 + """Indicates the actor runtime is running, either as an actor host or client.""" + + class ActorRuntimeStatus(_ActorRuntimeStatus, metaclass=_ActorRuntimeStatusEnumTypeWrapper): ... + INITIALIZING: ActorRuntime.ActorRuntimeStatus.ValueType # 0 + """Indicates that the actor runtime is still being initialized.""" + DISABLED: ActorRuntime.ActorRuntimeStatus.ValueType # 1 + """Indicates that the actor runtime is disabled. + This normally happens when Dapr is started without "placement-host-address" + """ + RUNNING: ActorRuntime.ActorRuntimeStatus.ValueType # 2 + """Indicates the actor runtime is running, either as an actor host or client.""" + + RUNTIME_STATUS_FIELD_NUMBER: builtins.int + ACTIVE_ACTORS_FIELD_NUMBER: builtins.int + HOST_READY_FIELD_NUMBER: builtins.int + PLACEMENT_FIELD_NUMBER: builtins.int + runtime_status: global___ActorRuntime.ActorRuntimeStatus.ValueType + """Contains an enum indicating whether the actor runtime has been initialized.""" + @property + def active_actors(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ActiveActorsCount]: + """Count of active actors per type.""" + host_ready: builtins.bool + """Indicates whether the actor runtime is ready to host actors.""" + placement: builtins.str + """Custom message from the placement provider.""" + def __init__( + self, + *, + runtime_status: global___ActorRuntime.ActorRuntimeStatus.ValueType = ..., + active_actors: collections.abc.Iterable[global___ActiveActorsCount] | None = ..., + host_ready: builtins.bool = ..., + placement: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["active_actors", b"active_actors", "host_ready", b"host_ready", "placement", b"placement", "runtime_status", b"runtime_status"]) -> None: ... + +global___ActorRuntime = ActorRuntime + +@typing_extensions.final +class ActiveActorsCount(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + TYPE_FIELD_NUMBER: builtins.int + COUNT_FIELD_NUMBER: builtins.int + type: builtins.str + count: builtins.int + def __init__( + self, + *, + type: builtins.str = ..., + count: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["count", b"count", "type", b"type"]) -> None: ... + +global___ActiveActorsCount = ActiveActorsCount + +@typing_extensions.final +class RegisteredComponents(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + VERSION_FIELD_NUMBER: builtins.int + CAPABILITIES_FIELD_NUMBER: builtins.int + name: builtins.str + type: builtins.str + version: builtins.str + @property + def capabilities(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: ... + def __init__( + self, + *, + name: builtins.str = ..., + type: builtins.str = ..., + version: builtins.str = ..., + capabilities: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["capabilities", b"capabilities", "name", b"name", "type", b"type", "version", b"version"]) -> None: ... + +global___RegisteredComponents = RegisteredComponents + +@typing_extensions.final +class MetadataHTTPEndpoint(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + name: builtins.str + def __init__( + self, + *, + name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ... + +global___MetadataHTTPEndpoint = MetadataHTTPEndpoint + +@typing_extensions.final +class AppConnectionProperties(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PORT_FIELD_NUMBER: builtins.int + PROTOCOL_FIELD_NUMBER: builtins.int + CHANNEL_ADDRESS_FIELD_NUMBER: builtins.int + MAX_CONCURRENCY_FIELD_NUMBER: builtins.int + HEALTH_FIELD_NUMBER: builtins.int + port: builtins.int + protocol: builtins.str + channel_address: builtins.str + max_concurrency: builtins.int + @property + def health(self) -> global___AppConnectionHealthProperties: ... + def __init__( + self, + *, + port: builtins.int = ..., + protocol: builtins.str = ..., + channel_address: builtins.str = ..., + max_concurrency: builtins.int = ..., + health: global___AppConnectionHealthProperties | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["health", b"health"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["channel_address", b"channel_address", "health", b"health", "max_concurrency", b"max_concurrency", "port", b"port", "protocol", b"protocol"]) -> None: ... + +global___AppConnectionProperties = AppConnectionProperties + +@typing_extensions.final +class AppConnectionHealthProperties(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + HEALTH_CHECK_PATH_FIELD_NUMBER: builtins.int + HEALTH_PROBE_INTERVAL_FIELD_NUMBER: builtins.int + HEALTH_PROBE_TIMEOUT_FIELD_NUMBER: builtins.int + HEALTH_THRESHOLD_FIELD_NUMBER: builtins.int + health_check_path: builtins.str + health_probe_interval: builtins.str + health_probe_timeout: builtins.str + health_threshold: builtins.int + def __init__( + self, + *, + health_check_path: builtins.str = ..., + health_probe_interval: builtins.str = ..., + health_probe_timeout: builtins.str = ..., + health_threshold: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["health_check_path", b"health_check_path", "health_probe_interval", b"health_probe_interval", "health_probe_timeout", b"health_probe_timeout", "health_threshold", b"health_threshold"]) -> None: ... + +global___AppConnectionHealthProperties = AppConnectionHealthProperties + +@typing_extensions.final +class PubsubSubscription(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + PUBSUB_NAME_FIELD_NUMBER: builtins.int + TOPIC_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + RULES_FIELD_NUMBER: builtins.int + DEAD_LETTER_TOPIC_FIELD_NUMBER: builtins.int + TYPE_FIELD_NUMBER: builtins.int + pubsub_name: builtins.str + topic: builtins.str + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: ... + @property + def rules(self) -> global___PubsubSubscriptionRules: ... + dead_letter_topic: builtins.str + type: global___PubsubSubscriptionType.ValueType + def __init__( + self, + *, + pubsub_name: builtins.str = ..., + topic: builtins.str = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + rules: global___PubsubSubscriptionRules | None = ..., + dead_letter_topic: builtins.str = ..., + type: global___PubsubSubscriptionType.ValueType = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["rules", b"rules"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["dead_letter_topic", b"dead_letter_topic", "metadata", b"metadata", "pubsub_name", b"pubsub_name", "rules", b"rules", "topic", b"topic", "type", b"type"]) -> None: ... + +global___PubsubSubscription = PubsubSubscription + +@typing_extensions.final +class PubsubSubscriptionRules(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RULES_FIELD_NUMBER: builtins.int + @property + def rules(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___PubsubSubscriptionRule]: ... + def __init__( + self, + *, + rules: collections.abc.Iterable[global___PubsubSubscriptionRule] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["rules", b"rules"]) -> None: ... + +global___PubsubSubscriptionRules = PubsubSubscriptionRules + +@typing_extensions.final +class PubsubSubscriptionRule(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MATCH_FIELD_NUMBER: builtins.int + PATH_FIELD_NUMBER: builtins.int + match: builtins.str + path: builtins.str + def __init__( + self, + *, + match: builtins.str = ..., + path: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["match", b"match", "path", b"path"]) -> None: ... + +global___PubsubSubscriptionRule = PubsubSubscriptionRule + +@typing_extensions.final +class SetMetadataRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + +global___SetMetadataRequest = SetMetadataRequest + +@typing_extensions.final +class GetConfigurationRequest(google.protobuf.message.Message): + """GetConfigurationRequest is the message to get a list of key-value configuration from specified configuration store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + KEYS_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """Required. The name of configuration store.""" + @property + def keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """Optional. The key of the configuration item to fetch. + If set, only query for the specified configuration items. + Empty list means fetch all. + """ + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """Optional. The metadata which will be sent to configuration store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + keys: collections.abc.Iterable[builtins.str] | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... + +global___GetConfigurationRequest = GetConfigurationRequest + +@typing_extensions.final +class GetConfigurationResponse(google.protobuf.message.Message): + """GetConfigurationResponse is the response conveying the list of configuration values. + It should be the FULL configuration of specified application which contains all of its configuration items. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ItemsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> dapr.proto.common.v1.common_pb2.ConfigurationItem: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: dapr.proto.common.v1.common_pb2.ConfigurationItem | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ITEMS_FIELD_NUMBER: builtins.int + @property + def items(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem]: ... + def __init__( + self, + *, + items: collections.abc.Mapping[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["items", b"items"]) -> None: ... + +global___GetConfigurationResponse = GetConfigurationResponse + +@typing_extensions.final +class SubscribeConfigurationRequest(google.protobuf.message.Message): + """SubscribeConfigurationRequest is the message to get a list of key-value configuration from specified configuration store.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + STORE_NAME_FIELD_NUMBER: builtins.int + KEYS_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of configuration store.""" + @property + def keys(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """Optional. The key of the configuration item to fetch. + If set, only query for the specified configuration items. + Empty list means fetch all. + """ + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata which will be sent to configuration store components.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + keys: collections.abc.Iterable[builtins.str] | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["keys", b"keys", "metadata", b"metadata", "store_name", b"store_name"]) -> None: ... + +global___SubscribeConfigurationRequest = SubscribeConfigurationRequest + +@typing_extensions.final +class UnsubscribeConfigurationRequest(google.protobuf.message.Message): + """UnSubscribeConfigurationRequest is the message to stop watching the key-value configuration.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STORE_NAME_FIELD_NUMBER: builtins.int + ID_FIELD_NUMBER: builtins.int + store_name: builtins.str + """The name of configuration store.""" + id: builtins.str + """The id to unsubscribe.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "store_name", b"store_name"]) -> None: ... + +global___UnsubscribeConfigurationRequest = UnsubscribeConfigurationRequest + +@typing_extensions.final +class SubscribeConfigurationResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ItemsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> dapr.proto.common.v1.common_pb2.ConfigurationItem: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: dapr.proto.common.v1.common_pb2.ConfigurationItem | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + ID_FIELD_NUMBER: builtins.int + ITEMS_FIELD_NUMBER: builtins.int + id: builtins.str + """Subscribe id, used to stop subscription.""" + @property + def items(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem]: + """The list of items containing configuration values""" + def __init__( + self, + *, + id: builtins.str = ..., + items: collections.abc.Mapping[builtins.str, dapr.proto.common.v1.common_pb2.ConfigurationItem] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["id", b"id", "items", b"items"]) -> None: ... + +global___SubscribeConfigurationResponse = SubscribeConfigurationResponse + +@typing_extensions.final +class UnsubscribeConfigurationResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + OK_FIELD_NUMBER: builtins.int + MESSAGE_FIELD_NUMBER: builtins.int + ok: builtins.bool + message: builtins.str + def __init__( + self, + *, + ok: builtins.bool = ..., + message: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["message", b"message", "ok", b"ok"]) -> None: ... + +global___UnsubscribeConfigurationResponse = UnsubscribeConfigurationResponse + +@typing_extensions.final +class TryLockRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STORE_NAME_FIELD_NUMBER: builtins.int + RESOURCE_ID_FIELD_NUMBER: builtins.int + LOCK_OWNER_FIELD_NUMBER: builtins.int + EXPIRY_IN_SECONDS_FIELD_NUMBER: builtins.int + store_name: builtins.str + """Required. The lock store name,e.g. `redis`.""" + resource_id: builtins.str + """Required. resource_id is the lock key. e.g. `order_id_111` + It stands for "which resource I want to protect" + """ + lock_owner: builtins.str + """Required. lock_owner indicate the identifier of lock owner. + You can generate a uuid as lock_owner.For example,in golang: + + req.LockOwner = uuid.New().String() + + This field is per request,not per process,so it is different for each request, + which aims to prevent multi-thread in the same process trying the same lock concurrently. + + The reason why we don't make it automatically generated is: + 1. If it is automatically generated,there must be a 'my_lock_owner_id' field in the response. + This name is so weird that we think it is inappropriate to put it into the api spec + 2. If we change the field 'my_lock_owner_id' in the response to 'lock_owner',which means the current lock owner of this lock, + we find that in some lock services users can't get the current lock owner.Actually users don't need it at all. + 3. When reentrant lock is needed,the existing lock_owner is required to identify client and check "whether this client can reenter this lock". + So this field in the request shouldn't be removed. + """ + expiry_in_seconds: builtins.int + """Required. The time before expiry.The time unit is second.""" + def __init__( + self, + *, + store_name: builtins.str = ..., + resource_id: builtins.str = ..., + lock_owner: builtins.str = ..., + expiry_in_seconds: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["expiry_in_seconds", b"expiry_in_seconds", "lock_owner", b"lock_owner", "resource_id", b"resource_id", "store_name", b"store_name"]) -> None: ... + +global___TryLockRequest = TryLockRequest + +@typing_extensions.final +class TryLockResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SUCCESS_FIELD_NUMBER: builtins.int + success: builtins.bool + def __init__( + self, + *, + success: builtins.bool = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["success", b"success"]) -> None: ... + +global___TryLockResponse = TryLockResponse + +@typing_extensions.final +class UnlockRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STORE_NAME_FIELD_NUMBER: builtins.int + RESOURCE_ID_FIELD_NUMBER: builtins.int + LOCK_OWNER_FIELD_NUMBER: builtins.int + store_name: builtins.str + resource_id: builtins.str + """resource_id is the lock key.""" + lock_owner: builtins.str + def __init__( + self, + *, + store_name: builtins.str = ..., + resource_id: builtins.str = ..., + lock_owner: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["lock_owner", b"lock_owner", "resource_id", b"resource_id", "store_name", b"store_name"]) -> None: ... + +global___UnlockRequest = UnlockRequest + +@typing_extensions.final +class UnlockResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _Status: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _StatusEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[UnlockResponse._Status.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + SUCCESS: UnlockResponse._Status.ValueType # 0 + LOCK_DOES_NOT_EXIST: UnlockResponse._Status.ValueType # 1 + LOCK_BELONGS_TO_OTHERS: UnlockResponse._Status.ValueType # 2 + INTERNAL_ERROR: UnlockResponse._Status.ValueType # 3 + + class Status(_Status, metaclass=_StatusEnumTypeWrapper): ... + SUCCESS: UnlockResponse.Status.ValueType # 0 + LOCK_DOES_NOT_EXIST: UnlockResponse.Status.ValueType # 1 + LOCK_BELONGS_TO_OTHERS: UnlockResponse.Status.ValueType # 2 + INTERNAL_ERROR: UnlockResponse.Status.ValueType # 3 + + STATUS_FIELD_NUMBER: builtins.int + status: global___UnlockResponse.Status.ValueType + def __init__( + self, + *, + status: global___UnlockResponse.Status.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["status", b"status"]) -> None: ... + +global___UnlockResponse = UnlockResponse + +@typing_extensions.final +class SubtleGetKeyRequest(google.protobuf.message.Message): + """SubtleGetKeyRequest is the request object for SubtleGetKeyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + class _KeyFormat: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + + class _KeyFormatEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[SubtleGetKeyRequest._KeyFormat.ValueType], builtins.type): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + PEM: SubtleGetKeyRequest._KeyFormat.ValueType # 0 + """PEM (PKIX) (default)""" + JSON: SubtleGetKeyRequest._KeyFormat.ValueType # 1 + """JSON (JSON Web Key) as string""" + + class KeyFormat(_KeyFormat, metaclass=_KeyFormatEnumTypeWrapper): ... + PEM: SubtleGetKeyRequest.KeyFormat.ValueType # 0 + """PEM (PKIX) (default)""" + JSON: SubtleGetKeyRequest.KeyFormat.ValueType # 1 + """JSON (JSON Web Key) as string""" + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + FORMAT_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + name: builtins.str + """Name (or name/version) of the key to use in the key vault""" + format: global___SubtleGetKeyRequest.KeyFormat.ValueType + """Response format""" + def __init__( + self, + *, + component_name: builtins.str = ..., + name: builtins.str = ..., + format: global___SubtleGetKeyRequest.KeyFormat.ValueType = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["component_name", b"component_name", "format", b"format", "name", b"name"]) -> None: ... + +global___SubtleGetKeyRequest = SubtleGetKeyRequest + +@typing_extensions.final +class SubtleGetKeyResponse(google.protobuf.message.Message): + """SubtleGetKeyResponse is the response for SubtleGetKeyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + PUBLIC_KEY_FIELD_NUMBER: builtins.int + name: builtins.str + """Name (or name/version) of the key. + This is returned as response too in case there is a version. + """ + public_key: builtins.str + """Public key, encoded in the requested format""" + def __init__( + self, + *, + name: builtins.str = ..., + public_key: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "public_key", b"public_key"]) -> None: ... + +global___SubtleGetKeyResponse = SubtleGetKeyResponse + +@typing_extensions.final +class SubtleEncryptRequest(google.protobuf.message.Message): + """SubtleEncryptRequest is the request for SubtleEncryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + PLAINTEXT_FIELD_NUMBER: builtins.int + ALGORITHM_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + NONCE_FIELD_NUMBER: builtins.int + ASSOCIATED_DATA_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + plaintext: builtins.bytes + """Message to encrypt.""" + algorithm: builtins.str + """Algorithm to use, as in the JWA standard.""" + key_name: builtins.str + """Name (or name/version) of the key.""" + nonce: builtins.bytes + """Nonce / initialization vector. + Ignored with asymmetric ciphers. + """ + associated_data: builtins.bytes + """Associated Data when using AEAD ciphers (optional).""" + def __init__( + self, + *, + component_name: builtins.str = ..., + plaintext: builtins.bytes = ..., + algorithm: builtins.str = ..., + key_name: builtins.str = ..., + nonce: builtins.bytes = ..., + associated_data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "plaintext", b"plaintext"]) -> None: ... + +global___SubtleEncryptRequest = SubtleEncryptRequest + +@typing_extensions.final +class SubtleEncryptResponse(google.protobuf.message.Message): + """SubtleEncryptResponse is the response for SubtleEncryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CIPHERTEXT_FIELD_NUMBER: builtins.int + TAG_FIELD_NUMBER: builtins.int + ciphertext: builtins.bytes + """Encrypted ciphertext.""" + tag: builtins.bytes + """Authentication tag. + This is nil when not using an authenticated cipher. + """ + def __init__( + self, + *, + ciphertext: builtins.bytes = ..., + tag: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["ciphertext", b"ciphertext", "tag", b"tag"]) -> None: ... + +global___SubtleEncryptResponse = SubtleEncryptResponse + +@typing_extensions.final +class SubtleDecryptRequest(google.protobuf.message.Message): + """SubtleDecryptRequest is the request for SubtleDecryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + CIPHERTEXT_FIELD_NUMBER: builtins.int + ALGORITHM_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + NONCE_FIELD_NUMBER: builtins.int + TAG_FIELD_NUMBER: builtins.int + ASSOCIATED_DATA_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + ciphertext: builtins.bytes + """Message to decrypt.""" + algorithm: builtins.str + """Algorithm to use, as in the JWA standard.""" + key_name: builtins.str + """Name (or name/version) of the key.""" + nonce: builtins.bytes + """Nonce / initialization vector. + Ignored with asymmetric ciphers. + """ + tag: builtins.bytes + """Authentication tag. + This is nil when not using an authenticated cipher. + """ + associated_data: builtins.bytes + """Associated Data when using AEAD ciphers (optional).""" + def __init__( + self, + *, + component_name: builtins.str = ..., + ciphertext: builtins.bytes = ..., + algorithm: builtins.str = ..., + key_name: builtins.str = ..., + nonce: builtins.bytes = ..., + tag: builtins.bytes = ..., + associated_data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "ciphertext", b"ciphertext", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "tag", b"tag"]) -> None: ... + +global___SubtleDecryptRequest = SubtleDecryptRequest + +@typing_extensions.final +class SubtleDecryptResponse(google.protobuf.message.Message): + """SubtleDecryptResponse is the response for SubtleDecryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PLAINTEXT_FIELD_NUMBER: builtins.int + plaintext: builtins.bytes + """Decrypted plaintext.""" + def __init__( + self, + *, + plaintext: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["plaintext", b"plaintext"]) -> None: ... + +global___SubtleDecryptResponse = SubtleDecryptResponse + +@typing_extensions.final +class SubtleWrapKeyRequest(google.protobuf.message.Message): + """SubtleWrapKeyRequest is the request for SubtleWrapKeyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + PLAINTEXT_KEY_FIELD_NUMBER: builtins.int + ALGORITHM_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + NONCE_FIELD_NUMBER: builtins.int + ASSOCIATED_DATA_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + plaintext_key: builtins.bytes + """Key to wrap""" + algorithm: builtins.str + """Algorithm to use, as in the JWA standard.""" + key_name: builtins.str + """Name (or name/version) of the key.""" + nonce: builtins.bytes + """Nonce / initialization vector. + Ignored with asymmetric ciphers. + """ + associated_data: builtins.bytes + """Associated Data when using AEAD ciphers (optional).""" + def __init__( + self, + *, + component_name: builtins.str = ..., + plaintext_key: builtins.bytes = ..., + algorithm: builtins.str = ..., + key_name: builtins.str = ..., + nonce: builtins.bytes = ..., + associated_data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "plaintext_key", b"plaintext_key"]) -> None: ... + +global___SubtleWrapKeyRequest = SubtleWrapKeyRequest + +@typing_extensions.final +class SubtleWrapKeyResponse(google.protobuf.message.Message): + """SubtleWrapKeyResponse is the response for SubtleWrapKeyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + WRAPPED_KEY_FIELD_NUMBER: builtins.int + TAG_FIELD_NUMBER: builtins.int + wrapped_key: builtins.bytes + """Wrapped key.""" + tag: builtins.bytes + """Authentication tag. + This is nil when not using an authenticated cipher. + """ + def __init__( + self, + *, + wrapped_key: builtins.bytes = ..., + tag: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["tag", b"tag", "wrapped_key", b"wrapped_key"]) -> None: ... + +global___SubtleWrapKeyResponse = SubtleWrapKeyResponse + +@typing_extensions.final +class SubtleUnwrapKeyRequest(google.protobuf.message.Message): + """SubtleUnwrapKeyRequest is the request for SubtleUnwrapKeyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + WRAPPED_KEY_FIELD_NUMBER: builtins.int + ALGORITHM_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + NONCE_FIELD_NUMBER: builtins.int + TAG_FIELD_NUMBER: builtins.int + ASSOCIATED_DATA_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + wrapped_key: builtins.bytes + """Wrapped key.""" + algorithm: builtins.str + """Algorithm to use, as in the JWA standard.""" + key_name: builtins.str + """Name (or name/version) of the key.""" + nonce: builtins.bytes + """Nonce / initialization vector. + Ignored with asymmetric ciphers. + """ + tag: builtins.bytes + """Authentication tag. + This is nil when not using an authenticated cipher. + """ + associated_data: builtins.bytes + """Associated Data when using AEAD ciphers (optional).""" + def __init__( + self, + *, + component_name: builtins.str = ..., + wrapped_key: builtins.bytes = ..., + algorithm: builtins.str = ..., + key_name: builtins.str = ..., + nonce: builtins.bytes = ..., + tag: builtins.bytes = ..., + associated_data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "associated_data", b"associated_data", "component_name", b"component_name", "key_name", b"key_name", "nonce", b"nonce", "tag", b"tag", "wrapped_key", b"wrapped_key"]) -> None: ... + +global___SubtleUnwrapKeyRequest = SubtleUnwrapKeyRequest + +@typing_extensions.final +class SubtleUnwrapKeyResponse(google.protobuf.message.Message): + """SubtleUnwrapKeyResponse is the response for SubtleUnwrapKeyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PLAINTEXT_KEY_FIELD_NUMBER: builtins.int + plaintext_key: builtins.bytes + """Key in plaintext""" + def __init__( + self, + *, + plaintext_key: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["plaintext_key", b"plaintext_key"]) -> None: ... + +global___SubtleUnwrapKeyResponse = SubtleUnwrapKeyResponse + +@typing_extensions.final +class SubtleSignRequest(google.protobuf.message.Message): + """SubtleSignRequest is the request for SubtleSignAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + DIGEST_FIELD_NUMBER: builtins.int + ALGORITHM_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + digest: builtins.bytes + """Digest to sign.""" + algorithm: builtins.str + """Algorithm to use, as in the JWA standard.""" + key_name: builtins.str + """Name (or name/version) of the key.""" + def __init__( + self, + *, + component_name: builtins.str = ..., + digest: builtins.bytes = ..., + algorithm: builtins.str = ..., + key_name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "component_name", b"component_name", "digest", b"digest", "key_name", b"key_name"]) -> None: ... + +global___SubtleSignRequest = SubtleSignRequest + +@typing_extensions.final +class SubtleSignResponse(google.protobuf.message.Message): + """SubtleSignResponse is the response for SubtleSignAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SIGNATURE_FIELD_NUMBER: builtins.int + signature: builtins.bytes + """The signature that was computed""" + def __init__( + self, + *, + signature: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["signature", b"signature"]) -> None: ... + +global___SubtleSignResponse = SubtleSignResponse + +@typing_extensions.final +class SubtleVerifyRequest(google.protobuf.message.Message): + """SubtleVerifyRequest is the request for SubtleVerifyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + DIGEST_FIELD_NUMBER: builtins.int + ALGORITHM_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + SIGNATURE_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + digest: builtins.bytes + """Digest of the message.""" + algorithm: builtins.str + """Algorithm to use, as in the JWA standard.""" + key_name: builtins.str + """Name (or name/version) of the key.""" + signature: builtins.bytes + """Signature to verify.""" + def __init__( + self, + *, + component_name: builtins.str = ..., + digest: builtins.bytes = ..., + algorithm: builtins.str = ..., + key_name: builtins.str = ..., + signature: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "component_name", b"component_name", "digest", b"digest", "key_name", b"key_name", "signature", b"signature"]) -> None: ... + +global___SubtleVerifyRequest = SubtleVerifyRequest + +@typing_extensions.final +class SubtleVerifyResponse(google.protobuf.message.Message): + """SubtleVerifyResponse is the response for SubtleVerifyAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + VALID_FIELD_NUMBER: builtins.int + valid: builtins.bool + """True if the signature is valid.""" + def __init__( + self, + *, + valid: builtins.bool = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["valid", b"valid"]) -> None: ... + +global___SubtleVerifyResponse = SubtleVerifyResponse + +@typing_extensions.final +class EncryptRequest(google.protobuf.message.Message): + """EncryptRequest is the request for EncryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + OPTIONS_FIELD_NUMBER: builtins.int + PAYLOAD_FIELD_NUMBER: builtins.int + @property + def options(self) -> global___EncryptRequestOptions: + """Request details. Must be present in the first message only.""" + @property + def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: + """Chunk of data of arbitrary size.""" + def __init__( + self, + *, + options: global___EncryptRequestOptions | None = ..., + payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> None: ... + +global___EncryptRequest = EncryptRequest + +@typing_extensions.final +class EncryptRequestOptions(google.protobuf.message.Message): + """EncryptRequestOptions contains options for the first message in the EncryptAlpha1 request.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + KEY_WRAP_ALGORITHM_FIELD_NUMBER: builtins.int + DATA_ENCRYPTION_CIPHER_FIELD_NUMBER: builtins.int + OMIT_DECRYPTION_KEY_NAME_FIELD_NUMBER: builtins.int + DECRYPTION_KEY_NAME_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component. Required.""" + key_name: builtins.str + """Name (or name/version) of the key. Required.""" + key_wrap_algorithm: builtins.str + """Key wrapping algorithm to use. Required. + Supported options include: A256KW (alias: AES), A128CBC, A192CBC, A256CBC, RSA-OAEP-256 (alias: RSA). + """ + data_encryption_cipher: builtins.str + """Cipher used to encrypt data (optional): "aes-gcm" (default) or "chacha20-poly1305" """ + omit_decryption_key_name: builtins.bool + """If true, the encrypted document does not contain a key reference. + In that case, calls to the Decrypt method must provide a key reference (name or name/version). + Defaults to false. + """ + decryption_key_name: builtins.str + """Key reference to embed in the encrypted document (name or name/version). + This is helpful if the reference of the key used to decrypt the document is different from the one used to encrypt it. + If unset, uses the reference of the key used to encrypt the document (this is the default behavior). + This option is ignored if omit_decryption_key_name is true. + """ + def __init__( + self, + *, + component_name: builtins.str = ..., + key_name: builtins.str = ..., + key_wrap_algorithm: builtins.str = ..., + data_encryption_cipher: builtins.str = ..., + omit_decryption_key_name: builtins.bool = ..., + decryption_key_name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["component_name", b"component_name", "data_encryption_cipher", b"data_encryption_cipher", "decryption_key_name", b"decryption_key_name", "key_name", b"key_name", "key_wrap_algorithm", b"key_wrap_algorithm", "omit_decryption_key_name", b"omit_decryption_key_name"]) -> None: ... + +global___EncryptRequestOptions = EncryptRequestOptions + +@typing_extensions.final +class EncryptResponse(google.protobuf.message.Message): + """EncryptResponse is the response for EncryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PAYLOAD_FIELD_NUMBER: builtins.int + @property + def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: + """Chunk of data.""" + def __init__( + self, + *, + payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> None: ... + +global___EncryptResponse = EncryptResponse + +@typing_extensions.final +class DecryptRequest(google.protobuf.message.Message): + """DecryptRequest is the request for DecryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + OPTIONS_FIELD_NUMBER: builtins.int + PAYLOAD_FIELD_NUMBER: builtins.int + @property + def options(self) -> global___DecryptRequestOptions: + """Request details. Must be present in the first message only.""" + @property + def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: + """Chunk of data of arbitrary size.""" + def __init__( + self, + *, + options: global___DecryptRequestOptions | None = ..., + payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["options", b"options", "payload", b"payload"]) -> None: ... + +global___DecryptRequest = DecryptRequest + +@typing_extensions.final +class DecryptRequestOptions(google.protobuf.message.Message): + """DecryptRequestOptions contains options for the first message in the DecryptAlpha1 request.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + COMPONENT_NAME_FIELD_NUMBER: builtins.int + KEY_NAME_FIELD_NUMBER: builtins.int + component_name: builtins.str + """Name of the component""" + key_name: builtins.str + """Name (or name/version) of the key to decrypt the message. + Overrides any key reference included in the message if present. + This is required if the message doesn't include a key reference (i.e. was created with omit_decryption_key_name set to true). + """ + def __init__( + self, + *, + component_name: builtins.str = ..., + key_name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["component_name", b"component_name", "key_name", b"key_name"]) -> None: ... + +global___DecryptRequestOptions = DecryptRequestOptions + +@typing_extensions.final +class DecryptResponse(google.protobuf.message.Message): + """DecryptResponse is the response for DecryptAlpha1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PAYLOAD_FIELD_NUMBER: builtins.int + @property + def payload(self) -> dapr.proto.common.v1.common_pb2.StreamPayload: + """Chunk of data.""" + def __init__( + self, + *, + payload: dapr.proto.common.v1.common_pb2.StreamPayload | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["payload", b"payload"]) -> None: ... + +global___DecryptResponse = DecryptResponse + +@typing_extensions.final +class GetWorkflowRequest(google.protobuf.message.Message): + """GetWorkflowRequest is the request for GetWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance to query.""" + workflow_component: builtins.str + """Name of the workflow component.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... + +global___GetWorkflowRequest = GetWorkflowRequest + +@typing_extensions.final +class GetWorkflowResponse(google.protobuf.message.Message): + """GetWorkflowResponse is the response for GetWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class PropertiesEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_NAME_FIELD_NUMBER: builtins.int + CREATED_AT_FIELD_NUMBER: builtins.int + LAST_UPDATED_AT_FIELD_NUMBER: builtins.int + RUNTIME_STATUS_FIELD_NUMBER: builtins.int + PROPERTIES_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance.""" + workflow_name: builtins.str + """Name of the workflow.""" + @property + def created_at(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The time at which the workflow instance was created.""" + @property + def last_updated_at(self) -> google.protobuf.timestamp_pb2.Timestamp: + """The last time at which the workflow instance had its state changed.""" + runtime_status: builtins.str + """The current status of the workflow instance, for example, "PENDING", "RUNNING", "SUSPENDED", "COMPLETED", "FAILED", and "TERMINATED".""" + @property + def properties(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """Additional component-specific properties of the workflow instance.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_name: builtins.str = ..., + created_at: google.protobuf.timestamp_pb2.Timestamp | None = ..., + last_updated_at: google.protobuf.timestamp_pb2.Timestamp | None = ..., + runtime_status: builtins.str = ..., + properties: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["created_at", b"created_at", "last_updated_at", b"last_updated_at"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["created_at", b"created_at", "instance_id", b"instance_id", "last_updated_at", b"last_updated_at", "properties", b"properties", "runtime_status", b"runtime_status", "workflow_name", b"workflow_name"]) -> None: ... + +global___GetWorkflowResponse = GetWorkflowResponse + +@typing_extensions.final +class StartWorkflowRequest(google.protobuf.message.Message): + """StartWorkflowRequest is the request for StartWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class OptionsEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + WORKFLOW_NAME_FIELD_NUMBER: builtins.int + OPTIONS_FIELD_NUMBER: builtins.int + INPUT_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """The ID to assign to the started workflow instance. If empty, a random ID is generated.""" + workflow_component: builtins.str + """Name of the workflow component.""" + workflow_name: builtins.str + """Name of the workflow.""" + @property + def options(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """Additional component-specific options for starting the workflow instance.""" + input: builtins.bytes + """Input data for the workflow instance.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + workflow_name: builtins.str = ..., + options: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + input: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["input", b"input", "instance_id", b"instance_id", "options", b"options", "workflow_component", b"workflow_component", "workflow_name", b"workflow_name"]) -> None: ... + +global___StartWorkflowRequest = StartWorkflowRequest + +@typing_extensions.final +class StartWorkflowResponse(google.protobuf.message.Message): + """StartWorkflowResponse is the response for StartWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the started workflow instance.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id"]) -> None: ... + +global___StartWorkflowResponse = StartWorkflowResponse + +@typing_extensions.final +class TerminateWorkflowRequest(google.protobuf.message.Message): + """TerminateWorkflowRequest is the request for TerminateWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance to terminate.""" + workflow_component: builtins.str + """Name of the workflow component.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... + +global___TerminateWorkflowRequest = TerminateWorkflowRequest + +@typing_extensions.final +class PauseWorkflowRequest(google.protobuf.message.Message): + """PauseWorkflowRequest is the request for PauseWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance to pause.""" + workflow_component: builtins.str + """Name of the workflow component.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... + +global___PauseWorkflowRequest = PauseWorkflowRequest + +@typing_extensions.final +class ResumeWorkflowRequest(google.protobuf.message.Message): + """ResumeWorkflowRequest is the request for ResumeWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance to resume.""" + workflow_component: builtins.str + """Name of the workflow component.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... + +global___ResumeWorkflowRequest = ResumeWorkflowRequest + +@typing_extensions.final +class RaiseEventWorkflowRequest(google.protobuf.message.Message): + """RaiseEventWorkflowRequest is the request for RaiseEventWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + EVENT_NAME_FIELD_NUMBER: builtins.int + EVENT_DATA_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance to raise an event for.""" + workflow_component: builtins.str + """Name of the workflow component.""" + event_name: builtins.str + """Name of the event.""" + event_data: builtins.bytes + """Data associated with the event.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + event_name: builtins.str = ..., + event_data: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["event_data", b"event_data", "event_name", b"event_name", "instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... + +global___RaiseEventWorkflowRequest = RaiseEventWorkflowRequest + +@typing_extensions.final +class PurgeWorkflowRequest(google.protobuf.message.Message): + """PurgeWorkflowRequest is the request for PurgeWorkflowBeta1.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + INSTANCE_ID_FIELD_NUMBER: builtins.int + WORKFLOW_COMPONENT_FIELD_NUMBER: builtins.int + instance_id: builtins.str + """ID of the workflow instance to purge.""" + workflow_component: builtins.str + """Name of the workflow component.""" + def __init__( + self, + *, + instance_id: builtins.str = ..., + workflow_component: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["instance_id", b"instance_id", "workflow_component", b"workflow_component"]) -> None: ... + +global___PurgeWorkflowRequest = PurgeWorkflowRequest + +@typing_extensions.final +class ShutdownRequest(google.protobuf.message.Message): + """ShutdownRequest is the request for Shutdown. + Empty + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___ShutdownRequest = ShutdownRequest + +@typing_extensions.final +class Job(google.protobuf.message.Message): + """Job is the definition of a job. At least one of schedule or due_time must be + provided but can also be provided together. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + SCHEDULE_FIELD_NUMBER: builtins.int + REPEATS_FIELD_NUMBER: builtins.int + DUE_TIME_FIELD_NUMBER: builtins.int + TTL_FIELD_NUMBER: builtins.int + DATA_FIELD_NUMBER: builtins.int + name: builtins.str + """The unique name for the job.""" + schedule: builtins.str + """schedule is an optional schedule at which the job is to be run. + Accepts both systemd timer style cron expressions, as well as human + readable '@' prefixed period strings as defined below. + + Systemd timer style cron accepts 6 fields: + seconds | minutes | hours | day of month | month | day of week + 0-59 | 0-59 | 0-23 | 1-31 | 1-12/jan-dec | 0-6/sun-sat + + "0 30 * * * *" - every hour on the half hour + "0 15 3 * * *" - every day at 03:15 + + Period string expressions: + Entry | Description | Equivalent To + ----- | ----------- | ------------- + @every `` | Run every `` (e.g. '@every 1h30m') | N/A + @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * + @monthly | Run once a month, midnight, first of month | 0 0 0 1 * * + @weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0 + @daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * + @hourly | Run once an hour, beginning of hour | 0 0 * * * * + """ + repeats: builtins.int + """repeats is the optional number of times in which the job should be + triggered. If not set, the job will run indefinitely or until expiration. + """ + due_time: builtins.str + """due_time is the optional time at which the job should be active, or the + "one shot" time if other scheduling type fields are not provided. Accepts + a "point in time" string in the format of RFC3339, Go duration string + (calculated from job creation time), or non-repeating ISO8601. + """ + ttl: builtins.str + """ttl is the optional time to live or expiration of the job. Accepts a + "point in time" string in the format of RFC3339, Go duration string + (calculated from job creation time), or non-repeating ISO8601. + """ + @property + def data(self) -> google.protobuf.any_pb2.Any: + """payload is the serialized job payload that will be sent to the recipient + when the job is triggered. + """ + def __init__( + self, + *, + name: builtins.str = ..., + schedule: builtins.str | None = ..., + repeats: builtins.int | None = ..., + due_time: builtins.str | None = ..., + ttl: builtins.str | None = ..., + data: google.protobuf.any_pb2.Any | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_due_time", b"_due_time", "_repeats", b"_repeats", "_schedule", b"_schedule", "_ttl", b"_ttl", "data", b"data", "due_time", b"due_time", "repeats", b"repeats", "schedule", b"schedule", "ttl", b"ttl"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_due_time", b"_due_time", "_repeats", b"_repeats", "_schedule", b"_schedule", "_ttl", b"_ttl", "data", b"data", "due_time", b"due_time", "name", b"name", "repeats", b"repeats", "schedule", b"schedule", "ttl", b"ttl"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_due_time", b"_due_time"]) -> typing_extensions.Literal["due_time"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_repeats", b"_repeats"]) -> typing_extensions.Literal["repeats"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_schedule", b"_schedule"]) -> typing_extensions.Literal["schedule"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_ttl", b"_ttl"]) -> typing_extensions.Literal["ttl"] | None: ... + +global___Job = Job + +@typing_extensions.final +class ScheduleJobRequest(google.protobuf.message.Message): + """ScheduleJobRequest is the message to create/schedule the job.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + JOB_FIELD_NUMBER: builtins.int + @property + def job(self) -> global___Job: + """The job details.""" + def __init__( + self, + *, + job: global___Job | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["job", b"job"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["job", b"job"]) -> None: ... + +global___ScheduleJobRequest = ScheduleJobRequest + +@typing_extensions.final +class ScheduleJobResponse(google.protobuf.message.Message): + """ScheduleJobResponse is the message response to create/schedule the job. + Empty + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___ScheduleJobResponse = ScheduleJobResponse + +@typing_extensions.final +class GetJobRequest(google.protobuf.message.Message): + """GetJobRequest is the message to retrieve a job.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + name: builtins.str + """The name of the job.""" + def __init__( + self, + *, + name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ... + +global___GetJobRequest = GetJobRequest + +@typing_extensions.final +class GetJobResponse(google.protobuf.message.Message): + """GetJobResponse is the message's response for a job retrieved.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + JOB_FIELD_NUMBER: builtins.int + @property + def job(self) -> global___Job: + """The job details.""" + def __init__( + self, + *, + job: global___Job | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["job", b"job"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["job", b"job"]) -> None: ... + +global___GetJobResponse = GetJobResponse + +@typing_extensions.final +class DeleteJobRequest(google.protobuf.message.Message): + """DeleteJobRequest is the message to delete the job by name.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + NAME_FIELD_NUMBER: builtins.int + name: builtins.str + """The name of the job.""" + def __init__( + self, + *, + name: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name"]) -> None: ... + +global___DeleteJobRequest = DeleteJobRequest + +@typing_extensions.final +class DeleteJobResponse(google.protobuf.message.Message): + """DeleteJobResponse is the message response to delete the job by name. + Empty + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + +global___DeleteJobResponse = DeleteJobResponse + +@typing_extensions.final +class ConversationAlpha1Request(google.protobuf.message.Message): + """ConversationAlpha1Request is the request object for Conversation.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ParametersEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> google.protobuf.any_pb2.Any: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: google.protobuf.any_pb2.Any | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + value: builtins.str + def __init__( + self, + *, + key: builtins.str = ..., + value: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + NAME_FIELD_NUMBER: builtins.int + CONTEXTID_FIELD_NUMBER: builtins.int + INPUTS_FIELD_NUMBER: builtins.int + PARAMETERS_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + SCRUBPII_FIELD_NUMBER: builtins.int + TEMPERATURE_FIELD_NUMBER: builtins.int + name: builtins.str + """The name of Coverstaion component""" + contextID: builtins.str + """The ID of an existing chat (like in ChatGPT)""" + @property + def inputs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ConversationInput]: + """Inputs for the conversation, support multiple input in one time.""" + @property + def parameters(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, google.protobuf.any_pb2.Any]: + """Parameters for all custom fields.""" + @property + def metadata(self) -> google.protobuf.internal.containers.ScalarMap[builtins.str, builtins.str]: + """The metadata passing to conversation components.""" + scrubPII: builtins.bool + """Scrub PII data that comes back from the LLM""" + temperature: builtins.float + """Temperature for the LLM to optimize for creativity or predictability""" + def __init__( + self, + *, + name: builtins.str = ..., + contextID: builtins.str | None = ..., + inputs: collections.abc.Iterable[global___ConversationInput] | None = ..., + parameters: collections.abc.Mapping[builtins.str, google.protobuf.any_pb2.Any] | None = ..., + metadata: collections.abc.Mapping[builtins.str, builtins.str] | None = ..., + scrubPII: builtins.bool | None = ..., + temperature: builtins.float | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "_scrubPII", b"_scrubPII", "_temperature", b"_temperature", "contextID", b"contextID", "scrubPII", b"scrubPII", "temperature", b"temperature"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "_scrubPII", b"_scrubPII", "_temperature", b"_temperature", "contextID", b"contextID", "inputs", b"inputs", "metadata", b"metadata", "name", b"name", "parameters", b"parameters", "scrubPII", b"scrubPII", "temperature", b"temperature"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_contextID", b"_contextID"]) -> typing_extensions.Literal["contextID"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_scrubPII", b"_scrubPII"]) -> typing_extensions.Literal["scrubPII"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_temperature", b"_temperature"]) -> typing_extensions.Literal["temperature"] | None: ... + +global___ConversationAlpha1Request = ConversationAlpha1Request + +@typing_extensions.final +class ConversationInput(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + MESSAGE_FIELD_NUMBER: builtins.int + ROLE_FIELD_NUMBER: builtins.int + SCRUBPII_FIELD_NUMBER: builtins.int + message: builtins.str + """The message to send to the llm""" + role: builtins.str + """The role to set for the message""" + scrubPII: builtins.bool + """Scrub PII data that goes into the LLM""" + def __init__( + self, + *, + message: builtins.str = ..., + role: builtins.str | None = ..., + scrubPII: builtins.bool | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_role", b"_role", "_scrubPII", b"_scrubPII", "role", b"role", "scrubPII", b"scrubPII"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_role", b"_role", "_scrubPII", b"_scrubPII", "message", b"message", "role", b"role", "scrubPII", b"scrubPII"]) -> None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_role", b"_role"]) -> typing_extensions.Literal["role"] | None: ... + @typing.overload + def WhichOneof(self, oneof_group: typing_extensions.Literal["_scrubPII", b"_scrubPII"]) -> typing_extensions.Literal["scrubPII"] | None: ... + +global___ConversationInput = ConversationInput + +@typing_extensions.final +class ConversationAlpha1Result(google.protobuf.message.Message): + """ConversationAlpha1Result is the result for one input.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ParametersEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> google.protobuf.any_pb2.Any: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: google.protobuf.any_pb2.Any | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key", b"key", "value", b"value"]) -> None: ... + + RESULT_FIELD_NUMBER: builtins.int + PARAMETERS_FIELD_NUMBER: builtins.int + result: builtins.str + """Result for the one conversation input.""" + @property + def parameters(self) -> google.protobuf.internal.containers.MessageMap[builtins.str, google.protobuf.any_pb2.Any]: + """Parameters for all custom fields.""" + def __init__( + self, + *, + result: builtins.str = ..., + parameters: collections.abc.Mapping[builtins.str, google.protobuf.any_pb2.Any] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["parameters", b"parameters", "result", b"result"]) -> None: ... + +global___ConversationAlpha1Result = ConversationAlpha1Result + +@typing_extensions.final +class ConversationAlpha1Response(google.protobuf.message.Message): + """ConversationAlpha1Response is the response for Conversation.""" + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + CONTEXTID_FIELD_NUMBER: builtins.int + OUTPUTS_FIELD_NUMBER: builtins.int + contextID: builtins.str + """The ID of an existing chat (like in ChatGPT)""" + @property + def outputs(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ConversationAlpha1Result]: + """An array of results.""" + def __init__( + self, + *, + contextID: builtins.str | None = ..., + outputs: collections.abc.Iterable[global___ConversationAlpha1Result] | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "contextID", b"contextID"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["_contextID", b"_contextID", "contextID", b"contextID", "outputs", b"outputs"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["_contextID", b"_contextID"]) -> typing_extensions.Literal["contextID"] | None: ... + +global___ConversationAlpha1Response = ConversationAlpha1Response diff --git a/dapr/proto/runtime/v1/dapr_pb2_grpc.py b/dapr/proto/runtime/v1/dapr_pb2_grpc.py index 60b4c241d..151b02863 100644 --- a/dapr/proto/runtime/v1/dapr_pb2_grpc.py +++ b/dapr/proto/runtime/v1/dapr_pb2_grpc.py @@ -1,2046 +1,2046 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 -from dapr.proto.runtime.v1 import dapr_pb2 as dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2 -from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 - - -class DaprStub(object): - """Dapr service provides APIs to user application to access Dapr building blocks. - """ - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.InvokeService = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/InvokeService', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeServiceRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, - ) - self.GetState = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetState', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateResponse.FromString, - ) - self.GetBulkState = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetBulkState', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateResponse.FromString, - ) - self.SaveState = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SaveState', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SaveStateRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.QueryStateAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/QueryStateAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateResponse.FromString, - ) - self.DeleteState = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/DeleteState', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteStateRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.DeleteBulkState = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/DeleteBulkState', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteBulkStateRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ExecuteStateTransaction = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/ExecuteStateTransaction', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteStateTransactionRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.PublishEvent = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/PublishEvent', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PublishEventRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.BulkPublishEventAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/BulkPublishEventAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishResponse.FromString, - ) - self.SubscribeTopicEventsAlpha1 = channel.stream_stream( - '/dapr.proto.runtime.v1.Dapr/SubscribeTopicEventsAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsRequestAlpha1.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsResponseAlpha1.FromString, - ) - self.InvokeBinding = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/InvokeBinding', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingResponse.FromString, - ) - self.GetSecret = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetSecret', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretResponse.FromString, - ) - self.GetBulkSecret = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetBulkSecret', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretResponse.FromString, - ) - self.RegisterActorTimer = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/RegisterActorTimer', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorTimerRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.UnregisterActorTimer = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/UnregisterActorTimer', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorTimerRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.RegisterActorReminder = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/RegisterActorReminder', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorReminderRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.UnregisterActorReminder = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/UnregisterActorReminder', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorReminderRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.GetActorState = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetActorState', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateResponse.FromString, - ) - self.ExecuteActorStateTransaction = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/ExecuteActorStateTransaction', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteActorStateTransactionRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.InvokeActor = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/InvokeActor', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorResponse.FromString, - ) - self.GetConfigurationAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetConfigurationAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, - ) - self.GetConfiguration = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetConfiguration', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, - ) - self.SubscribeConfigurationAlpha1 = channel.unary_stream( - '/dapr.proto.runtime.v1.Dapr/SubscribeConfigurationAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, - ) - self.SubscribeConfiguration = channel.unary_stream( - '/dapr.proto.runtime.v1.Dapr/SubscribeConfiguration', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, - ) - self.UnsubscribeConfigurationAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfigurationAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, - ) - self.UnsubscribeConfiguration = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfiguration', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, - ) - self.TryLockAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/TryLockAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockResponse.FromString, - ) - self.UnlockAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/UnlockAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockResponse.FromString, - ) - self.EncryptAlpha1 = channel.stream_stream( - '/dapr.proto.runtime.v1.Dapr/EncryptAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptResponse.FromString, - ) - self.DecryptAlpha1 = channel.stream_stream( - '/dapr.proto.runtime.v1.Dapr/DecryptAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptResponse.FromString, - ) - self.GetMetadata = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetMetadata', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataResponse.FromString, - ) - self.SetMetadata = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SetMetadata', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SetMetadataRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.SubtleGetKeyAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleGetKeyAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyResponse.FromString, - ) - self.SubtleEncryptAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleEncryptAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptResponse.FromString, - ) - self.SubtleDecryptAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleDecryptAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptResponse.FromString, - ) - self.SubtleWrapKeyAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleWrapKeyAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyResponse.FromString, - ) - self.SubtleUnwrapKeyAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleUnwrapKeyAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyResponse.FromString, - ) - self.SubtleSignAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleSignAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignResponse.FromString, - ) - self.SubtleVerifyAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/SubtleVerifyAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyResponse.FromString, - ) - self.StartWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/StartWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, - ) - self.GetWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, - ) - self.PurgeWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.TerminateWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.PauseWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/PauseWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ResumeWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.RaiseEventWorkflowAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.StartWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/StartWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, - ) - self.GetWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, - ) - self.PurgeWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.TerminateWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.PauseWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/PauseWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ResumeWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.RaiseEventWorkflowBeta1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowBeta1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.Shutdown = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/Shutdown', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ShutdownRequest.SerializeToString, - response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, - ) - self.ScheduleJobAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/ScheduleJobAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobResponse.FromString, - ) - self.GetJobAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/GetJobAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobResponse.FromString, - ) - self.DeleteJobAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/DeleteJobAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobRequest.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobResponse.FromString, - ) - self.ConverseAlpha1 = channel.unary_unary( - '/dapr.proto.runtime.v1.Dapr/ConverseAlpha1', - request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Request.SerializeToString, - response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Response.FromString, - ) - - -class DaprServicer(object): - """Dapr service provides APIs to user application to access Dapr building blocks. - """ - - def InvokeService(self, request, context): - """Invokes a method on a remote Dapr app. - Deprecated: Use proxy mode service invocation instead. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetState(self, request, context): - """Gets the state for a specific key. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetBulkState(self, request, context): - """Gets a bulk of state items for a list of keys - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SaveState(self, request, context): - """Saves the state for a specific key. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def QueryStateAlpha1(self, request, context): - """Queries the state. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteState(self, request, context): - """Deletes the state for a specific key. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteBulkState(self, request, context): - """Deletes a bulk of state items for a list of keys - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ExecuteStateTransaction(self, request, context): - """Executes transactions for a specified store - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PublishEvent(self, request, context): - """Publishes events to the specific topic. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def BulkPublishEventAlpha1(self, request, context): - """Bulk Publishes multiple events to the specified topic. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubscribeTopicEventsAlpha1(self, request_iterator, context): - """SubscribeTopicEventsAlpha1 subscribes to a PubSub topic and receives topic - events from it. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def InvokeBinding(self, request, context): - """Invokes binding data to specific output bindings - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetSecret(self, request, context): - """Gets secrets from secret stores. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetBulkSecret(self, request, context): - """Gets a bulk of secrets - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def RegisterActorTimer(self, request, context): - """Register an actor timer. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UnregisterActorTimer(self, request, context): - """Unregister an actor timer. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def RegisterActorReminder(self, request, context): - """Register an actor reminder. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UnregisterActorReminder(self, request, context): - """Unregister an actor reminder. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetActorState(self, request, context): - """Gets the state for a specific actor. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ExecuteActorStateTransaction(self, request, context): - """Executes state transactions for a specified actor - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def InvokeActor(self, request, context): - """InvokeActor calls a method on an actor. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetConfigurationAlpha1(self, request, context): - """GetConfiguration gets configuration from configuration store. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetConfiguration(self, request, context): - """GetConfiguration gets configuration from configuration store. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubscribeConfigurationAlpha1(self, request, context): - """SubscribeConfiguration gets configuration from configuration store and subscribe the updates event by grpc stream - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubscribeConfiguration(self, request, context): - """SubscribeConfiguration gets configuration from configuration store and subscribe the updates event by grpc stream - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UnsubscribeConfigurationAlpha1(self, request, context): - """UnSubscribeConfiguration unsubscribe the subscription of configuration - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UnsubscribeConfiguration(self, request, context): - """UnSubscribeConfiguration unsubscribe the subscription of configuration - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def TryLockAlpha1(self, request, context): - """TryLockAlpha1 tries to get a lock with an expiry. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def UnlockAlpha1(self, request, context): - """UnlockAlpha1 unlocks a lock. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def EncryptAlpha1(self, request_iterator, context): - """EncryptAlpha1 encrypts a message using the Dapr encryption scheme and a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DecryptAlpha1(self, request_iterator, context): - """DecryptAlpha1 decrypts a message using the Dapr encryption scheme and a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetMetadata(self, request, context): - """Gets metadata of the sidecar - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetMetadata(self, request, context): - """Sets value in extended metadata of the sidecar - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleGetKeyAlpha1(self, request, context): - """SubtleGetKeyAlpha1 returns the public part of an asymmetric key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleEncryptAlpha1(self, request, context): - """SubtleEncryptAlpha1 encrypts a small message using a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleDecryptAlpha1(self, request, context): - """SubtleDecryptAlpha1 decrypts a small message using a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleWrapKeyAlpha1(self, request, context): - """SubtleWrapKeyAlpha1 wraps a key using a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleUnwrapKeyAlpha1(self, request, context): - """SubtleUnwrapKeyAlpha1 unwraps a key using a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleSignAlpha1(self, request, context): - """SubtleSignAlpha1 signs a message using a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SubtleVerifyAlpha1(self, request, context): - """SubtleVerifyAlpha1 verifies the signature of a message using a key stored in the vault. - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StartWorkflowAlpha1(self, request, context): - """Starts a new instance of a workflow - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetWorkflowAlpha1(self, request, context): - """Gets details about a started workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PurgeWorkflowAlpha1(self, request, context): - """Purge Workflow - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def TerminateWorkflowAlpha1(self, request, context): - """Terminates a running workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PauseWorkflowAlpha1(self, request, context): - """Pauses a running workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResumeWorkflowAlpha1(self, request, context): - """Resumes a paused workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def RaiseEventWorkflowAlpha1(self, request, context): - """Raise an event to a running workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def StartWorkflowBeta1(self, request, context): - """Starts a new instance of a workflow - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetWorkflowBeta1(self, request, context): - """Gets details about a started workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PurgeWorkflowBeta1(self, request, context): - """Purge Workflow - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def TerminateWorkflowBeta1(self, request, context): - """Terminates a running workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def PauseWorkflowBeta1(self, request, context): - """Pauses a running workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ResumeWorkflowBeta1(self, request, context): - """Resumes a paused workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def RaiseEventWorkflowBeta1(self, request, context): - """Raise an event to a running workflow instance - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def Shutdown(self, request, context): - """Shutdown the sidecar - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ScheduleJobAlpha1(self, request, context): - """Create and schedule a job - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetJobAlpha1(self, request, context): - """Gets a scheduled job - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteJobAlpha1(self, request, context): - """Delete a job - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ConverseAlpha1(self, request, context): - """Converse with a LLM service - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_DaprServicer_to_server(servicer, server): - rpc_method_handlers = { - 'InvokeService': grpc.unary_unary_rpc_method_handler( - servicer.InvokeService, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeServiceRequest.FromString, - response_serializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.SerializeToString, - ), - 'GetState': grpc.unary_unary_rpc_method_handler( - servicer.GetState, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateResponse.SerializeToString, - ), - 'GetBulkState': grpc.unary_unary_rpc_method_handler( - servicer.GetBulkState, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateResponse.SerializeToString, - ), - 'SaveState': grpc.unary_unary_rpc_method_handler( - servicer.SaveState, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SaveStateRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'QueryStateAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.QueryStateAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateResponse.SerializeToString, - ), - 'DeleteState': grpc.unary_unary_rpc_method_handler( - servicer.DeleteState, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteStateRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'DeleteBulkState': grpc.unary_unary_rpc_method_handler( - servicer.DeleteBulkState, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteBulkStateRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ExecuteStateTransaction': grpc.unary_unary_rpc_method_handler( - servicer.ExecuteStateTransaction, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteStateTransactionRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'PublishEvent': grpc.unary_unary_rpc_method_handler( - servicer.PublishEvent, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PublishEventRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'BulkPublishEventAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.BulkPublishEventAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishResponse.SerializeToString, - ), - 'SubscribeTopicEventsAlpha1': grpc.stream_stream_rpc_method_handler( - servicer.SubscribeTopicEventsAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsRequestAlpha1.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsResponseAlpha1.SerializeToString, - ), - 'InvokeBinding': grpc.unary_unary_rpc_method_handler( - servicer.InvokeBinding, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingResponse.SerializeToString, - ), - 'GetSecret': grpc.unary_unary_rpc_method_handler( - servicer.GetSecret, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretResponse.SerializeToString, - ), - 'GetBulkSecret': grpc.unary_unary_rpc_method_handler( - servicer.GetBulkSecret, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretResponse.SerializeToString, - ), - 'RegisterActorTimer': grpc.unary_unary_rpc_method_handler( - servicer.RegisterActorTimer, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorTimerRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'UnregisterActorTimer': grpc.unary_unary_rpc_method_handler( - servicer.UnregisterActorTimer, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorTimerRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'RegisterActorReminder': grpc.unary_unary_rpc_method_handler( - servicer.RegisterActorReminder, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorReminderRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'UnregisterActorReminder': grpc.unary_unary_rpc_method_handler( - servicer.UnregisterActorReminder, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorReminderRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'GetActorState': grpc.unary_unary_rpc_method_handler( - servicer.GetActorState, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateResponse.SerializeToString, - ), - 'ExecuteActorStateTransaction': grpc.unary_unary_rpc_method_handler( - servicer.ExecuteActorStateTransaction, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteActorStateTransactionRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'InvokeActor': grpc.unary_unary_rpc_method_handler( - servicer.InvokeActor, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorResponse.SerializeToString, - ), - 'GetConfigurationAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.GetConfigurationAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.SerializeToString, - ), - 'GetConfiguration': grpc.unary_unary_rpc_method_handler( - servicer.GetConfiguration, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.SerializeToString, - ), - 'SubscribeConfigurationAlpha1': grpc.unary_stream_rpc_method_handler( - servicer.SubscribeConfigurationAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.SerializeToString, - ), - 'SubscribeConfiguration': grpc.unary_stream_rpc_method_handler( - servicer.SubscribeConfiguration, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.SerializeToString, - ), - 'UnsubscribeConfigurationAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.UnsubscribeConfigurationAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.SerializeToString, - ), - 'UnsubscribeConfiguration': grpc.unary_unary_rpc_method_handler( - servicer.UnsubscribeConfiguration, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.SerializeToString, - ), - 'TryLockAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.TryLockAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockResponse.SerializeToString, - ), - 'UnlockAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.UnlockAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockResponse.SerializeToString, - ), - 'EncryptAlpha1': grpc.stream_stream_rpc_method_handler( - servicer.EncryptAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptResponse.SerializeToString, - ), - 'DecryptAlpha1': grpc.stream_stream_rpc_method_handler( - servicer.DecryptAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptResponse.SerializeToString, - ), - 'GetMetadata': grpc.unary_unary_rpc_method_handler( - servicer.GetMetadata, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataResponse.SerializeToString, - ), - 'SetMetadata': grpc.unary_unary_rpc_method_handler( - servicer.SetMetadata, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SetMetadataRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'SubtleGetKeyAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleGetKeyAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyResponse.SerializeToString, - ), - 'SubtleEncryptAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleEncryptAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptResponse.SerializeToString, - ), - 'SubtleDecryptAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleDecryptAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptResponse.SerializeToString, - ), - 'SubtleWrapKeyAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleWrapKeyAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyResponse.SerializeToString, - ), - 'SubtleUnwrapKeyAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleUnwrapKeyAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyResponse.SerializeToString, - ), - 'SubtleSignAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleSignAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignResponse.SerializeToString, - ), - 'SubtleVerifyAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.SubtleVerifyAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyResponse.SerializeToString, - ), - 'StartWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.StartWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.SerializeToString, - ), - 'GetWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.GetWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.SerializeToString, - ), - 'PurgeWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.PurgeWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'TerminateWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.TerminateWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'PauseWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.PauseWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ResumeWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.ResumeWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'RaiseEventWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.RaiseEventWorkflowAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'StartWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.StartWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.SerializeToString, - ), - 'GetWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.GetWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.SerializeToString, - ), - 'PurgeWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.PurgeWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'TerminateWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.TerminateWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'PauseWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.PauseWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ResumeWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.ResumeWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'RaiseEventWorkflowBeta1': grpc.unary_unary_rpc_method_handler( - servicer.RaiseEventWorkflowBeta1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'Shutdown': grpc.unary_unary_rpc_method_handler( - servicer.Shutdown, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ShutdownRequest.FromString, - response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, - ), - 'ScheduleJobAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.ScheduleJobAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobResponse.SerializeToString, - ), - 'GetJobAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.GetJobAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobResponse.SerializeToString, - ), - 'DeleteJobAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.DeleteJobAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobRequest.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobResponse.SerializeToString, - ), - 'ConverseAlpha1': grpc.unary_unary_rpc_method_handler( - servicer.ConverseAlpha1, - request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Request.FromString, - response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Response.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'dapr.proto.runtime.v1.Dapr', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - - # This class is part of an EXPERIMENTAL API. -class Dapr(object): - """Dapr service provides APIs to user application to access Dapr building blocks. - """ - - @staticmethod - def InvokeService(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/InvokeService', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeServiceRequest.SerializeToString, - dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetState', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetBulkState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetBulkState', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SaveState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SaveState', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SaveStateRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def QueryStateAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/QueryStateAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/DeleteState', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteStateRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteBulkState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/DeleteBulkState', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteBulkStateRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ExecuteStateTransaction(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ExecuteStateTransaction', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteStateTransactionRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PublishEvent(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PublishEvent', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PublishEventRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def BulkPublishEventAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/BulkPublishEventAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubscribeTopicEventsAlpha1(request_iterator, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.stream_stream(request_iterator, target, '/dapr.proto.runtime.v1.Dapr/SubscribeTopicEventsAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsRequestAlpha1.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsResponseAlpha1.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def InvokeBinding(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/InvokeBinding', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetSecret(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetSecret', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetBulkSecret(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetBulkSecret', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def RegisterActorTimer(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RegisterActorTimer', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorTimerRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UnregisterActorTimer(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnregisterActorTimer', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorTimerRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def RegisterActorReminder(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RegisterActorReminder', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorReminderRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UnregisterActorReminder(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnregisterActorReminder', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorReminderRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetActorState(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetActorState', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ExecuteActorStateTransaction(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ExecuteActorStateTransaction', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteActorStateTransactionRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def InvokeActor(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/InvokeActor', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetConfigurationAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetConfigurationAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetConfiguration(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetConfiguration', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubscribeConfigurationAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/dapr.proto.runtime.v1.Dapr/SubscribeConfigurationAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubscribeConfiguration(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_stream(request, target, '/dapr.proto.runtime.v1.Dapr/SubscribeConfiguration', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UnsubscribeConfigurationAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfigurationAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UnsubscribeConfiguration(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfiguration', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def TryLockAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/TryLockAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def UnlockAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnlockAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def EncryptAlpha1(request_iterator, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.stream_stream(request_iterator, target, '/dapr.proto.runtime.v1.Dapr/EncryptAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DecryptAlpha1(request_iterator, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.stream_stream(request_iterator, target, '/dapr.proto.runtime.v1.Dapr/DecryptAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetMetadata(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetMetadata', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SetMetadata(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SetMetadata', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SetMetadataRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleGetKeyAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleGetKeyAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleEncryptAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleEncryptAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleDecryptAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleDecryptAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleWrapKeyAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleWrapKeyAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleUnwrapKeyAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleUnwrapKeyAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleSignAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleSignAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def SubtleVerifyAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleVerifyAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def StartWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/StartWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PurgeWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def TerminateWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PauseWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PauseWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResumeWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def RaiseEventWorkflowAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def StartWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/StartWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PurgeWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def TerminateWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def PauseWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PauseWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ResumeWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def RaiseEventWorkflowBeta1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowBeta1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def Shutdown(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/Shutdown', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ShutdownRequest.SerializeToString, - google_dot_protobuf_dot_empty__pb2.Empty.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ScheduleJobAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ScheduleJobAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def GetJobAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetJobAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def DeleteJobAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/DeleteJobAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobRequest.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobResponse.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) - - @staticmethod - def ConverseAlpha1(request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None): - return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ConverseAlpha1', - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Request.SerializeToString, - dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Response.FromString, - options, channel_credentials, - insecure, call_credentials, compression, wait_for_ready, timeout, metadata) +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from dapr.proto.common.v1 import common_pb2 as dapr_dot_proto_dot_common_dot_v1_dot_common__pb2 +from dapr.proto.runtime.v1 import dapr_pb2 as dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2 +from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2 + + +class DaprStub(object): + """Dapr service provides APIs to user application to access Dapr building blocks. + """ + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.InvokeService = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/InvokeService', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeServiceRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, + ) + self.GetState = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetState', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateResponse.FromString, + ) + self.GetBulkState = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetBulkState', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateResponse.FromString, + ) + self.SaveState = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SaveState', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SaveStateRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.QueryStateAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/QueryStateAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateResponse.FromString, + ) + self.DeleteState = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/DeleteState', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteStateRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.DeleteBulkState = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/DeleteBulkState', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteBulkStateRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.ExecuteStateTransaction = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/ExecuteStateTransaction', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteStateTransactionRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.PublishEvent = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/PublishEvent', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PublishEventRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.BulkPublishEventAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/BulkPublishEventAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishResponse.FromString, + ) + self.SubscribeTopicEventsAlpha1 = channel.stream_stream( + '/dapr.proto.runtime.v1.Dapr/SubscribeTopicEventsAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsRequestAlpha1.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsResponseAlpha1.FromString, + ) + self.InvokeBinding = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/InvokeBinding', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingResponse.FromString, + ) + self.GetSecret = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetSecret', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretResponse.FromString, + ) + self.GetBulkSecret = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetBulkSecret', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretResponse.FromString, + ) + self.RegisterActorTimer = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/RegisterActorTimer', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorTimerRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.UnregisterActorTimer = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/UnregisterActorTimer', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorTimerRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.RegisterActorReminder = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/RegisterActorReminder', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorReminderRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.UnregisterActorReminder = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/UnregisterActorReminder', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorReminderRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.GetActorState = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetActorState', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateResponse.FromString, + ) + self.ExecuteActorStateTransaction = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/ExecuteActorStateTransaction', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteActorStateTransactionRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.InvokeActor = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/InvokeActor', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorResponse.FromString, + ) + self.GetConfigurationAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetConfigurationAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, + ) + self.GetConfiguration = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetConfiguration', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, + ) + self.SubscribeConfigurationAlpha1 = channel.unary_stream( + '/dapr.proto.runtime.v1.Dapr/SubscribeConfigurationAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, + ) + self.SubscribeConfiguration = channel.unary_stream( + '/dapr.proto.runtime.v1.Dapr/SubscribeConfiguration', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, + ) + self.UnsubscribeConfigurationAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfigurationAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, + ) + self.UnsubscribeConfiguration = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfiguration', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, + ) + self.TryLockAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/TryLockAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockResponse.FromString, + ) + self.UnlockAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/UnlockAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockResponse.FromString, + ) + self.EncryptAlpha1 = channel.stream_stream( + '/dapr.proto.runtime.v1.Dapr/EncryptAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptResponse.FromString, + ) + self.DecryptAlpha1 = channel.stream_stream( + '/dapr.proto.runtime.v1.Dapr/DecryptAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptResponse.FromString, + ) + self.GetMetadata = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetMetadata', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataResponse.FromString, + ) + self.SetMetadata = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SetMetadata', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SetMetadataRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.SubtleGetKeyAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleGetKeyAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyResponse.FromString, + ) + self.SubtleEncryptAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleEncryptAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptResponse.FromString, + ) + self.SubtleDecryptAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleDecryptAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptResponse.FromString, + ) + self.SubtleWrapKeyAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleWrapKeyAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyResponse.FromString, + ) + self.SubtleUnwrapKeyAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleUnwrapKeyAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyResponse.FromString, + ) + self.SubtleSignAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleSignAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignResponse.FromString, + ) + self.SubtleVerifyAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/SubtleVerifyAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyResponse.FromString, + ) + self.StartWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/StartWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, + ) + self.GetWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, + ) + self.PurgeWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.TerminateWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.PauseWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/PauseWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.ResumeWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.RaiseEventWorkflowAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.StartWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/StartWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, + ) + self.GetWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, + ) + self.PurgeWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.TerminateWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.PauseWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/PauseWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.ResumeWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.RaiseEventWorkflowBeta1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowBeta1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.Shutdown = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/Shutdown', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ShutdownRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + ) + self.ScheduleJobAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/ScheduleJobAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobResponse.FromString, + ) + self.GetJobAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/GetJobAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobResponse.FromString, + ) + self.DeleteJobAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/DeleteJobAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobRequest.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobResponse.FromString, + ) + self.ConverseAlpha1 = channel.unary_unary( + '/dapr.proto.runtime.v1.Dapr/ConverseAlpha1', + request_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Request.SerializeToString, + response_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Response.FromString, + ) + + +class DaprServicer(object): + """Dapr service provides APIs to user application to access Dapr building blocks. + """ + + def InvokeService(self, request, context): + """Invokes a method on a remote Dapr app. + Deprecated: Use proxy mode service invocation instead. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetState(self, request, context): + """Gets the state for a specific key. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetBulkState(self, request, context): + """Gets a bulk of state items for a list of keys + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SaveState(self, request, context): + """Saves the state for a specific key. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def QueryStateAlpha1(self, request, context): + """Queries the state. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteState(self, request, context): + """Deletes the state for a specific key. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteBulkState(self, request, context): + """Deletes a bulk of state items for a list of keys + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExecuteStateTransaction(self, request, context): + """Executes transactions for a specified store + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PublishEvent(self, request, context): + """Publishes events to the specific topic. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def BulkPublishEventAlpha1(self, request, context): + """Bulk Publishes multiple events to the specified topic. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubscribeTopicEventsAlpha1(self, request_iterator, context): + """SubscribeTopicEventsAlpha1 subscribes to a PubSub topic and receives topic + events from it. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def InvokeBinding(self, request, context): + """Invokes binding data to specific output bindings + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSecret(self, request, context): + """Gets secrets from secret stores. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetBulkSecret(self, request, context): + """Gets a bulk of secrets + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RegisterActorTimer(self, request, context): + """Register an actor timer. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UnregisterActorTimer(self, request, context): + """Unregister an actor timer. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RegisterActorReminder(self, request, context): + """Register an actor reminder. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UnregisterActorReminder(self, request, context): + """Unregister an actor reminder. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetActorState(self, request, context): + """Gets the state for a specific actor. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExecuteActorStateTransaction(self, request, context): + """Executes state transactions for a specified actor + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def InvokeActor(self, request, context): + """InvokeActor calls a method on an actor. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetConfigurationAlpha1(self, request, context): + """GetConfiguration gets configuration from configuration store. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetConfiguration(self, request, context): + """GetConfiguration gets configuration from configuration store. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubscribeConfigurationAlpha1(self, request, context): + """SubscribeConfiguration gets configuration from configuration store and subscribe the updates event by grpc stream + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubscribeConfiguration(self, request, context): + """SubscribeConfiguration gets configuration from configuration store and subscribe the updates event by grpc stream + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UnsubscribeConfigurationAlpha1(self, request, context): + """UnSubscribeConfiguration unsubscribe the subscription of configuration + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UnsubscribeConfiguration(self, request, context): + """UnSubscribeConfiguration unsubscribe the subscription of configuration + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def TryLockAlpha1(self, request, context): + """TryLockAlpha1 tries to get a lock with an expiry. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UnlockAlpha1(self, request, context): + """UnlockAlpha1 unlocks a lock. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EncryptAlpha1(self, request_iterator, context): + """EncryptAlpha1 encrypts a message using the Dapr encryption scheme and a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DecryptAlpha1(self, request_iterator, context): + """DecryptAlpha1 decrypts a message using the Dapr encryption scheme and a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetMetadata(self, request, context): + """Gets metadata of the sidecar + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetMetadata(self, request, context): + """Sets value in extended metadata of the sidecar + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleGetKeyAlpha1(self, request, context): + """SubtleGetKeyAlpha1 returns the public part of an asymmetric key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleEncryptAlpha1(self, request, context): + """SubtleEncryptAlpha1 encrypts a small message using a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleDecryptAlpha1(self, request, context): + """SubtleDecryptAlpha1 decrypts a small message using a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleWrapKeyAlpha1(self, request, context): + """SubtleWrapKeyAlpha1 wraps a key using a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleUnwrapKeyAlpha1(self, request, context): + """SubtleUnwrapKeyAlpha1 unwraps a key using a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleSignAlpha1(self, request, context): + """SubtleSignAlpha1 signs a message using a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SubtleVerifyAlpha1(self, request, context): + """SubtleVerifyAlpha1 verifies the signature of a message using a key stored in the vault. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartWorkflowAlpha1(self, request, context): + """Starts a new instance of a workflow + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetWorkflowAlpha1(self, request, context): + """Gets details about a started workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PurgeWorkflowAlpha1(self, request, context): + """Purge Workflow + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def TerminateWorkflowAlpha1(self, request, context): + """Terminates a running workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PauseWorkflowAlpha1(self, request, context): + """Pauses a running workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ResumeWorkflowAlpha1(self, request, context): + """Resumes a paused workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RaiseEventWorkflowAlpha1(self, request, context): + """Raise an event to a running workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StartWorkflowBeta1(self, request, context): + """Starts a new instance of a workflow + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetWorkflowBeta1(self, request, context): + """Gets details about a started workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PurgeWorkflowBeta1(self, request, context): + """Purge Workflow + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def TerminateWorkflowBeta1(self, request, context): + """Terminates a running workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PauseWorkflowBeta1(self, request, context): + """Pauses a running workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ResumeWorkflowBeta1(self, request, context): + """Resumes a paused workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def RaiseEventWorkflowBeta1(self, request, context): + """Raise an event to a running workflow instance + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Shutdown(self, request, context): + """Shutdown the sidecar + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ScheduleJobAlpha1(self, request, context): + """Create and schedule a job + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetJobAlpha1(self, request, context): + """Gets a scheduled job + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteJobAlpha1(self, request, context): + """Delete a job + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ConverseAlpha1(self, request, context): + """Converse with a LLM service + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_DaprServicer_to_server(servicer, server): + rpc_method_handlers = { + 'InvokeService': grpc.unary_unary_rpc_method_handler( + servicer.InvokeService, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeServiceRequest.FromString, + response_serializer=dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.SerializeToString, + ), + 'GetState': grpc.unary_unary_rpc_method_handler( + servicer.GetState, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateResponse.SerializeToString, + ), + 'GetBulkState': grpc.unary_unary_rpc_method_handler( + servicer.GetBulkState, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateResponse.SerializeToString, + ), + 'SaveState': grpc.unary_unary_rpc_method_handler( + servicer.SaveState, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SaveStateRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'QueryStateAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.QueryStateAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateResponse.SerializeToString, + ), + 'DeleteState': grpc.unary_unary_rpc_method_handler( + servicer.DeleteState, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteStateRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'DeleteBulkState': grpc.unary_unary_rpc_method_handler( + servicer.DeleteBulkState, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteBulkStateRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'ExecuteStateTransaction': grpc.unary_unary_rpc_method_handler( + servicer.ExecuteStateTransaction, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteStateTransactionRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'PublishEvent': grpc.unary_unary_rpc_method_handler( + servicer.PublishEvent, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PublishEventRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'BulkPublishEventAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.BulkPublishEventAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishResponse.SerializeToString, + ), + 'SubscribeTopicEventsAlpha1': grpc.stream_stream_rpc_method_handler( + servicer.SubscribeTopicEventsAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsRequestAlpha1.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsResponseAlpha1.SerializeToString, + ), + 'InvokeBinding': grpc.unary_unary_rpc_method_handler( + servicer.InvokeBinding, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingResponse.SerializeToString, + ), + 'GetSecret': grpc.unary_unary_rpc_method_handler( + servicer.GetSecret, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretResponse.SerializeToString, + ), + 'GetBulkSecret': grpc.unary_unary_rpc_method_handler( + servicer.GetBulkSecret, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretResponse.SerializeToString, + ), + 'RegisterActorTimer': grpc.unary_unary_rpc_method_handler( + servicer.RegisterActorTimer, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorTimerRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'UnregisterActorTimer': grpc.unary_unary_rpc_method_handler( + servicer.UnregisterActorTimer, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorTimerRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'RegisterActorReminder': grpc.unary_unary_rpc_method_handler( + servicer.RegisterActorReminder, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorReminderRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'UnregisterActorReminder': grpc.unary_unary_rpc_method_handler( + servicer.UnregisterActorReminder, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorReminderRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'GetActorState': grpc.unary_unary_rpc_method_handler( + servicer.GetActorState, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateResponse.SerializeToString, + ), + 'ExecuteActorStateTransaction': grpc.unary_unary_rpc_method_handler( + servicer.ExecuteActorStateTransaction, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteActorStateTransactionRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'InvokeActor': grpc.unary_unary_rpc_method_handler( + servicer.InvokeActor, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorResponse.SerializeToString, + ), + 'GetConfigurationAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.GetConfigurationAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.SerializeToString, + ), + 'GetConfiguration': grpc.unary_unary_rpc_method_handler( + servicer.GetConfiguration, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.SerializeToString, + ), + 'SubscribeConfigurationAlpha1': grpc.unary_stream_rpc_method_handler( + servicer.SubscribeConfigurationAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.SerializeToString, + ), + 'SubscribeConfiguration': grpc.unary_stream_rpc_method_handler( + servicer.SubscribeConfiguration, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.SerializeToString, + ), + 'UnsubscribeConfigurationAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.UnsubscribeConfigurationAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.SerializeToString, + ), + 'UnsubscribeConfiguration': grpc.unary_unary_rpc_method_handler( + servicer.UnsubscribeConfiguration, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.SerializeToString, + ), + 'TryLockAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.TryLockAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockResponse.SerializeToString, + ), + 'UnlockAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.UnlockAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockResponse.SerializeToString, + ), + 'EncryptAlpha1': grpc.stream_stream_rpc_method_handler( + servicer.EncryptAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptResponse.SerializeToString, + ), + 'DecryptAlpha1': grpc.stream_stream_rpc_method_handler( + servicer.DecryptAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptResponse.SerializeToString, + ), + 'GetMetadata': grpc.unary_unary_rpc_method_handler( + servicer.GetMetadata, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataResponse.SerializeToString, + ), + 'SetMetadata': grpc.unary_unary_rpc_method_handler( + servicer.SetMetadata, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SetMetadataRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'SubtleGetKeyAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleGetKeyAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyResponse.SerializeToString, + ), + 'SubtleEncryptAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleEncryptAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptResponse.SerializeToString, + ), + 'SubtleDecryptAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleDecryptAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptResponse.SerializeToString, + ), + 'SubtleWrapKeyAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleWrapKeyAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyResponse.SerializeToString, + ), + 'SubtleUnwrapKeyAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleUnwrapKeyAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyResponse.SerializeToString, + ), + 'SubtleSignAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleSignAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignResponse.SerializeToString, + ), + 'SubtleVerifyAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.SubtleVerifyAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyResponse.SerializeToString, + ), + 'StartWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.StartWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.SerializeToString, + ), + 'GetWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.GetWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.SerializeToString, + ), + 'PurgeWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.PurgeWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'TerminateWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.TerminateWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'PauseWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.PauseWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'ResumeWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.ResumeWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'RaiseEventWorkflowAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.RaiseEventWorkflowAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'StartWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.StartWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.SerializeToString, + ), + 'GetWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.GetWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.SerializeToString, + ), + 'PurgeWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.PurgeWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'TerminateWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.TerminateWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'PauseWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.PauseWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'ResumeWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.ResumeWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'RaiseEventWorkflowBeta1': grpc.unary_unary_rpc_method_handler( + servicer.RaiseEventWorkflowBeta1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'Shutdown': grpc.unary_unary_rpc_method_handler( + servicer.Shutdown, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ShutdownRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + 'ScheduleJobAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.ScheduleJobAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobResponse.SerializeToString, + ), + 'GetJobAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.GetJobAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobResponse.SerializeToString, + ), + 'DeleteJobAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.DeleteJobAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobRequest.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobResponse.SerializeToString, + ), + 'ConverseAlpha1': grpc.unary_unary_rpc_method_handler( + servicer.ConverseAlpha1, + request_deserializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Request.FromString, + response_serializer=dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Response.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'dapr.proto.runtime.v1.Dapr', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class Dapr(object): + """Dapr service provides APIs to user application to access Dapr building blocks. + """ + + @staticmethod + def InvokeService(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/InvokeService', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeServiceRequest.SerializeToString, + dapr_dot_proto_dot_common_dot_v1_dot_common__pb2.InvokeResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetState', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetStateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetBulkState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetBulkState', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkStateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SaveState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SaveState', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SaveStateRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def QueryStateAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/QueryStateAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.QueryStateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeleteState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/DeleteState', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteStateRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeleteBulkState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/DeleteBulkState', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteBulkStateRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ExecuteStateTransaction(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ExecuteStateTransaction', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteStateTransactionRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PublishEvent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PublishEvent', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PublishEventRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def BulkPublishEventAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/BulkPublishEventAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.BulkPublishResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubscribeTopicEventsAlpha1(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream(request_iterator, target, '/dapr.proto.runtime.v1.Dapr/SubscribeTopicEventsAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsRequestAlpha1.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeTopicEventsResponseAlpha1.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def InvokeBinding(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/InvokeBinding', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeBindingResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetSecret(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetSecret', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetSecretResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetBulkSecret(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetBulkSecret', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetBulkSecretResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def RegisterActorTimer(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RegisterActorTimer', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorTimerRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UnregisterActorTimer(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnregisterActorTimer', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorTimerRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def RegisterActorReminder(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RegisterActorReminder', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RegisterActorReminderRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UnregisterActorReminder(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnregisterActorReminder', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnregisterActorReminderRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetActorState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetActorState', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetActorStateResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ExecuteActorStateTransaction(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ExecuteActorStateTransaction', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ExecuteActorStateTransactionRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def InvokeActor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/InvokeActor', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.InvokeActorResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetConfigurationAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetConfigurationAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetConfiguration(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetConfiguration', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetConfigurationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubscribeConfigurationAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream(request, target, '/dapr.proto.runtime.v1.Dapr/SubscribeConfigurationAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubscribeConfiguration(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream(request, target, '/dapr.proto.runtime.v1.Dapr/SubscribeConfiguration', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubscribeConfigurationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UnsubscribeConfigurationAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfigurationAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UnsubscribeConfiguration(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnsubscribeConfiguration', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnsubscribeConfigurationResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def TryLockAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/TryLockAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TryLockResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UnlockAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/UnlockAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.UnlockResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def EncryptAlpha1(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream(request_iterator, target, '/dapr.proto.runtime.v1.Dapr/EncryptAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.EncryptResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DecryptAlpha1(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream(request_iterator, target, '/dapr.proto.runtime.v1.Dapr/DecryptAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DecryptResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetMetadata(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetMetadata', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetMetadataResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SetMetadata(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SetMetadata', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SetMetadataRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleGetKeyAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleGetKeyAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleGetKeyResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleEncryptAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleEncryptAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleEncryptResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleDecryptAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleDecryptAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleDecryptResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleWrapKeyAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleWrapKeyAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleWrapKeyResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleUnwrapKeyAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleUnwrapKeyAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleUnwrapKeyResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleSignAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleSignAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleSignResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SubtleVerifyAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/SubtleVerifyAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.SubtleVerifyResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def StartWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/StartWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PurgeWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def TerminateWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PauseWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PauseWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ResumeWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def RaiseEventWorkflowAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def StartWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/StartWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.StartWorkflowResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetWorkflowResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PurgeWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PurgeWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PurgeWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def TerminateWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/TerminateWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.TerminateWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def PauseWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/PauseWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.PauseWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ResumeWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ResumeWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ResumeWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def RaiseEventWorkflowBeta1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/RaiseEventWorkflowBeta1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.RaiseEventWorkflowRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def Shutdown(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/Shutdown', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ShutdownRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ScheduleJobAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ScheduleJobAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ScheduleJobResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetJobAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/GetJobAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.GetJobResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def DeleteJobAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/DeleteJobAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobRequest.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.DeleteJobResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def ConverseAlpha1(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/dapr.proto.runtime.v1.Dapr/ConverseAlpha1', + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Request.SerializeToString, + dapr_dot_proto_dot_runtime_dot_v1_dot_dapr__pb2.ConversationAlpha1Response.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/dapr/serializers/__init__.py b/dapr/serializers/__init__.py index ac72d1c22..4ee913064 100644 --- a/dapr/serializers/__init__.py +++ b/dapr/serializers/__init__.py @@ -1,19 +1,19 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.serializers.base import Serializer -from dapr.serializers.json import DefaultJSONSerializer - -__all__ = ['Serializer', 'DefaultJSONSerializer'] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.serializers.base import Serializer +from dapr.serializers.json import DefaultJSONSerializer + +__all__ = ['Serializer', 'DefaultJSONSerializer'] diff --git a/dapr/serializers/base.py b/dapr/serializers/base.py index 5ff1d9e8b..995dc8601 100644 --- a/dapr/serializers/base.py +++ b/dapr/serializers/base.py @@ -1,36 +1,36 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from abc import ABC, abstractmethod -from typing import Any, Callable, Optional, Type - - -class Serializer(ABC): - """Serializer base class.""" - - @abstractmethod - def serialize( - self, obj: object, custom_hook: Optional[Callable[[object], bytes]] = None - ) -> bytes: - ... - - @abstractmethod - def deserialize( - self, - data: bytes, - data_type: Optional[Type] = object, - custom_hook: Optional[Callable[[bytes], object]] = None, - ) -> Any: - ... +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from abc import ABC, abstractmethod +from typing import Any, Callable, Optional, Type + + +class Serializer(ABC): + """Serializer base class.""" + + @abstractmethod + def serialize( + self, obj: object, custom_hook: Optional[Callable[[object], bytes]] = None + ) -> bytes: + ... + + @abstractmethod + def deserialize( + self, + data: bytes, + data_type: Optional[Type] = object, + custom_hook: Optional[Callable[[bytes], object]] = None, + ) -> Any: + ... diff --git a/dapr/serializers/json.py b/dapr/serializers/json.py index 4e9665187..02c899c3f 100644 --- a/dapr/serializers/json.py +++ b/dapr/serializers/json.py @@ -1,109 +1,109 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import base64 -import re -import datetime -import json - -from typing import Any, Callable, Optional, Type -from dateutil import parser - -from dapr.serializers.base import Serializer -from dapr.serializers.util import ( - convert_from_dapr_duration, - convert_to_dapr_duration, - DAPR_DURATION_PARSER, -) - - -class DefaultJSONSerializer(Serializer): - def __init__(self, ensure_ascii: bool = True) -> None: - self.ensure_ascii = ensure_ascii - - def serialize( - self, obj: object, custom_hook: Optional[Callable[[object], bytes]] = None - ) -> bytes: - dict_obj = obj - - # importing this from top scope creates a circular import - from dapr.actor.runtime.config import ActorRuntimeConfig - - if callable(custom_hook): - dict_obj = custom_hook(obj) - elif isinstance(obj, bytes): - dict_obj = base64.b64encode(obj).decode('utf-8') - elif isinstance(obj, ActorRuntimeConfig): - dict_obj = obj.as_dict() - - serialized = json.dumps( - dict_obj, cls=DaprJSONEncoder, separators=(',', ':'), ensure_ascii=self.ensure_ascii - ) - - return serialized.encode('utf-8') - - def deserialize( - self, - data: bytes, - data_type: Optional[Type] = object, - custom_hook: Optional[Callable[[bytes], object]] = None, - ) -> Any: - if not isinstance(data, (str, bytes)): - raise ValueError('data must be str or bytes types') - - obj = json.loads(data, cls=DaprJSONDecoder) - - return custom_hook(obj) if callable(custom_hook) else obj - - -class DaprJSONEncoder(json.JSONEncoder): - def default(self, obj): - # See "Date Time String Format" in the ECMA-262 specification. - if isinstance(obj, datetime.datetime): - r = obj.isoformat() - if obj.microsecond: - r = r[:23] + r[26:] - if r.endswith('+00:00'): - r = r[:-6] + 'Z' - return r - elif isinstance(obj, datetime.date): - return obj.isoformat() - elif isinstance(obj, datetime.timedelta): - return convert_to_dapr_duration(obj) - elif isinstance(obj, bytes): - return base64.b64encode(obj).decode('utf-8') - else: - return json.JSONEncoder.default(self, obj) - - -class DaprJSONDecoder(json.JSONDecoder): - # TODO: improve regex - datetime_regex = re.compile(r'(\d{4}[-/]\d{2}[-/]\d{2})') - - def __init__(self, *args, **kwargs): - json.JSONDecoder.__init__(self, *args, **kwargs) - self.parse_string = DaprJSONDecoder.custom_scanstring - self.scan_once = json.scanner.py_make_scanner(self) # type: ignore - - @classmethod - def custom_scanstring(cls, s, end, strict=True): - (s, end) = json.decoder.scanstring(s, end, strict) # type: ignore - if cls.datetime_regex.match(s): - return (parser.parse(s), end) - - duration = DAPR_DURATION_PARSER.match(s) - if duration is not None and duration.lastindex is not None: - return (convert_from_dapr_duration(s), end) - return (s, end) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import base64 +import re +import datetime +import json + +from typing import Any, Callable, Optional, Type +from dateutil import parser + +from dapr.serializers.base import Serializer +from dapr.serializers.util import ( + convert_from_dapr_duration, + convert_to_dapr_duration, + DAPR_DURATION_PARSER, +) + + +class DefaultJSONSerializer(Serializer): + def __init__(self, ensure_ascii: bool = True) -> None: + self.ensure_ascii = ensure_ascii + + def serialize( + self, obj: object, custom_hook: Optional[Callable[[object], bytes]] = None + ) -> bytes: + dict_obj = obj + + # importing this from top scope creates a circular import + from dapr.actor.runtime.config import ActorRuntimeConfig + + if callable(custom_hook): + dict_obj = custom_hook(obj) + elif isinstance(obj, bytes): + dict_obj = base64.b64encode(obj).decode('utf-8') + elif isinstance(obj, ActorRuntimeConfig): + dict_obj = obj.as_dict() + + serialized = json.dumps( + dict_obj, cls=DaprJSONEncoder, separators=(',', ':'), ensure_ascii=self.ensure_ascii + ) + + return serialized.encode('utf-8') + + def deserialize( + self, + data: bytes, + data_type: Optional[Type] = object, + custom_hook: Optional[Callable[[bytes], object]] = None, + ) -> Any: + if not isinstance(data, (str, bytes)): + raise ValueError('data must be str or bytes types') + + obj = json.loads(data, cls=DaprJSONDecoder) + + return custom_hook(obj) if callable(custom_hook) else obj + + +class DaprJSONEncoder(json.JSONEncoder): + def default(self, obj): + # See "Date Time String Format" in the ECMA-262 specification. + if isinstance(obj, datetime.datetime): + r = obj.isoformat() + if obj.microsecond: + r = r[:23] + r[26:] + if r.endswith('+00:00'): + r = r[:-6] + 'Z' + return r + elif isinstance(obj, datetime.date): + return obj.isoformat() + elif isinstance(obj, datetime.timedelta): + return convert_to_dapr_duration(obj) + elif isinstance(obj, bytes): + return base64.b64encode(obj).decode('utf-8') + else: + return json.JSONEncoder.default(self, obj) + + +class DaprJSONDecoder(json.JSONDecoder): + # TODO: improve regex + datetime_regex = re.compile(r'(\d{4}[-/]\d{2}[-/]\d{2})') + + def __init__(self, *args, **kwargs): + json.JSONDecoder.__init__(self, *args, **kwargs) + self.parse_string = DaprJSONDecoder.custom_scanstring + self.scan_once = json.scanner.py_make_scanner(self) # type: ignore + + @classmethod + def custom_scanstring(cls, s, end, strict=True): + (s, end) = json.decoder.scanstring(s, end, strict) # type: ignore + if cls.datetime_regex.match(s): + return (parser.parse(s), end) + + duration = DAPR_DURATION_PARSER.match(s) + if duration is not None and duration.lastindex is not None: + return (convert_from_dapr_duration(s), end) + return (s, end) diff --git a/dapr/serializers/util.py b/dapr/serializers/util.py index 522ad03d3..5b1d91b67 100644 --- a/dapr/serializers/util.py +++ b/dapr/serializers/util.py @@ -1,77 +1,77 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import re -from datetime import timedelta - -# Regex to parse Go Duration datatype, e.g. 4h15m50s123ms345μs -DAPR_DURATION_PARSER = re.compile( - r'((?P\d+)h)?((?P\d+)m)?((?P\d+)s)?((?P\d+)ms)?((?P\d+)(μs|us))?$' -) # noqa: E501 - - -def convert_from_dapr_duration(duration: str) -> timedelta: - """Converts Dapr duration format (Go duration format) to datetime.timedelta. - - Args: - duration (str): Dapr duration string. - - Returns: - :obj:`datetime.delta`: the python datetime object. - """ - - matched = DAPR_DURATION_PARSER.match(duration) - if not matched or matched.lastindex == 0: - raise ValueError(f"Invalid Dapr Duration format: '{duration}'") - - days = 0.0 - hours = 0.0 - - if matched.group('hours') is not None: - days, hours = divmod(float(matched.group('hours')), 24) - mins = 0.0 if not matched.group('mins') else float(matched.group('mins')) - seconds = 0.0 if not matched.group('seconds') else float(matched.group('seconds')) - milliseconds = ( - 0.0 if not matched.group('milliseconds') else float(matched.group('milliseconds')) - ) - microseconds = ( - 0.0 if not matched.group('microseconds') else float(matched.group('microseconds')) - ) - - return timedelta( - days=days, - hours=hours, - minutes=mins, - seconds=seconds, - milliseconds=milliseconds, - microseconds=microseconds, - ) - - -def convert_to_dapr_duration(td: timedelta) -> str: - """Converts date.timedelta to Dapr duration format. - - Args: - td (datetime.timedelta): python datetime object. - - Returns: - str: dapr duration format string. - """ - - total_minutes, seconds = divmod(td.total_seconds(), 60.0) - milliseconds, microseconds = divmod(td.microseconds, 1000.0) - hours, mins = divmod(total_minutes, 60.0) - - return f'{hours:.0f}h{mins:.0f}m{seconds:.0f}s{milliseconds:.0f}ms{microseconds:.0f}μs' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import re +from datetime import timedelta + +# Regex to parse Go Duration datatype, e.g. 4h15m50s123ms345μs +DAPR_DURATION_PARSER = re.compile( + r'((?P\d+)h)?((?P\d+)m)?((?P\d+)s)?((?P\d+)ms)?((?P\d+)(μs|us))?$' +) # noqa: E501 + + +def convert_from_dapr_duration(duration: str) -> timedelta: + """Converts Dapr duration format (Go duration format) to datetime.timedelta. + + Args: + duration (str): Dapr duration string. + + Returns: + :obj:`datetime.delta`: the python datetime object. + """ + + matched = DAPR_DURATION_PARSER.match(duration) + if not matched or matched.lastindex == 0: + raise ValueError(f"Invalid Dapr Duration format: '{duration}'") + + days = 0.0 + hours = 0.0 + + if matched.group('hours') is not None: + days, hours = divmod(float(matched.group('hours')), 24) + mins = 0.0 if not matched.group('mins') else float(matched.group('mins')) + seconds = 0.0 if not matched.group('seconds') else float(matched.group('seconds')) + milliseconds = ( + 0.0 if not matched.group('milliseconds') else float(matched.group('milliseconds')) + ) + microseconds = ( + 0.0 if not matched.group('microseconds') else float(matched.group('microseconds')) + ) + + return timedelta( + days=days, + hours=hours, + minutes=mins, + seconds=seconds, + milliseconds=milliseconds, + microseconds=microseconds, + ) + + +def convert_to_dapr_duration(td: timedelta) -> str: + """Converts date.timedelta to Dapr duration format. + + Args: + td (datetime.timedelta): python datetime object. + + Returns: + str: dapr duration format string. + """ + + total_minutes, seconds = divmod(td.total_seconds(), 60.0) + milliseconds, microseconds = divmod(td.microseconds, 1000.0) + hours, mins = divmod(total_minutes, 60.0) + + return f'{hours:.0f}h{mins:.0f}m{seconds:.0f}s{milliseconds:.0f}ms{microseconds:.0f}μs' diff --git a/dapr/version/__init__.py b/dapr/version/__init__.py index 3eb160812..fbcd58b65 100644 --- a/dapr/version/__init__.py +++ b/dapr/version/__init__.py @@ -1,3 +1,3 @@ -from dapr.version.version import __version__ - -__all__ = ['__version__'] +from dapr.version.version import __version__ + +__all__ = ['__version__'] diff --git a/dapr/version/version.py b/dapr/version/version.py index 287c4a57c..cc71c630a 100644 --- a/dapr/version/version.py +++ b/dapr/version/version.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -__version__ = '1.14.0rc1.dev' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +__version__ = '1.14.0rc1.dev' diff --git a/daprdocs/README.md b/daprdocs/README.md index 5213ae214..499536370 100644 --- a/daprdocs/README.md +++ b/daprdocs/README.md @@ -1,25 +1,25 @@ -# Dapr Python SDK documentation - -This page covers how the documentation is structured for the Dapr Python SDK. - -## Dapr Docs - -All Dapr documentation is hosted at [docs.dapr.io](https://docs.dapr.io), including the docs for the [Python SDK](https://docs.dapr.io/developing-applications/sdks/python/). Head over there if you want to read the docs. - -### Python SDK docs source - -Although the docs site code and content is in the [docs repo](https://github.com/dapr/docs), the Python SDK content and images are within the `content` and `static` directories, respectively. - -This allows separation of roles and expertise between maintainers, and makes it easy to find the docs files you are looking for. - -## Writing Python SDK docs - -To get up and running to write Python SDK docs, visit the [docs repo](https://github.com/dapr/docs) to initialize your environment. It will clone both the docs repo and this repo, so you can make changes and see it rendered within the site instantly, as well as commit and PR into this repo. - -Make sure to read the [docs contributing guide](https://docs.dapr.io/contributing/contributing-docs/) for information on style/semantics/etc. - -## Docs architecture - -The docs site is built on [Hugo](https://gohugo.io), which lives in the docs repo. This repo is setup as a git submodule so that when the repo is cloned and initialized, the python repo, along with the docs, are cloned as well. - +# Dapr Python SDK documentation + +This page covers how the documentation is structured for the Dapr Python SDK. + +## Dapr Docs + +All Dapr documentation is hosted at [docs.dapr.io](https://docs.dapr.io), including the docs for the [Python SDK](https://docs.dapr.io/developing-applications/sdks/python/). Head over there if you want to read the docs. + +### Python SDK docs source + +Although the docs site code and content is in the [docs repo](https://github.com/dapr/docs), the Python SDK content and images are within the `content` and `static` directories, respectively. + +This allows separation of roles and expertise between maintainers, and makes it easy to find the docs files you are looking for. + +## Writing Python SDK docs + +To get up and running to write Python SDK docs, visit the [docs repo](https://github.com/dapr/docs) to initialize your environment. It will clone both the docs repo and this repo, so you can make changes and see it rendered within the site instantly, as well as commit and PR into this repo. + +Make sure to read the [docs contributing guide](https://docs.dapr.io/contributing/contributing-docs/) for information on style/semantics/etc. + +## Docs architecture + +The docs site is built on [Hugo](https://gohugo.io), which lives in the docs repo. This repo is setup as a git submodule so that when the repo is cloned and initialized, the python repo, along with the docs, are cloned as well. + Then, in the Hugo configuration file, the `daprdocs/content` and `daprdocs/static` directories are redirected to the `daprdocs/developing-applications/sdks/python` and `static/python` directories, respectively. Thus, all the content within this repo is folded into the main docs site. \ No newline at end of file diff --git a/daprdocs/content/en/python-sdk-contributing/python-contributing.md b/daprdocs/content/en/python-sdk-contributing/python-contributing.md index 24ed10efe..c7f9c4962 100644 --- a/daprdocs/content/en/python-sdk-contributing/python-contributing.md +++ b/daprdocs/content/en/python-sdk-contributing/python-contributing.md @@ -1,27 +1,27 @@ ---- -type: docs -title: "Contributing to the Python SDK" -linkTitle: "Python SDK" -weight: 3000 -description: Guidelines for contributing to the Dapr Python SDK ---- - -When contributing to the [Python SDK](https://github.com/dapr/python-sdk) the following rules and best-practices should be followed. - -## Examples - -The `examples` directory contains code samples for users to run to try out specific functionality of the various Python SDK packages and extensions. When writing new and updated samples keep in mind: - -- All examples should be runnable on Windows, Linux, and MacOS. While Python code is consistent among operating systems, any pre/post example commands should provide options through [codetabs]({{< ref "contributing-docs.md#tabbed-content" >}}) -- Contain steps to download/install any required pre-requisites. Someone coming in with a fresh OS install should be able to start on the example and complete it without an error. Links to external download pages are fine. - -## Docs - -The `daprdocs` directory contains the markdown files that are rendered into the [Dapr Docs](https://docs.dapr.io) website. When the documentation website is built this repo is cloned and configured so that its contents are rendered with the docs content. When writing docs keep in mind: - - - All rules in the [docs guide]({{< ref contributing-docs.md >}}) should be followed in addition to these. - - All files and directories should be prefixed with `python-` to ensure all file/directory names are globally unique across all Dapr documentation. - -## Github Dapr Bot Commands - +--- +type: docs +title: "Contributing to the Python SDK" +linkTitle: "Python SDK" +weight: 3000 +description: Guidelines for contributing to the Dapr Python SDK +--- + +When contributing to the [Python SDK](https://github.com/dapr/python-sdk) the following rules and best-practices should be followed. + +## Examples + +The `examples` directory contains code samples for users to run to try out specific functionality of the various Python SDK packages and extensions. When writing new and updated samples keep in mind: + +- All examples should be runnable on Windows, Linux, and MacOS. While Python code is consistent among operating systems, any pre/post example commands should provide options through [codetabs]({{< ref "contributing-docs.md#tabbed-content" >}}) +- Contain steps to download/install any required pre-requisites. Someone coming in with a fresh OS install should be able to start on the example and complete it without an error. Links to external download pages are fine. + +## Docs + +The `daprdocs` directory contains the markdown files that are rendered into the [Dapr Docs](https://docs.dapr.io) website. When the documentation website is built this repo is cloned and configured so that its contents are rendered with the docs content. When writing docs keep in mind: + + - All rules in the [docs guide]({{< ref contributing-docs.md >}}) should be followed in addition to these. + - All files and directories should be prefixed with `python-` to ensure all file/directory names are globally unique across all Dapr documentation. + +## Github Dapr Bot Commands + Checkout the [daprbot documentation](https://docs.dapr.io/contributing/daprbot/) for Github commands you can run in this repo for common tasks. For example, you can run the `/assign` (as a comment on an issue) to assign issues to a user or group of users. \ No newline at end of file diff --git a/daprdocs/content/en/python-sdk-docs/_index.md b/daprdocs/content/en/python-sdk-docs/_index.md index b098c21d1..a7a1018ea 100644 --- a/daprdocs/content/en/python-sdk-docs/_index.md +++ b/daprdocs/content/en/python-sdk-docs/_index.md @@ -1,150 +1,150 @@ ---- -type: docs -title: "Dapr Python SDK" -linkTitle: "Python" -weight: 1000 -description: Python SDK packages for developing Dapr applications -no_list: true -cascade: - github_repo: https://github.com/dapr/python-sdk - github_subdir: daprdocs/content/en/python-sdk-docs - path_base_for_github_subdir: content/en/developing-applications/sdks/python/ - github_branch: master ---- - -Dapr offers a variety of subpackages to help with the development of Python applications. Using them you can create Python clients, servers, and virtual actors with Dapr. - -## Prerequisites - -- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed -- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) -- [Python 3.8+](https://www.python.org/downloads/) installed - -## Installation - -To get started with the Python SDK, install the main Dapr Python SDK package. - -{{< tabs Stable Development>}} - -{{% codetab %}} - -```bash -pip install dapr -``` -{{% /codetab %}} - -{{% codetab %}} - -> **Note:** The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK before installing the dapr-dev package. - -```bash -pip install dapr-dev -``` - -{{% /codetab %}} - -{{< /tabs >}} - - -## Available subpackages - -### SDK imports - -Python SDK imports are subpackages included with the main SDK install, but need to be imported when used. The most common imports provided by the Dapr Python SDK are: - -
-
-
-
Client
-

Write Python applications to interact with a Dapr sidecar and other Dapr applications, including stateful virtual actors in Python

- -
-
-
-
-
Actors
-

Create and interact with Dapr's Actor framework.

- -
-
-
- -Learn more about _all_ of the [available Dapr Python SDK imports](https://github.com/dapr/python-sdk/tree/master/dapr). - -### SDK extensions - -SDK extensions mainly work as utilities for receiving pub/sub events, programatically creating pub/sub subscriptions, and handling input binding events. While you can acheive all of these tasks without an extension, using a Python SDK extension proves convenient. - -
-
-
-
gRPC
-

Create Dapr services with the gRPC server extension.

- -
-
-
-
-
FastAPI
-

Integrate with Dapr Python virtual actors and pub/sub using the Dapr FastAPI extension.

- -
-
-
-
-
Flask
-

Integrate with Dapr Python virtual actors using the Dapr Flask extension.

- -
-
-
-
-
Workflow
-

Author workflows that work with other Dapr APIs in Python.

- -
-
-
- -Learn more about [the Dapr Python SDK extensions](https://github.com/dapr/python-sdk/tree/master/ext). - -## Try it out - -Clone the Python SDK repo. - -```bash -git clone https://github.com/dapr/python-sdk.git -``` - -Walk through the Python quickstarts, tutorials, and examples to see Dapr in action: - -| SDK samples | Description | -| ----------- | ----------- | -| [Quickstarts]({{< ref quickstarts >}}) | Experience Dapr's API building blocks in just a few minutes using the Python SDK. | -| [SDK samples](https://github.com/dapr/python-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. | -| [Bindings tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/bindings) | See how Dapr Python SDK works alongside other Dapr SDKs to enable bindings. | -| [Distributed Calculator tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/distributed-calculator/python) | Use the Dapr Python SDK to handle method invocation and state persistent capabilities. | -| [Hello World tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/hello-world) | Learn how to get Dapr up and running locally on your machine with the Python SDK. | -| [Hello Kubernetes tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/hello-kubernetes) | Get up and running with the Dapr Python SDK in a Kubernetes cluster. | -| [Observability tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/observability) | Explore Dapr's metric collection, tracing, logging and health check capabilities using the Python SDK. | -| [Pub/sub tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/pub-sub) | See how Dapr Python SDK works alongside other Dapr SDKs to enable pub/sub applications. | - - -## More information - -
-
-
-
Serialization
-

Learn more about serialization in Dapr SDKs.

- -
-
-
-
-
PyPI
-

Python Package Index

- -
-
-
+--- +type: docs +title: "Dapr Python SDK" +linkTitle: "Python" +weight: 1000 +description: Python SDK packages for developing Dapr applications +no_list: true +cascade: + github_repo: https://github.com/dapr/python-sdk + github_subdir: daprdocs/content/en/python-sdk-docs + path_base_for_github_subdir: content/en/developing-applications/sdks/python/ + github_branch: master +--- + +Dapr offers a variety of subpackages to help with the development of Python applications. Using them you can create Python clients, servers, and virtual actors with Dapr. + +## Prerequisites + +- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed +- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) +- [Python 3.8+](https://www.python.org/downloads/) installed + +## Installation + +To get started with the Python SDK, install the main Dapr Python SDK package. + +{{< tabs Stable Development>}} + +{{% codetab %}} + +```bash +pip install dapr +``` +{{% /codetab %}} + +{{% codetab %}} + +> **Note:** The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK before installing the dapr-dev package. + +```bash +pip install dapr-dev +``` + +{{% /codetab %}} + +{{< /tabs >}} + + +## Available subpackages + +### SDK imports + +Python SDK imports are subpackages included with the main SDK install, but need to be imported when used. The most common imports provided by the Dapr Python SDK are: + +
+
+
+
Client
+

Write Python applications to interact with a Dapr sidecar and other Dapr applications, including stateful virtual actors in Python

+ +
+
+
+
+
Actors
+

Create and interact with Dapr's Actor framework.

+ +
+
+
+ +Learn more about _all_ of the [available Dapr Python SDK imports](https://github.com/dapr/python-sdk/tree/master/dapr). + +### SDK extensions + +SDK extensions mainly work as utilities for receiving pub/sub events, programatically creating pub/sub subscriptions, and handling input binding events. While you can acheive all of these tasks without an extension, using a Python SDK extension proves convenient. + +
+
+
+
gRPC
+

Create Dapr services with the gRPC server extension.

+ +
+
+
+
+
FastAPI
+

Integrate with Dapr Python virtual actors and pub/sub using the Dapr FastAPI extension.

+ +
+
+
+
+
Flask
+

Integrate with Dapr Python virtual actors using the Dapr Flask extension.

+ +
+
+
+
+
Workflow
+

Author workflows that work with other Dapr APIs in Python.

+ +
+
+
+ +Learn more about [the Dapr Python SDK extensions](https://github.com/dapr/python-sdk/tree/master/ext). + +## Try it out + +Clone the Python SDK repo. + +```bash +git clone https://github.com/dapr/python-sdk.git +``` + +Walk through the Python quickstarts, tutorials, and examples to see Dapr in action: + +| SDK samples | Description | +| ----------- | ----------- | +| [Quickstarts]({{< ref quickstarts >}}) | Experience Dapr's API building blocks in just a few minutes using the Python SDK. | +| [SDK samples](https://github.com/dapr/python-sdk/tree/master/examples) | Clone the SDK repo to try out some examples and get started. | +| [Bindings tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/bindings) | See how Dapr Python SDK works alongside other Dapr SDKs to enable bindings. | +| [Distributed Calculator tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/distributed-calculator/python) | Use the Dapr Python SDK to handle method invocation and state persistent capabilities. | +| [Hello World tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/hello-world) | Learn how to get Dapr up and running locally on your machine with the Python SDK. | +| [Hello Kubernetes tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/hello-kubernetes) | Get up and running with the Dapr Python SDK in a Kubernetes cluster. | +| [Observability tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/observability) | Explore Dapr's metric collection, tracing, logging and health check capabilities using the Python SDK. | +| [Pub/sub tutorial](https://github.com/dapr/quickstarts/tree/master/tutorials/pub-sub) | See how Dapr Python SDK works alongside other Dapr SDKs to enable pub/sub applications. | + + +## More information + +
+
+
+
Serialization
+

Learn more about serialization in Dapr SDKs.

+ +
+
+
+
+
PyPI
+

Python Package Index

+ +
+
+
diff --git a/daprdocs/content/en/python-sdk-docs/python-client.md b/daprdocs/content/en/python-sdk-docs/python-client.md index b4e92a9be..907571b18 100644 --- a/daprdocs/content/en/python-sdk-docs/python-client.md +++ b/daprdocs/content/en/python-sdk-docs/python-client.md @@ -1,598 +1,598 @@ ---- -type: docs -title: "Getting started with the Dapr client Python SDK" -linkTitle: "Client" -weight: 10000 -description: How to get up and running with the Dapr Python SDK ---- - -The Dapr client package allows you to interact with other Dapr applications from a Python application. - -{{% alert title="Note" color="primary" %}} - If you haven't already, [try out one of the quickstarts]({{< ref quickstarts >}}) for a quick walk-through on how to use the Dapr Python SDK with an API building block. - -{{% /alert %}} - -## Prerequisites - -[Install the Dapr Python package]({{< ref "python#installation" >}}) before getting started. - -## Import the client package - -The `dapr` package contains the `DaprClient`, which is used to create and use a client. - -```python -from dapr.clients import DaprClient -``` - -## Initialising the client -You can initialise a Dapr client in multiple ways: - -#### Default values: -When you initialise the client without any parameters it will use the default values for a Dapr -sidecar instance (`127.0.0.1:50001`). -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - # use the client -``` - -#### Specifying an endpoint on initialisation: -When passed as an argument in the constructor, the gRPC endpoint takes precedence over any -configuration or environment variable. - -```python -from dapr.clients import DaprClient - -with DaprClient("mydomain:50051?tls=true") as d: - # use the client -``` - -#### Configuration options: - -##### Dapr Sidecar Endpoints -You can use the standardised `DAPR_GRPC_ENDPOINT` environment variable to -specify the gRPC endpoint. When this variable is set, the client can be initialised -without any arguments: - -```bash -export DAPR_GRPC_ENDPOINT="mydomain:50051?tls=true" -``` -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - # the client will use the endpoint specified in the environment variables -``` - -The legacy environment variables `DAPR_RUNTIME_HOST`, `DAPR_HTTP_PORT` and `DAPR_GRPC_PORT` are -also supported, but `DAPR_GRPC_ENDPOINT` takes precedence. - -##### Dapr API Token -If your Dapr instance is configured to require the `DAPR_API_TOKEN` environment variable, you can -set it in the environment and the client will use it automatically. -You can read more about Dapr API token authentication [here](https://docs.dapr.io/operations/security/api-token/). - -##### Health timeout -On client initialisation, a health check is performed against the Dapr sidecar (`/healthz/outbound`). -The client will wait for the sidecar to be up and running before proceeding. - -The default healthcheck timeout is 60 seconds, but it can be overridden by setting the `DAPR_HEALTH_TIMEOUT` -environment variable. - -##### Retries and timeout - -The Dapr client can retry a request if a specific error code is received from the sidecar. This is -configurable through the `DAPR_API_MAX_RETRIES` environment variable and is picked up automatically, -not requiring any code changes. -The default value for `DAPR_API_MAX_RETRIES` is `0`, which means no retries will be made. - -You can fine-tune more retry parameters by creating a `dapr.clients.retry.RetryPolicy` object and -passing it to the DaprClient constructor: - -```python -from dapr.clients.retry import RetryPolicy - -retry = RetryPolicy( - max_attempts=5, - initial_backoff=1, - max_backoff=20, - backoff_multiplier=1.5, - retryable_http_status_codes=[408, 429, 500, 502, 503, 504], - retryable_grpc_status_codes=[StatusCode.UNAVAILABLE, StatusCode.DEADLINE_EXCEEDED, ] -) - -with DaprClient(retry_policy=retry) as d: - ... -``` - -or for actors: -```python -factory = ActorProxyFactory(retry_policy=RetryPolicy(max_attempts=3)) -proxy = ActorProxy.create('DemoActor', ActorId('1'), DemoActorInterface, factory) -``` - -**Timeout** can be set for all calls through the environment variable `DAPR_API_TIMEOUT_SECONDS`. The default value is 60 seconds. - -> Note: You can control timeouts on service invocation separately, by passing a `timeout` parameter to the `invoke_method` method. - -## Error handling -Initially, errors in Dapr followed the [Standard gRPC error model](https://grpc.io/docs/guides/error/#standard-error-model). However, to provide more detailed and informative error messages, in version 1.13 an enhanced error model has been introduced which aligns with the gRPC [Richer error model](https://grpc.io/docs/guides/error/#richer-error-model). In response, the Python SDK implemented `DaprGrpcError`, a custom exception class designed to improve the developer experience. -It's important to note that the transition to using `DaprGrpcError` for all gRPC status exceptions is a work in progress. As of now, not every API call in the SDK has been updated to leverage this custom exception. We are actively working on this enhancement and welcome contributions from the community. - -Example of handling `DaprGrpcError` exceptions when using the Dapr python-SDK: - -```python -try: - d.save_state(store_name=storeName, key=key, value=value) -except DaprGrpcError as err: - print(f'Status code: {err.code()}') - print(f"Message: {err.message()}") - print(f"Error code: {err.error_code()}") - print(f"Error info(reason): {err.error_info.reason}") - print(f"Resource info (resource type): {err.resource_info.resource_type}") - print(f"Resource info (resource name): {err.resource_info.resource_name}") - print(f"Bad request (field): {err.bad_request.field_violations[0].field}") - print(f"Bad request (description): {err.bad_request.field_violations[0].description}") -``` - - -## Building blocks - -The Python SDK allows you to interface with all of the [Dapr building blocks]({{< ref building-blocks >}}). - -### Invoke a service - -The Dapr Python SDK provides a simple API for invoking services via either HTTP or gRPC (deprecated). The protocol can be selected by setting the `DAPR_API_METHOD_INVOCATION_PROTOCOL` environment variable, defaulting to HTTP when unset. GRPC service invocation in Dapr is deprecated and GRPC proxying is recommended as an alternative. - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - # invoke a method (gRPC or HTTP GET) - resp = d.invoke_method('service-to-invoke', 'method-to-invoke', data='{"message":"Hello World"}') - - # for other HTTP verbs the verb must be specified - # invoke a 'POST' method (HTTP only) - resp = d.invoke_method('service-to-invoke', 'method-to-invoke', data='{"id":"100", "FirstName":"Value", "LastName":"Value"}', http_verb='post') -``` - -The base endpoint for HTTP api calls is specified in the `DAPR_HTTP_ENDPOINT` environment variable. -If this variable is not set, the endpoint value is derived from the `DAPR_RUNTIME_HOST` and `DAPR_HTTP_PORT` variables, whose default values are `127.0.0.1` and `3500` accordingly. - -The base endpoint for gRPC calls is the one used for the client initialisation ([explained above](#initialising-the-client)). - - -- For a full guide on service invocation visit [How-To: Invoke a service]({{< ref howto-invoke-discover-services.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/invoke-simple) for code samples and instructions to try out service invocation. - -### Save & get application state - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - # Save state - d.save_state(store_name="statestore", key="key1", value="value1") - - # Get state - data = d.get_state(store_name="statestore", key="key1").data - - # Delete state - d.delete_state(store_name="statestore", key="key1") -``` - -- For a full list of state operations visit [How-To: Get & save state]({{< ref howto-get-save-state.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/state_store) for code samples and instructions to try out state management. - -### Query application state (Alpha) - -```python - from dapr import DaprClient - - query = ''' - { - "filter": { - "EQ": { "state": "CA" } - }, - "sort": [ - { - "key": "person.id", - "order": "DESC" - } - ] - } - ''' - - with DaprClient() as d: - resp = d.query_state( - store_name='state_store', - query=query, - states_metadata={"metakey": "metavalue"}, # optional - ) -``` - -- For a full list of state store query options visit [How-To: Query state]({{< ref howto-state-query-api.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/state_store_query) for code samples and instructions to try out state store querying. - -### Publish & subscribe - -#### Publish messages - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - resp = d.publish_event(pubsub_name='pubsub', topic_name='TOPIC_A', data='{"message":"Hello World"}') -``` - -#### Subscribe to messages - -```python -from cloudevents.sdk.event import v1 -from dapr.ext.grpc import App -import json - -app = App() - -# Default subscription for a topic -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A') -def mytopic(event: v1.Event) -> None: - data = json.loads(event.Data()) - print(f'Received: id={data["id"]}, message="{data ["message"]}"' - ' content_type="{event.content_type}"',flush=True) - -# Specific handler using Pub/Sub routing -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A', - rule=Rule("event.type == \"important\"", 1)) -def mytopic_important(event: v1.Event) -> None: - data = json.loads(event.Data()) - print(f'Received: id={data["id"]}, message="{data ["message"]}"' - ' content_type="{event.content_type}"',flush=True) -``` - -- For more information about pub/sub, visit [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/pubsub-simple) for code samples and instructions to try out pub/sub. - -#### Streaming message subscription - -You can create a streaming subscription to a PubSub topic using either the `subscribe` -or `subscribe_handler` methods. - -The `subscribe` method returns a `Subscription` object, which allows you to pull messages from the -stream by -calling the `next_message` method. This will block on the main thread while waiting for messages. -When done, you should call the close method to terminate the -subscription and stop receiving messages. - -The `subscribe_with_handler` method accepts a callback function that is executed for each message -received from the stream. -It runs in a separate thread, so it doesn't block the main thread. The callback should return a -`TopicEventResponse` (ex. `TopicEventResponse('success')`), indicating whether the message was -processed successfully, should be retried, or should be discarded. The method will automatically -manage message acknowledgements based on the returned status. The call to `subscribe_with_handler` -method returns a close function, which should be called to terminate the subscription when you're -done. - -Here's an example of using the `subscribe` method: - -```python -import time - -from dapr.clients import DaprClient -from dapr.clients.grpc.subscription import StreamInactiveError - -counter = 0 - - -def process_message(message): - global counter - counter += 1 - # Process the message here - print(f'Processing message: {message.data()} from {message.topic()}...') - return 'success' - - -def main(): - with DaprClient() as client: - global counter - - subscription = client.subscribe( - pubsub_name='pubsub', topic='TOPIC_A', dead_letter_topic='TOPIC_A_DEAD' - ) - - try: - while counter < 5: - try: - message = subscription.next_message() - - except StreamInactiveError as e: - print('Stream is inactive. Retrying...') - time.sleep(1) - continue - if message is None: - print('No message received within timeout period.') - continue - - # Process the message - response_status = process_message(message) - - if response_status == 'success': - subscription.respond_success(message) - elif response_status == 'retry': - subscription.respond_retry(message) - elif response_status == 'drop': - subscription.respond_drop(message) - - finally: - print("Closing subscription...") - subscription.close() - - -if __name__ == '__main__': - main() -``` - -And here's an example of using the `subscribe_with_handler` method: - -```python -import time - -from dapr.clients import DaprClient -from dapr.clients.grpc._response import TopicEventResponse - -counter = 0 - - -def process_message(message): - # Process the message here - global counter - counter += 1 - print(f'Processing message: {message.data()} from {message.topic()}...') - return TopicEventResponse('success') - - -def main(): - with (DaprClient() as client): - # This will start a new thread that will listen for messages - # and process them in the `process_message` function - close_fn = client.subscribe_with_handler( - pubsub_name='pubsub', topic='TOPIC_A', handler_fn=process_message, - dead_letter_topic='TOPIC_A_DEAD' - ) - - while counter < 5: - time.sleep(1) - - print("Closing subscription...") - close_fn() - - -if __name__ == '__main__': - main() -``` - -- For more information about pub/sub, visit [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/main/examples/pubsub-simple) for code samples and instructions to try out streaming pub/sub. - -### Interact with output bindings - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - resp = d.invoke_binding(binding_name='kafkaBinding', operation='create', data='{"message":"Hello World"}') -``` - -- For a full guide on output bindings visit [How-To: Use bindings]({{< ref howto-bindings.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/main/examples/invoke-binding) for code samples and instructions to try out output bindings. - -### Retrieve secrets - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - resp = d.get_secret(store_name='localsecretstore', key='secretKey') -``` - -- For a full guide on secrets visit [How-To: Retrieve secrets]({{< ref howto-secrets.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/secret_store) for code samples and instructions to try out retrieving secrets - -### Configuration - -#### Get configuration - -```python -from dapr.clients import DaprClient - -with DaprClient() as d: - # Get Configuration - configuration = d.get_configuration(store_name='configurationstore', keys=['orderId'], config_metadata={}) -``` - -#### Subscribe to configuration - -```python -import asyncio -from time import sleep -from dapr.clients import DaprClient - -async def executeConfiguration(): - with DaprClient() as d: - storeName = 'configurationstore' - - key = 'orderId' - - # Wait for sidecar to be up within 20 seconds. - d.wait(20) - - # Subscribe to configuration by key. - configuration = await d.subscribe_configuration(store_name=storeName, keys=[key], config_metadata={}) - while True: - if configuration != None: - items = configuration.get_items() - for key, item in items: - print(f"Subscribe key={key} value={item.value} version={item.version}", flush=True) - else: - print("Nothing yet") - sleep(5) - -asyncio.run(executeConfiguration()) -``` - -- Learn more about managing configurations via the [How-To: Manage configuration]({{< ref howto-manage-configuration.md >}}) guide. -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/configuration) for code samples and instructions to try out configuration. - -### Distributed Lock - -```python -from dapr.clients import DaprClient - -def main(): - # Lock parameters - store_name = 'lockstore' # as defined in components/lockstore.yaml - resource_id = 'example-lock-resource' - client_id = 'example-client-id' - expiry_in_seconds = 60 - - with DaprClient() as dapr: - print('Will try to acquire a lock from lock store named [%s]' % store_name) - print('The lock is for a resource named [%s]' % resource_id) - print('The client identifier is [%s]' % client_id) - print('The lock will will expire in %s seconds.' % expiry_in_seconds) - - with dapr.try_lock(store_name, resource_id, client_id, expiry_in_seconds) as lock_result: - assert lock_result.success, 'Failed to acquire the lock. Aborting.' - print('Lock acquired successfully!!!') - - # At this point the lock was released - by magic of the `with` clause ;) - unlock_result = dapr.unlock(store_name, resource_id, client_id) - print('We already released the lock so unlocking will not work.') - print('We tried to unlock it anyway and got back [%s]' % unlock_result.status) -``` - -- Learn more about using a distributed lock: [How-To: Use a lock]({{< ref howto-use-distributed-lock.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/blob/master/examples/distributed_lock) for code samples and instructions to try out distributed lock. - -### Cryptography - -```python -from dapr.clients import DaprClient - -message = 'The secret is "passw0rd"' - -def main(): - with DaprClient() as d: - resp = d.encrypt( - data=message.encode(), - options=EncryptOptions( - component_name='crypto-localstorage', - key_name='rsa-private-key.pem', - key_wrap_algorithm='RSA', - ), - ) - encrypt_bytes = resp.read() - - resp = d.decrypt( - data=encrypt_bytes, - options=DecryptOptions( - component_name='crypto-localstorage', - key_name='rsa-private-key.pem', - ), - ) - decrypt_bytes = resp.read() - - print(decrypt_bytes.decode()) # The secret is "passw0rd" -``` - -- For a full list of state operations visit [How-To: Use the cryptography APIs]({{< ref howto-cryptography.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/crypto) for code samples and instructions to try out cryptography - -### Workflow - -```python -from dapr.ext.workflow import WorkflowRuntime, DaprWorkflowContext, WorkflowActivityContext -from dapr.clients import DaprClient - -instanceId = "exampleInstanceID" -workflowComponent = "dapr" -workflowName = "hello_world_wf" -eventName = "event1" -eventData = "eventData" - -def main(): - with DaprClient() as d: - host = settings.DAPR_RUNTIME_HOST - port = settings.DAPR_GRPC_PORT - workflowRuntime = WorkflowRuntime(host, port) - workflowRuntime = WorkflowRuntime() - workflowRuntime.register_workflow(hello_world_wf) - workflowRuntime.register_activity(hello_act) - workflowRuntime.start() - - # Start the workflow - start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, - workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) - print(f"start_resp {start_resp.instance_id}") - - # ... - - # Pause Test - d.pause_workflow(instance_id=instanceId, workflow_component=workflowComponent) - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - print(f"Get response from {workflowName} after pause call: {getResponse.runtime_status}") - - # Resume Test - d.resume_workflow(instance_id=instanceId, workflow_component=workflowComponent) - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - print(f"Get response from {workflowName} after resume call: {getResponse.runtime_status}") - - sleep(1) - # Raise event - d.raise_workflow_event(instance_id=instanceId, workflow_component=workflowComponent, - event_name=eventName, event_data=eventData) - - sleep(5) - # Purge Test - d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) - try: - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - except DaprInternalError as err: - if nonExistentIDError in err._message: - print("Instance Successfully Purged") - - - # Kick off another workflow for termination purposes - # This will also test using the same instance ID on a new workflow after - # the old instance was purged - start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, - workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) - print(f"start_resp {start_resp.instance_id}") - - # Terminate Test - d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponent) - sleep(1) - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - print(f"Get response from {workflowName} after terminate call: {getResponse.runtime_status}") - - # Purge Test - d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) - try: - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - except DaprInternalError as err: - if nonExistentIDError in err._message: - print("Instance Successfully Purged") - - workflowRuntime.shutdown() -``` - -- Learn more about authoring and managing workflows: - - [How-To: Author a workflow]({{< ref howto-author-workflow.md >}}). - - [How-To: Manage a workflow]({{< ref howto-manage-workflow.md >}}). -- Visit [Python SDK examples](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) for code samples and instructions to try out Dapr Workflow. - - -## Related links -[Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples) +--- +type: docs +title: "Getting started with the Dapr client Python SDK" +linkTitle: "Client" +weight: 10000 +description: How to get up and running with the Dapr Python SDK +--- + +The Dapr client package allows you to interact with other Dapr applications from a Python application. + +{{% alert title="Note" color="primary" %}} + If you haven't already, [try out one of the quickstarts]({{< ref quickstarts >}}) for a quick walk-through on how to use the Dapr Python SDK with an API building block. + +{{% /alert %}} + +## Prerequisites + +[Install the Dapr Python package]({{< ref "python#installation" >}}) before getting started. + +## Import the client package + +The `dapr` package contains the `DaprClient`, which is used to create and use a client. + +```python +from dapr.clients import DaprClient +``` + +## Initialising the client +You can initialise a Dapr client in multiple ways: + +#### Default values: +When you initialise the client without any parameters it will use the default values for a Dapr +sidecar instance (`127.0.0.1:50001`). +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + # use the client +``` + +#### Specifying an endpoint on initialisation: +When passed as an argument in the constructor, the gRPC endpoint takes precedence over any +configuration or environment variable. + +```python +from dapr.clients import DaprClient + +with DaprClient("mydomain:50051?tls=true") as d: + # use the client +``` + +#### Configuration options: + +##### Dapr Sidecar Endpoints +You can use the standardised `DAPR_GRPC_ENDPOINT` environment variable to +specify the gRPC endpoint. When this variable is set, the client can be initialised +without any arguments: + +```bash +export DAPR_GRPC_ENDPOINT="mydomain:50051?tls=true" +``` +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + # the client will use the endpoint specified in the environment variables +``` + +The legacy environment variables `DAPR_RUNTIME_HOST`, `DAPR_HTTP_PORT` and `DAPR_GRPC_PORT` are +also supported, but `DAPR_GRPC_ENDPOINT` takes precedence. + +##### Dapr API Token +If your Dapr instance is configured to require the `DAPR_API_TOKEN` environment variable, you can +set it in the environment and the client will use it automatically. +You can read more about Dapr API token authentication [here](https://docs.dapr.io/operations/security/api-token/). + +##### Health timeout +On client initialisation, a health check is performed against the Dapr sidecar (`/healthz/outbound`). +The client will wait for the sidecar to be up and running before proceeding. + +The default healthcheck timeout is 60 seconds, but it can be overridden by setting the `DAPR_HEALTH_TIMEOUT` +environment variable. + +##### Retries and timeout + +The Dapr client can retry a request if a specific error code is received from the sidecar. This is +configurable through the `DAPR_API_MAX_RETRIES` environment variable and is picked up automatically, +not requiring any code changes. +The default value for `DAPR_API_MAX_RETRIES` is `0`, which means no retries will be made. + +You can fine-tune more retry parameters by creating a `dapr.clients.retry.RetryPolicy` object and +passing it to the DaprClient constructor: + +```python +from dapr.clients.retry import RetryPolicy + +retry = RetryPolicy( + max_attempts=5, + initial_backoff=1, + max_backoff=20, + backoff_multiplier=1.5, + retryable_http_status_codes=[408, 429, 500, 502, 503, 504], + retryable_grpc_status_codes=[StatusCode.UNAVAILABLE, StatusCode.DEADLINE_EXCEEDED, ] +) + +with DaprClient(retry_policy=retry) as d: + ... +``` + +or for actors: +```python +factory = ActorProxyFactory(retry_policy=RetryPolicy(max_attempts=3)) +proxy = ActorProxy.create('DemoActor', ActorId('1'), DemoActorInterface, factory) +``` + +**Timeout** can be set for all calls through the environment variable `DAPR_API_TIMEOUT_SECONDS`. The default value is 60 seconds. + +> Note: You can control timeouts on service invocation separately, by passing a `timeout` parameter to the `invoke_method` method. + +## Error handling +Initially, errors in Dapr followed the [Standard gRPC error model](https://grpc.io/docs/guides/error/#standard-error-model). However, to provide more detailed and informative error messages, in version 1.13 an enhanced error model has been introduced which aligns with the gRPC [Richer error model](https://grpc.io/docs/guides/error/#richer-error-model). In response, the Python SDK implemented `DaprGrpcError`, a custom exception class designed to improve the developer experience. +It's important to note that the transition to using `DaprGrpcError` for all gRPC status exceptions is a work in progress. As of now, not every API call in the SDK has been updated to leverage this custom exception. We are actively working on this enhancement and welcome contributions from the community. + +Example of handling `DaprGrpcError` exceptions when using the Dapr python-SDK: + +```python +try: + d.save_state(store_name=storeName, key=key, value=value) +except DaprGrpcError as err: + print(f'Status code: {err.code()}') + print(f"Message: {err.message()}") + print(f"Error code: {err.error_code()}") + print(f"Error info(reason): {err.error_info.reason}") + print(f"Resource info (resource type): {err.resource_info.resource_type}") + print(f"Resource info (resource name): {err.resource_info.resource_name}") + print(f"Bad request (field): {err.bad_request.field_violations[0].field}") + print(f"Bad request (description): {err.bad_request.field_violations[0].description}") +``` + + +## Building blocks + +The Python SDK allows you to interface with all of the [Dapr building blocks]({{< ref building-blocks >}}). + +### Invoke a service + +The Dapr Python SDK provides a simple API for invoking services via either HTTP or gRPC (deprecated). The protocol can be selected by setting the `DAPR_API_METHOD_INVOCATION_PROTOCOL` environment variable, defaulting to HTTP when unset. GRPC service invocation in Dapr is deprecated and GRPC proxying is recommended as an alternative. + +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + # invoke a method (gRPC or HTTP GET) + resp = d.invoke_method('service-to-invoke', 'method-to-invoke', data='{"message":"Hello World"}') + + # for other HTTP verbs the verb must be specified + # invoke a 'POST' method (HTTP only) + resp = d.invoke_method('service-to-invoke', 'method-to-invoke', data='{"id":"100", "FirstName":"Value", "LastName":"Value"}', http_verb='post') +``` + +The base endpoint for HTTP api calls is specified in the `DAPR_HTTP_ENDPOINT` environment variable. +If this variable is not set, the endpoint value is derived from the `DAPR_RUNTIME_HOST` and `DAPR_HTTP_PORT` variables, whose default values are `127.0.0.1` and `3500` accordingly. + +The base endpoint for gRPC calls is the one used for the client initialisation ([explained above](#initialising-the-client)). + + +- For a full guide on service invocation visit [How-To: Invoke a service]({{< ref howto-invoke-discover-services.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/invoke-simple) for code samples and instructions to try out service invocation. + +### Save & get application state + +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + # Save state + d.save_state(store_name="statestore", key="key1", value="value1") + + # Get state + data = d.get_state(store_name="statestore", key="key1").data + + # Delete state + d.delete_state(store_name="statestore", key="key1") +``` + +- For a full list of state operations visit [How-To: Get & save state]({{< ref howto-get-save-state.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/state_store) for code samples and instructions to try out state management. + +### Query application state (Alpha) + +```python + from dapr import DaprClient + + query = ''' + { + "filter": { + "EQ": { "state": "CA" } + }, + "sort": [ + { + "key": "person.id", + "order": "DESC" + } + ] + } + ''' + + with DaprClient() as d: + resp = d.query_state( + store_name='state_store', + query=query, + states_metadata={"metakey": "metavalue"}, # optional + ) +``` + +- For a full list of state store query options visit [How-To: Query state]({{< ref howto-state-query-api.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/state_store_query) for code samples and instructions to try out state store querying. + +### Publish & subscribe + +#### Publish messages + +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + resp = d.publish_event(pubsub_name='pubsub', topic_name='TOPIC_A', data='{"message":"Hello World"}') +``` + +#### Subscribe to messages + +```python +from cloudevents.sdk.event import v1 +from dapr.ext.grpc import App +import json + +app = App() + +# Default subscription for a topic +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A') +def mytopic(event: v1.Event) -> None: + data = json.loads(event.Data()) + print(f'Received: id={data["id"]}, message="{data ["message"]}"' + ' content_type="{event.content_type}"',flush=True) + +# Specific handler using Pub/Sub routing +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A', + rule=Rule("event.type == \"important\"", 1)) +def mytopic_important(event: v1.Event) -> None: + data = json.loads(event.Data()) + print(f'Received: id={data["id"]}, message="{data ["message"]}"' + ' content_type="{event.content_type}"',flush=True) +``` + +- For more information about pub/sub, visit [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/pubsub-simple) for code samples and instructions to try out pub/sub. + +#### Streaming message subscription + +You can create a streaming subscription to a PubSub topic using either the `subscribe` +or `subscribe_handler` methods. + +The `subscribe` method returns a `Subscription` object, which allows you to pull messages from the +stream by +calling the `next_message` method. This will block on the main thread while waiting for messages. +When done, you should call the close method to terminate the +subscription and stop receiving messages. + +The `subscribe_with_handler` method accepts a callback function that is executed for each message +received from the stream. +It runs in a separate thread, so it doesn't block the main thread. The callback should return a +`TopicEventResponse` (ex. `TopicEventResponse('success')`), indicating whether the message was +processed successfully, should be retried, or should be discarded. The method will automatically +manage message acknowledgements based on the returned status. The call to `subscribe_with_handler` +method returns a close function, which should be called to terminate the subscription when you're +done. + +Here's an example of using the `subscribe` method: + +```python +import time + +from dapr.clients import DaprClient +from dapr.clients.grpc.subscription import StreamInactiveError + +counter = 0 + + +def process_message(message): + global counter + counter += 1 + # Process the message here + print(f'Processing message: {message.data()} from {message.topic()}...') + return 'success' + + +def main(): + with DaprClient() as client: + global counter + + subscription = client.subscribe( + pubsub_name='pubsub', topic='TOPIC_A', dead_letter_topic='TOPIC_A_DEAD' + ) + + try: + while counter < 5: + try: + message = subscription.next_message() + + except StreamInactiveError as e: + print('Stream is inactive. Retrying...') + time.sleep(1) + continue + if message is None: + print('No message received within timeout period.') + continue + + # Process the message + response_status = process_message(message) + + if response_status == 'success': + subscription.respond_success(message) + elif response_status == 'retry': + subscription.respond_retry(message) + elif response_status == 'drop': + subscription.respond_drop(message) + + finally: + print("Closing subscription...") + subscription.close() + + +if __name__ == '__main__': + main() +``` + +And here's an example of using the `subscribe_with_handler` method: + +```python +import time + +from dapr.clients import DaprClient +from dapr.clients.grpc._response import TopicEventResponse + +counter = 0 + + +def process_message(message): + # Process the message here + global counter + counter += 1 + print(f'Processing message: {message.data()} from {message.topic()}...') + return TopicEventResponse('success') + + +def main(): + with (DaprClient() as client): + # This will start a new thread that will listen for messages + # and process them in the `process_message` function + close_fn = client.subscribe_with_handler( + pubsub_name='pubsub', topic='TOPIC_A', handler_fn=process_message, + dead_letter_topic='TOPIC_A_DEAD' + ) + + while counter < 5: + time.sleep(1) + + print("Closing subscription...") + close_fn() + + +if __name__ == '__main__': + main() +``` + +- For more information about pub/sub, visit [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/main/examples/pubsub-simple) for code samples and instructions to try out streaming pub/sub. + +### Interact with output bindings + +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + resp = d.invoke_binding(binding_name='kafkaBinding', operation='create', data='{"message":"Hello World"}') +``` + +- For a full guide on output bindings visit [How-To: Use bindings]({{< ref howto-bindings.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/main/examples/invoke-binding) for code samples and instructions to try out output bindings. + +### Retrieve secrets + +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + resp = d.get_secret(store_name='localsecretstore', key='secretKey') +``` + +- For a full guide on secrets visit [How-To: Retrieve secrets]({{< ref howto-secrets.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/secret_store) for code samples and instructions to try out retrieving secrets + +### Configuration + +#### Get configuration + +```python +from dapr.clients import DaprClient + +with DaprClient() as d: + # Get Configuration + configuration = d.get_configuration(store_name='configurationstore', keys=['orderId'], config_metadata={}) +``` + +#### Subscribe to configuration + +```python +import asyncio +from time import sleep +from dapr.clients import DaprClient + +async def executeConfiguration(): + with DaprClient() as d: + storeName = 'configurationstore' + + key = 'orderId' + + # Wait for sidecar to be up within 20 seconds. + d.wait(20) + + # Subscribe to configuration by key. + configuration = await d.subscribe_configuration(store_name=storeName, keys=[key], config_metadata={}) + while True: + if configuration != None: + items = configuration.get_items() + for key, item in items: + print(f"Subscribe key={key} value={item.value} version={item.version}", flush=True) + else: + print("Nothing yet") + sleep(5) + +asyncio.run(executeConfiguration()) +``` + +- Learn more about managing configurations via the [How-To: Manage configuration]({{< ref howto-manage-configuration.md >}}) guide. +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/configuration) for code samples and instructions to try out configuration. + +### Distributed Lock + +```python +from dapr.clients import DaprClient + +def main(): + # Lock parameters + store_name = 'lockstore' # as defined in components/lockstore.yaml + resource_id = 'example-lock-resource' + client_id = 'example-client-id' + expiry_in_seconds = 60 + + with DaprClient() as dapr: + print('Will try to acquire a lock from lock store named [%s]' % store_name) + print('The lock is for a resource named [%s]' % resource_id) + print('The client identifier is [%s]' % client_id) + print('The lock will will expire in %s seconds.' % expiry_in_seconds) + + with dapr.try_lock(store_name, resource_id, client_id, expiry_in_seconds) as lock_result: + assert lock_result.success, 'Failed to acquire the lock. Aborting.' + print('Lock acquired successfully!!!') + + # At this point the lock was released - by magic of the `with` clause ;) + unlock_result = dapr.unlock(store_name, resource_id, client_id) + print('We already released the lock so unlocking will not work.') + print('We tried to unlock it anyway and got back [%s]' % unlock_result.status) +``` + +- Learn more about using a distributed lock: [How-To: Use a lock]({{< ref howto-use-distributed-lock.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/blob/master/examples/distributed_lock) for code samples and instructions to try out distributed lock. + +### Cryptography + +```python +from dapr.clients import DaprClient + +message = 'The secret is "passw0rd"' + +def main(): + with DaprClient() as d: + resp = d.encrypt( + data=message.encode(), + options=EncryptOptions( + component_name='crypto-localstorage', + key_name='rsa-private-key.pem', + key_wrap_algorithm='RSA', + ), + ) + encrypt_bytes = resp.read() + + resp = d.decrypt( + data=encrypt_bytes, + options=DecryptOptions( + component_name='crypto-localstorage', + key_name='rsa-private-key.pem', + ), + ) + decrypt_bytes = resp.read() + + print(decrypt_bytes.decode()) # The secret is "passw0rd" +``` + +- For a full list of state operations visit [How-To: Use the cryptography APIs]({{< ref howto-cryptography.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples/crypto) for code samples and instructions to try out cryptography + +### Workflow + +```python +from dapr.ext.workflow import WorkflowRuntime, DaprWorkflowContext, WorkflowActivityContext +from dapr.clients import DaprClient + +instanceId = "exampleInstanceID" +workflowComponent = "dapr" +workflowName = "hello_world_wf" +eventName = "event1" +eventData = "eventData" + +def main(): + with DaprClient() as d: + host = settings.DAPR_RUNTIME_HOST + port = settings.DAPR_GRPC_PORT + workflowRuntime = WorkflowRuntime(host, port) + workflowRuntime = WorkflowRuntime() + workflowRuntime.register_workflow(hello_world_wf) + workflowRuntime.register_activity(hello_act) + workflowRuntime.start() + + # Start the workflow + start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, + workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) + print(f"start_resp {start_resp.instance_id}") + + # ... + + # Pause Test + d.pause_workflow(instance_id=instanceId, workflow_component=workflowComponent) + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + print(f"Get response from {workflowName} after pause call: {getResponse.runtime_status}") + + # Resume Test + d.resume_workflow(instance_id=instanceId, workflow_component=workflowComponent) + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + print(f"Get response from {workflowName} after resume call: {getResponse.runtime_status}") + + sleep(1) + # Raise event + d.raise_workflow_event(instance_id=instanceId, workflow_component=workflowComponent, + event_name=eventName, event_data=eventData) + + sleep(5) + # Purge Test + d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) + try: + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + except DaprInternalError as err: + if nonExistentIDError in err._message: + print("Instance Successfully Purged") + + + # Kick off another workflow for termination purposes + # This will also test using the same instance ID on a new workflow after + # the old instance was purged + start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, + workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) + print(f"start_resp {start_resp.instance_id}") + + # Terminate Test + d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponent) + sleep(1) + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + print(f"Get response from {workflowName} after terminate call: {getResponse.runtime_status}") + + # Purge Test + d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) + try: + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + except DaprInternalError as err: + if nonExistentIDError in err._message: + print("Instance Successfully Purged") + + workflowRuntime.shutdown() +``` + +- Learn more about authoring and managing workflows: + - [How-To: Author a workflow]({{< ref howto-author-workflow.md >}}). + - [How-To: Manage a workflow]({{< ref howto-manage-workflow.md >}}). +- Visit [Python SDK examples](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) for code samples and instructions to try out Dapr Workflow. + + +## Related links +[Python SDK examples](https://github.com/dapr/python-sdk/tree/master/examples) diff --git a/daprdocs/content/en/python-sdk-docs/python-actor.md b/daprdocs/content/en/python-sdk-docs/python-sdk-actors/_index.md similarity index 100% rename from daprdocs/content/en/python-sdk-docs/python-actor.md rename to daprdocs/content/en/python-sdk-docs/python-sdk-actors/_index.md diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-actors/python-mock-actors.md b/daprdocs/content/en/python-sdk-docs/python-sdk-actors/python-mock-actors.md new file mode 100644 index 000000000..59538ada2 --- /dev/null +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-actors/python-mock-actors.md @@ -0,0 +1,66 @@ +--- +type: docs +title: "Dapr Python SDK mock actor unit tests" +linkTitle: "Mock Actors" +weight: 200000 +description: How to unit test actor methods using mock actors +--- + +The Dapr Python SDK provides the ability to create mock actors to unit test your actor methods and how they interact with the actor state. + +## Basic Usage +``` +from dapr.actor.runtime.mock_actor import create_mock_actor + +class MyActor(Actor, MyActorInterface): + async def save_state(self, data) -> None: + await self._state_manager.set_state('state', data) + await self._state_manager.save_state() + +mock_actor = create_mock_actor(MyActor, "id") + +await mock_actor.save_state(5) +assert mockactor._state_manager._mock_state == 5 #True +``` +Mock actors work by passing your actor class as well as the actor id (str) into the function create_mock_actor, which returns an instance of the actor with a bunch of the internal actor methods overwritten, such that instead of attempting to interact with Dapr to save state, manage timers, etc it instead only uses local variables. + +Those variables are: +* **_state_manager._mock_state()** +A [str, object] dict where all the actor state is stored. Any variable saved via _state_manager.save_state(key, value), or any other statemanager method is stored in the dict as that key, value combo. Any value loaded via try_get_state or any other statemanager method is taken from this dict. + +* **_state_manager._mock_timers()** +A [str, ActorTimerData] dict which holds the active actor timers. Any actor method which would add or remove a timer adds or pops the appropriate ActorTimerData object from this dict. + +* **_state_manager._mock_reminders()** +A [str, ActorReminderData] dict which holds the active actor reminders. Any actor method which would add or remove a timer adds or pops the appropriate ActorReminderData object from this dict. + +**Note: The timers and reminders will never actually trigger. The dictionaries exist only so methods that should add or remove timers/reminders can be tested. If you need to test the callbacks they should activate, you should call them directly with the appropriate values:** +``` +result = await mock_actor.recieve_reminder(name, state, due_time, period, _ttl) +# Test the result directly or test for side effects (like changing state) by querying _state_manager._mock_state +``` + +## Usage and Limitations + +**The \_on\_activate method will not be called automatically the way it is when Dapr initializes a new Actor instance. You should call it manually as needed as part of your tests.** + +The \_\_init\_\_, register_timer, unregister_timer, register_reminder, unregister_reminder methods are all overwritten by the MockActor class that gets applied as a mixin via create_mock_actor. If your actor itself overwrites these methods, those modifications will themselves be overwritten and the actor will likely not behave as you expect. + +*note: \_\_init\_\_ is a special case where you are expected to define it as* +``` + def __init__(self, ctx, actor_id): + super().__init__(ctx, actor_id) +``` +*Mock actors work fine with this, but if you have added any extra logic into \_\_init\_\_, it will be overwritten. It is worth noting that the correct way to apply logic on initialization is via \_on\_activate (which can also be safely used with mock actors) instead of \_\_init\_\_.* + +The actor _runtime_ctx variable is set to None. Obviously all the normal actor methods have been overwritten such as to not call it, but if your code itself interacts directly with _runtime_ctx, it will likely break. + +The actor _state_manager is overwritten with an instance of MockStateManager. This has all the same methods and functionality of the base ActorStateManager, except for using the various _mock variables for storing data instead of the _runtime_ctx. If your code implements its own custom state manager it will be overwritten and your code will likely break. + +## Type Hinting + +Because of Python's lack of a unified method for type hinting type intersections (see: [python/typing #213](https://github.com/python/typing/issues/213)), type hinting is unfortunately mostly broken with Mock Actors. The return type is type hinted as "instance of Actor subclass T" when it should really be type hinted as "instance of MockActor subclass T" or "instance of type intersection [Actor subclass T, MockActor]" (where, it is worth noting, MockActor is itself a subclass of Actor). + +This means that, for example, if you hover over ```mockactor._state_manager``` in a code editor, it will come up as an instance of ActorStateManager (instead of MockStateManager), and various IDE helper functions (like VSCode's ```Go to Definition```, which will bring you to the definition of ActorStateManager instead of MockStateManager) won't work properly. + +For now, this issue is unfixable, so it's merely something to be noted because of the confusion it might cause. If in the future it becomes possible to accurately type hint cases like this feel free to open an issue about implementing it. \ No newline at end of file diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/_index.md b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/_index.md index 8b7bc9c50..39ad47f89 100644 --- a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/_index.md +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/_index.md @@ -1,7 +1,7 @@ ---- -type: docs -title: "Dapr Python SDK extensions" -linkTitle: "Extensions" -weight: 30000 -description: Python SDK for developing Dapr applications ---- +--- +type: docs +title: "Dapr Python SDK extensions" +linkTitle: "Extensions" +weight: 30000 +description: Python SDK for developing Dapr applications +--- diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-fastapi.md b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-fastapi.md index b9bf5b6bd..55b4009c7 100644 --- a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-fastapi.md +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-fastapi.md @@ -1,115 +1,115 @@ ---- -type: docs -title: "Dapr Python SDK integration with FastAPI" -linkTitle: "FastAPI" -weight: 200000 -description: How to create Dapr Python virtual actors and pubsub with the FastAPI extension ---- - -The Dapr Python SDK provides integration with FastAPI using the `dapr-ext-fastapi` extension. - -## Installation - -You can download and install the Dapr FastAPI extension with: - -{{< tabs Stable Development>}} - -{{% codetab %}} -```bash -pip install dapr-ext-fastapi -``` -{{% /codetab %}} - -{{% codetab %}} -{{% alert title="Note" color="warning" %}} -The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. -{{% /alert %}} - -```bash -pip install dapr-ext-fastapi-dev -``` -{{% /codetab %}} - -{{< /tabs >}} - -## Example - -### Subscribing to events of different types - -```python -import uvicorn -from fastapi import Body, FastAPI -from dapr.ext.fastapi import DaprApp -from pydantic import BaseModel - -class RawEventModel(BaseModel): - body: str - -class User(BaseModel): - id: int - name = 'Jane Doe' - -class CloudEventModel(BaseModel): - data: User - datacontenttype: str - id: str - pubsubname: str - source: str - specversion: str - topic: str - traceid: str - traceparent: str - tracestate: str - type: str - - -app = FastAPI() -dapr_app = DaprApp(app) - -# Allow handling event with any structure (Easiest, but least robust) -# dapr publish --publish-app-id sample --topic any_topic --pubsub pubsub --data '{"id":"7", "desc": "good", "size":"small"}' -@dapr_app.subscribe(pubsub='pubsub', topic='any_topic') -def any_event_handler(event_data = Body()): - print(event_data) - -# For robustness choose one of the below based on if publisher is using CloudEvents - -# Handle events sent with CloudEvents -# dapr publish --publish-app-id sample --topic cloud_topic --pubsub pubsub --data '{"id":"7", "name":"Bob Jones"}' -@dapr_app.subscribe(pubsub='pubsub', topic='cloud_topic') -def cloud_event_handler(event_data: CloudEventModel): - print(event_data) - -# Handle raw events sent without CloudEvents -# curl -X "POST" http://localhost:3500/v1.0/publish/pubsub/raw_topic?metadata.rawPayload=true -H "Content-Type: application/json" -d '{"body": "345"}' -@dapr_app.subscribe(pubsub='pubsub', topic='raw_topic') -def raw_event_handler(event_data: RawEventModel): - print(event_data) - - - -if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=30212) -``` - -### Creating an actor - -```python -from fastapi import FastAPI -from dapr.ext.fastapi import DaprActor -from demo_actor import DemoActor - -app = FastAPI(title=f'{DemoActor.__name__}Service') - -# Add Dapr Actor Extension -actor = DaprActor(app) - -@app.on_event("startup") -async def startup_event(): - # Register DemoActor - await actor.register_actor(DemoActor) - -@app.get("/GetMyData") -def get_my_data(): - return "{'message': 'myData'}" +--- +type: docs +title: "Dapr Python SDK integration with FastAPI" +linkTitle: "FastAPI" +weight: 200000 +description: How to create Dapr Python virtual actors and pubsub with the FastAPI extension +--- + +The Dapr Python SDK provides integration with FastAPI using the `dapr-ext-fastapi` extension. + +## Installation + +You can download and install the Dapr FastAPI extension with: + +{{< tabs Stable Development>}} + +{{% codetab %}} +```bash +pip install dapr-ext-fastapi +``` +{{% /codetab %}} + +{{% codetab %}} +{{% alert title="Note" color="warning" %}} +The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. +{{% /alert %}} + +```bash +pip install dapr-ext-fastapi-dev +``` +{{% /codetab %}} + +{{< /tabs >}} + +## Example + +### Subscribing to events of different types + +```python +import uvicorn +from fastapi import Body, FastAPI +from dapr.ext.fastapi import DaprApp +from pydantic import BaseModel + +class RawEventModel(BaseModel): + body: str + +class User(BaseModel): + id: int + name = 'Jane Doe' + +class CloudEventModel(BaseModel): + data: User + datacontenttype: str + id: str + pubsubname: str + source: str + specversion: str + topic: str + traceid: str + traceparent: str + tracestate: str + type: str + + +app = FastAPI() +dapr_app = DaprApp(app) + +# Allow handling event with any structure (Easiest, but least robust) +# dapr publish --publish-app-id sample --topic any_topic --pubsub pubsub --data '{"id":"7", "desc": "good", "size":"small"}' +@dapr_app.subscribe(pubsub='pubsub', topic='any_topic') +def any_event_handler(event_data = Body()): + print(event_data) + +# For robustness choose one of the below based on if publisher is using CloudEvents + +# Handle events sent with CloudEvents +# dapr publish --publish-app-id sample --topic cloud_topic --pubsub pubsub --data '{"id":"7", "name":"Bob Jones"}' +@dapr_app.subscribe(pubsub='pubsub', topic='cloud_topic') +def cloud_event_handler(event_data: CloudEventModel): + print(event_data) + +# Handle raw events sent without CloudEvents +# curl -X "POST" http://localhost:3500/v1.0/publish/pubsub/raw_topic?metadata.rawPayload=true -H "Content-Type: application/json" -d '{"body": "345"}' +@dapr_app.subscribe(pubsub='pubsub', topic='raw_topic') +def raw_event_handler(event_data: RawEventModel): + print(event_data) + + + +if __name__ == "__main__": + uvicorn.run(app, host="0.0.0.0", port=30212) +``` + +### Creating an actor + +```python +from fastapi import FastAPI +from dapr.ext.fastapi import DaprActor +from demo_actor import DemoActor + +app = FastAPI(title=f'{DemoActor.__name__}Service') + +# Add Dapr Actor Extension +actor = DaprActor(app) + +@app.on_event("startup") +async def startup_event(): + # Register DemoActor + await actor.register_actor(DemoActor) + +@app.get("/GetMyData") +def get_my_data(): + return "{'message': 'myData'}" ``` \ No newline at end of file diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-flask.md b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-flask.md index e48948bce..6f17d7dbb 100644 --- a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-flask.md +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-flask.md @@ -1,60 +1,60 @@ ---- -type: docs -title: "Dapr Python SDK integration with Flask" -linkTitle: "Flask" -weight: 300000 -description: How to create Dapr Python virtual actors with the Flask extension ---- - -The Dapr Python SDK provides integration with Flask using the `flask-dapr` extension. - -## Installation - -You can download and install the Dapr Flask extension with: - -{{< tabs Stable Development>}} - -{{% codetab %}} -```bash -pip install flask-dapr -``` -{{% /codetab %}} - -{{% codetab %}} -{{% alert title="Note" color="warning" %}} -The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. -{{% /alert %}} - -```bash -pip install flask-dapr-dev -``` -{{% /codetab %}} - -{{< /tabs >}} - -## Example - -```python -from flask import Flask -from flask_dapr.actor import DaprActor - -from dapr.conf import settings -from demo_actor import DemoActor - -app = Flask(f'{DemoActor.__name__}Service') - -# Enable DaprActor Flask extension -actor = DaprActor(app) - -# Register DemoActor -actor.register_actor(DemoActor) - -# Setup method route -@app.route('/GetMyData', methods=['GET']) -def get_my_data(): - return {'message': 'myData'}, 200 - -# Run application -if __name__ == '__main__': - app.run(port=settings.HTTP_APP_PORT) +--- +type: docs +title: "Dapr Python SDK integration with Flask" +linkTitle: "Flask" +weight: 300000 +description: How to create Dapr Python virtual actors with the Flask extension +--- + +The Dapr Python SDK provides integration with Flask using the `flask-dapr` extension. + +## Installation + +You can download and install the Dapr Flask extension with: + +{{< tabs Stable Development>}} + +{{% codetab %}} +```bash +pip install flask-dapr +``` +{{% /codetab %}} + +{{% codetab %}} +{{% alert title="Note" color="warning" %}} +The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. +{{% /alert %}} + +```bash +pip install flask-dapr-dev +``` +{{% /codetab %}} + +{{< /tabs >}} + +## Example + +```python +from flask import Flask +from flask_dapr.actor import DaprActor + +from dapr.conf import settings +from demo_actor import DemoActor + +app = Flask(f'{DemoActor.__name__}Service') + +# Enable DaprActor Flask extension +actor = DaprActor(app) + +# Register DemoActor +actor.register_actor(DemoActor) + +# Setup method route +@app.route('/GetMyData', methods=['GET']) +def get_my_data(): + return {'message': 'myData'}, 200 + +# Run application +if __name__ == '__main__': + app.run(port=settings.HTTP_APP_PORT) ``` \ No newline at end of file diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-grpc.md b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-grpc.md index 0e868238a..52f7d01d6 100644 --- a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-grpc.md +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-grpc.md @@ -1,118 +1,118 @@ ---- -type: docs -title: "Getting started with the Dapr Python gRPC service extension" -linkTitle: "gRPC" -weight: 100000 -description: How to get up and running with the Dapr Python gRPC extension ---- - -The Dapr Python SDK provides a built in gRPC server extension, `dapr.ext.grpc`, for creating Dapr services. - -## Installation - -You can download and install the Dapr gRPC server extension with: - -{{< tabs Stable Development>}} - -{{% codetab %}} -```bash -pip install dapr-ext-grpc -``` -{{% /codetab %}} - -{{% codetab %}} -{{% alert title="Note" color="warning" %}} -The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. -{{% /alert %}} - -```bash -pip3 install dapr-ext-grpc-dev -``` -{{% /codetab %}} - -{{< /tabs >}} - -## Examples - -The `App` object can be used to create a server. - -### Listen for service invocation requests - -The `InvokeMethodReqest` and `InvokeMethodResponse` objects can be used to handle incoming requests. - -A simple service that will listen and respond to requests will look like: - -```python -from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse - -app = App() - -@app.method(name='my-method') -def mymethod(request: InvokeMethodRequest) -> InvokeMethodResponse: - print(request.metadata, flush=True) - print(request.text(), flush=True) - - return InvokeMethodResponse(b'INVOKE_RECEIVED', "text/plain; charset=UTF-8") - -app.run(50051) -``` - -A full sample can be found [here](https://github.com/dapr/python-sdk/tree/v1.0.0rc2/examples/invoke-simple). - -### Subscribe to a topic - -When subscribing to a topic, you can instruct dapr whether the event delivered has been accepted, or whether it should be dropped, or retried later. - -```python -from typing import Optional -from cloudevents.sdk.event import v1 -from dapr.ext.grpc import App -from dapr.clients.grpc._response import TopicEventResponse - -app = App() - -# Default subscription for a topic -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A') -def mytopic(event: v1.Event) -> Optional[TopicEventResponse]: - print(event.Data(),flush=True) - # Returning None (or not doing a return explicitly) is equivalent - # to returning a TopicEventResponse("success"). - # You can also return TopicEventResponse("retry") for dapr to log - # the message and retry delivery later, or TopicEventResponse("drop") - # for it to drop the message - return TopicEventResponse("success") - -# Specific handler using Pub/Sub routing -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A', - rule=Rule("event.type == \"important\"", 1)) -def mytopic_important(event: v1.Event) -> None: - print(event.Data(),flush=True) - -# Handler with disabled topic validation -@app.subscribe(pubsub_name='pubsub-mqtt', topic='topic/#', disable_topic_validation=True,) -def mytopic_wildcard(event: v1.Event) -> None: - print(event.Data(),flush=True) - -app.run(50051) -``` - -A full sample can be found [here](https://github.com/dapr/python-sdk/blob/v1.0.0rc2/examples/pubsub-simple/subscriber.py). - -### Setup input binding trigger - -```python -from dapr.ext.grpc import App, BindingRequest - -app = App() - -@app.binding('kafkaBinding') -def binding(request: BindingRequest): - print(request.text(), flush=True) - -app.run(50051) -``` - -A full sample can be found [here](https://github.com/dapr/python-sdk/tree/v1.0.0rc2/examples/invoke-binding). - -## Related links -- [PyPi](https://pypi.org/project/dapr-ext-grpc/) +--- +type: docs +title: "Getting started with the Dapr Python gRPC service extension" +linkTitle: "gRPC" +weight: 100000 +description: How to get up and running with the Dapr Python gRPC extension +--- + +The Dapr Python SDK provides a built in gRPC server extension, `dapr.ext.grpc`, for creating Dapr services. + +## Installation + +You can download and install the Dapr gRPC server extension with: + +{{< tabs Stable Development>}} + +{{% codetab %}} +```bash +pip install dapr-ext-grpc +``` +{{% /codetab %}} + +{{% codetab %}} +{{% alert title="Note" color="warning" %}} +The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. +{{% /alert %}} + +```bash +pip3 install dapr-ext-grpc-dev +``` +{{% /codetab %}} + +{{< /tabs >}} + +## Examples + +The `App` object can be used to create a server. + +### Listen for service invocation requests + +The `InvokeMethodReqest` and `InvokeMethodResponse` objects can be used to handle incoming requests. + +A simple service that will listen and respond to requests will look like: + +```python +from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse + +app = App() + +@app.method(name='my-method') +def mymethod(request: InvokeMethodRequest) -> InvokeMethodResponse: + print(request.metadata, flush=True) + print(request.text(), flush=True) + + return InvokeMethodResponse(b'INVOKE_RECEIVED', "text/plain; charset=UTF-8") + +app.run(50051) +``` + +A full sample can be found [here](https://github.com/dapr/python-sdk/tree/v1.0.0rc2/examples/invoke-simple). + +### Subscribe to a topic + +When subscribing to a topic, you can instruct dapr whether the event delivered has been accepted, or whether it should be dropped, or retried later. + +```python +from typing import Optional +from cloudevents.sdk.event import v1 +from dapr.ext.grpc import App +from dapr.clients.grpc._response import TopicEventResponse + +app = App() + +# Default subscription for a topic +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A') +def mytopic(event: v1.Event) -> Optional[TopicEventResponse]: + print(event.Data(),flush=True) + # Returning None (or not doing a return explicitly) is equivalent + # to returning a TopicEventResponse("success"). + # You can also return TopicEventResponse("retry") for dapr to log + # the message and retry delivery later, or TopicEventResponse("drop") + # for it to drop the message + return TopicEventResponse("success") + +# Specific handler using Pub/Sub routing +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A', + rule=Rule("event.type == \"important\"", 1)) +def mytopic_important(event: v1.Event) -> None: + print(event.Data(),flush=True) + +# Handler with disabled topic validation +@app.subscribe(pubsub_name='pubsub-mqtt', topic='topic/#', disable_topic_validation=True,) +def mytopic_wildcard(event: v1.Event) -> None: + print(event.Data(),flush=True) + +app.run(50051) +``` + +A full sample can be found [here](https://github.com/dapr/python-sdk/blob/v1.0.0rc2/examples/pubsub-simple/subscriber.py). + +### Setup input binding trigger + +```python +from dapr.ext.grpc import App, BindingRequest + +app = App() + +@app.binding('kafkaBinding') +def binding(request: BindingRequest): + print(request.text(), flush=True) + +app.run(50051) +``` + +A full sample can be found [here](https://github.com/dapr/python-sdk/tree/v1.0.0rc2/examples/invoke-binding). + +## Related links +- [PyPi](https://pypi.org/project/dapr-ext-grpc/) diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/_index.md b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/_index.md index e5fe5ce81..f356142ff 100644 --- a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/_index.md +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/_index.md @@ -1,42 +1,42 @@ ---- -type: docs -title: "Dapr Python SDK integration with Dapr Workflow extension" -linkTitle: "Dapr Workflow" -weight: 400000 -description: How to get up and running with the Dapr Workflow extension -no_list: true ---- - -{{% alert title="Note" color="primary" %}} -Dapr Workflow is currently in alpha. -{{% /alert %}} - -The Dapr Python SDK provides a built in Dapr Workflow extension, `dapr.ext.workflow`, for creating Dapr services. - -## Installation - -You can download and install the Dapr Workflow extension with: - -{{< tabs Stable Development>}} - -{{% codetab %}} -```bash -pip install dapr-ext-workflow -``` -{{% /codetab %}} - -{{% codetab %}} -{{% alert title="Note" color="warning" %}} -The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. -{{% /alert %}} - -```bash -pip3 install dapr-ext-workflow-dev -``` -{{% /codetab %}} - -{{< /tabs >}} - -## Next steps - -{{< button text="Getting started with the Dapr Workflow Python SDK" page="python-workflow.md" >}} +--- +type: docs +title: "Dapr Python SDK integration with Dapr Workflow extension" +linkTitle: "Dapr Workflow" +weight: 400000 +description: How to get up and running with the Dapr Workflow extension +no_list: true +--- + +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in alpha. +{{% /alert %}} + +The Dapr Python SDK provides a built in Dapr Workflow extension, `dapr.ext.workflow`, for creating Dapr services. + +## Installation + +You can download and install the Dapr Workflow extension with: + +{{< tabs Stable Development>}} + +{{% codetab %}} +```bash +pip install dapr-ext-workflow +``` +{{% /codetab %}} + +{{% codetab %}} +{{% alert title="Note" color="warning" %}} +The development package will contain features and behavior that will be compatible with the pre-release version of the Dapr runtime. Make sure to uninstall any stable versions of the Python SDK extension before installing the `dapr-dev` package. +{{% /alert %}} + +```bash +pip3 install dapr-ext-workflow-dev +``` +{{% /codetab %}} + +{{< /tabs >}} + +## Next steps + +{{< button text="Getting started with the Dapr Workflow Python SDK" page="python-workflow.md" >}} diff --git a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/python-workflow.md b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/python-workflow.md index 984b2cb89..75c073ad5 100644 --- a/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/python-workflow.md +++ b/daprdocs/content/en/python-sdk-docs/python-sdk-extensions/python-workflow-ext/python-workflow.md @@ -1,186 +1,186 @@ ---- -type: docs -title: "Getting started with the Dapr Workflow Python SDK" -linkTitle: "Workflow" -weight: 30000 -description: How to get up and running with workflows using the Dapr Python SDK ---- - -{{% alert title="Note" color="primary" %}} -Dapr Workflow is currently in alpha. -{{% /alert %}} - -Let’s create a Dapr workflow and invoke it using the console. With the [provided hello world workflow example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow), you will: - -- Run a [Python console application using `DaprClient`](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) -- Utilize the Python workflow SDK and API calls to start, pause, resume, terminate, and purge workflow instances - -This example uses the default configuration from `dapr init` in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). - -In the Python example project, the `app.py` file contains the setup of the app, including: -- The workflow definition -- The workflow activity definitions -- The registration of the workflow and workflow activities - -## Prerequisites -- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed -- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) -- [Python 3.8+](https://www.python.org/downloads/) installed -- [Dapr Python package]({{< ref "python#installation" >}}) and the [workflow extension]({{< ref "python-workflow/_index.md" >}}) installed -- Verify you're using the latest proto bindings - -## Set up the environment - -Run the following command to install the requirements for running this workflow sample with the Dapr Python SDK. - -```bash -pip3 install -r demo_workflow/requirements.txt -``` - -Clone the [Python SDK repo]. - -```bash -git clone https://github.com/dapr/python-sdk.git -``` - -From the Python SDK root directory, navigate to the Dapr Workflow example. - -```bash -cd examples/demo_workflow -``` - -## Run the application locally - -To run the Dapr application, you need to start the Python program and a Dapr sidecar. In the terminal, run: - -```bash -dapr run --app-id orderapp --app-protocol grpc --dapr-grpc-port 50001 --resources-path components --placement-host-address localhost:50005 -- python3 app.py -``` - -> **Note:** Since Python3.exe is not defined in Windows, you may need to use `python app.py` instead of `python3 app.py`. - - -**Expected output** - -``` -== APP == ==========Start Counter Increase as per Input:========== - -== APP == start_resp exampleInstanceID - -== APP == Hi Counter! -== APP == New counter value is: 1! - -== APP == Hi Counter! -== APP == New counter value is: 11! - -== APP == Hi Counter! -== APP == Hi Counter! -== APP == Get response from hello_world_wf after pause call: Suspended - -== APP == Hi Counter! -== APP == Get response from hello_world_wf after resume call: Running - -== APP == Hi Counter! -== APP == New counter value is: 111! - -== APP == Hi Counter! -== APP == Instance Successfully Purged - -== APP == start_resp exampleInstanceID - -== APP == Hi Counter! -== APP == New counter value is: 1112! - -== APP == Hi Counter! -== APP == New counter value is: 1122! - -== APP == Get response from hello_world_wf after terminate call: Terminated -== APP == Get response from child_wf after terminate call: Terminated -== APP == Instance Successfully Purged -``` - -## What happened? - -When you ran `dapr run`, the Dapr client: -1. Registered the workflow (`hello_world_wf`) and its actvity (`hello_act`) -1. Started the workflow engine - -```python -def main(): - with DaprClient() as d: - host = settings.DAPR_RUNTIME_HOST - port = settings.DAPR_GRPC_PORT - workflowRuntime = WorkflowRuntime(host, port) - workflowRuntime = WorkflowRuntime() - workflowRuntime.register_workflow(hello_world_wf) - workflowRuntime.register_activity(hello_act) - workflowRuntime.start() - - print("==========Start Counter Increase as per Input:==========") - start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, - workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) - print(f"start_resp {start_resp.instance_id}") -``` - -Dapr then paused and resumed the workflow: - -```python - # Pause - d.pause_workflow(instance_id=instanceId, workflow_component=workflowComponent) - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - print(f"Get response from {workflowName} after pause call: {getResponse.runtime_status}") - - # Resume - d.resume_workflow(instance_id=instanceId, workflow_component=workflowComponent) - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - print(f"Get response from {workflowName} after resume call: {getResponse.runtime_status}") -``` - -Once the workflow resumed, Dapr raised a workflow event and printed the new counter value: - -```python - # Raise event - d.raise_workflow_event(instance_id=instanceId, workflow_component=workflowComponent, - event_name=eventName, event_data=eventData) -``` - -To clear out the workflow state from your state store, Dapr purged the workflow: - -```python - # Purge - d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) - try: - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - except DaprInternalError as err: - if nonExistentIDError in err._message: - print("Instance Successfully Purged") -``` - -The sample then demonstrated terminating a workflow by: -- Starting a new workflow using the same `instanceId` as the purged workflow. -- Terminating the workflow and purging before shutting down the workflow. - -```python - # Kick off another workflow - start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, - workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) - print(f"start_resp {start_resp.instance_id}") - - # Terminate - d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponent) - sleep(1) - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - print(f"Get response from {workflowName} after terminate call: {getResponse.runtime_status}") - - # Purge - d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) - try: - getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) - except DaprInternalError as err: - if nonExistentIDError in err._message: - print("Instance Successfully Purged") -``` - -## Next steps -- [Learn more about Dapr workflow]({{< ref workflow-overview.md >}}) -- [Workflow API reference]({{< ref workflow_api.md >}}) +--- +type: docs +title: "Getting started with the Dapr Workflow Python SDK" +linkTitle: "Workflow" +weight: 30000 +description: How to get up and running with workflows using the Dapr Python SDK +--- + +{{% alert title="Note" color="primary" %}} +Dapr Workflow is currently in alpha. +{{% /alert %}} + +Let’s create a Dapr workflow and invoke it using the console. With the [provided hello world workflow example](https://github.com/dapr/python-sdk/tree/master/examples/demo_workflow), you will: + +- Run a [Python console application using `DaprClient`](https://github.com/dapr/python-sdk/blob/master/examples/demo_workflow/app.py) +- Utilize the Python workflow SDK and API calls to start, pause, resume, terminate, and purge workflow instances + +This example uses the default configuration from `dapr init` in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). + +In the Python example project, the `app.py` file contains the setup of the app, including: +- The workflow definition +- The workflow activity definitions +- The registration of the workflow and workflow activities + +## Prerequisites +- [Dapr CLI]({{< ref install-dapr-cli.md >}}) installed +- Initialized [Dapr environment]({{< ref install-dapr-selfhost.md >}}) +- [Python 3.8+](https://www.python.org/downloads/) installed +- [Dapr Python package]({{< ref "python#installation" >}}) and the [workflow extension]({{< ref "python-workflow/_index.md" >}}) installed +- Verify you're using the latest proto bindings + +## Set up the environment + +Run the following command to install the requirements for running this workflow sample with the Dapr Python SDK. + +```bash +pip3 install -r demo_workflow/requirements.txt +``` + +Clone the [Python SDK repo]. + +```bash +git clone https://github.com/dapr/python-sdk.git +``` + +From the Python SDK root directory, navigate to the Dapr Workflow example. + +```bash +cd examples/demo_workflow +``` + +## Run the application locally + +To run the Dapr application, you need to start the Python program and a Dapr sidecar. In the terminal, run: + +```bash +dapr run --app-id orderapp --app-protocol grpc --dapr-grpc-port 50001 --resources-path components --placement-host-address localhost:50005 -- python3 app.py +``` + +> **Note:** Since Python3.exe is not defined in Windows, you may need to use `python app.py` instead of `python3 app.py`. + + +**Expected output** + +``` +== APP == ==========Start Counter Increase as per Input:========== + +== APP == start_resp exampleInstanceID + +== APP == Hi Counter! +== APP == New counter value is: 1! + +== APP == Hi Counter! +== APP == New counter value is: 11! + +== APP == Hi Counter! +== APP == Hi Counter! +== APP == Get response from hello_world_wf after pause call: Suspended + +== APP == Hi Counter! +== APP == Get response from hello_world_wf after resume call: Running + +== APP == Hi Counter! +== APP == New counter value is: 111! + +== APP == Hi Counter! +== APP == Instance Successfully Purged + +== APP == start_resp exampleInstanceID + +== APP == Hi Counter! +== APP == New counter value is: 1112! + +== APP == Hi Counter! +== APP == New counter value is: 1122! + +== APP == Get response from hello_world_wf after terminate call: Terminated +== APP == Get response from child_wf after terminate call: Terminated +== APP == Instance Successfully Purged +``` + +## What happened? + +When you ran `dapr run`, the Dapr client: +1. Registered the workflow (`hello_world_wf`) and its actvity (`hello_act`) +1. Started the workflow engine + +```python +def main(): + with DaprClient() as d: + host = settings.DAPR_RUNTIME_HOST + port = settings.DAPR_GRPC_PORT + workflowRuntime = WorkflowRuntime(host, port) + workflowRuntime = WorkflowRuntime() + workflowRuntime.register_workflow(hello_world_wf) + workflowRuntime.register_activity(hello_act) + workflowRuntime.start() + + print("==========Start Counter Increase as per Input:==========") + start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, + workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) + print(f"start_resp {start_resp.instance_id}") +``` + +Dapr then paused and resumed the workflow: + +```python + # Pause + d.pause_workflow(instance_id=instanceId, workflow_component=workflowComponent) + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + print(f"Get response from {workflowName} after pause call: {getResponse.runtime_status}") + + # Resume + d.resume_workflow(instance_id=instanceId, workflow_component=workflowComponent) + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + print(f"Get response from {workflowName} after resume call: {getResponse.runtime_status}") +``` + +Once the workflow resumed, Dapr raised a workflow event and printed the new counter value: + +```python + # Raise event + d.raise_workflow_event(instance_id=instanceId, workflow_component=workflowComponent, + event_name=eventName, event_data=eventData) +``` + +To clear out the workflow state from your state store, Dapr purged the workflow: + +```python + # Purge + d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) + try: + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + except DaprInternalError as err: + if nonExistentIDError in err._message: + print("Instance Successfully Purged") +``` + +The sample then demonstrated terminating a workflow by: +- Starting a new workflow using the same `instanceId` as the purged workflow. +- Terminating the workflow and purging before shutting down the workflow. + +```python + # Kick off another workflow + start_resp = d.start_workflow(instance_id=instanceId, workflow_component=workflowComponent, + workflow_name=workflowName, input=inputData, workflow_options=workflowOptions) + print(f"start_resp {start_resp.instance_id}") + + # Terminate + d.terminate_workflow(instance_id=instanceId, workflow_component=workflowComponent) + sleep(1) + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + print(f"Get response from {workflowName} after terminate call: {getResponse.runtime_status}") + + # Purge + d.purge_workflow(instance_id=instanceId, workflow_component=workflowComponent) + try: + getResponse = d.get_workflow(instance_id=instanceId, workflow_component=workflowComponent) + except DaprInternalError as err: + if nonExistentIDError in err._message: + print("Instance Successfully Purged") +``` + +## Next steps +- [Learn more about Dapr workflow]({{< ref workflow-overview.md >}}) +- [Workflow API reference]({{< ref workflow_api.md >}}) diff --git a/dev-requirements.txt b/dev-requirements.txt index 158667256..f4c65d16a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,18 +1,18 @@ -mypy>=1.2.0 -mypy-extensions>=0.4.3 -mypy-protobuf>=2.9 -flake8>=3.7.9 -tox>=4.3.0 -coverage>=5.3 -wheel -# used in unit test only -opentelemetry-sdk -opentelemetry-instrumentation-grpc -httpx>=0.24 -pyOpenSSL>=23.2.0 -# needed for type checking -Flask>=1.1 -# needed for auto fix -ruff===0.2.2 -# needed for dapr-ext-workflow -durabletask>=0.1.1a1 +mypy>=1.2.0 +mypy-extensions>=0.4.3 +mypy-protobuf>=2.9 +flake8>=3.7.9 +tox>=4.3.0 +coverage>=5.3 +wheel +# used in unit test only +opentelemetry-sdk +opentelemetry-instrumentation-grpc +httpx>=0.24 +pyOpenSSL>=23.2.0 +# needed for type checking +Flask>=1.1 +# needed for auto fix +ruff===0.2.2 +# needed for dapr-ext-workflow +durabletask>=0.1.1a1 diff --git a/docs/Makefile b/docs/Makefile index d4bb2cbb9..73a28c713 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,20 +1,20 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/actor/actor.client.rst b/docs/actor/actor.client.rst index daf42e476..c4d6cbf3e 100644 --- a/docs/actor/actor.client.rst +++ b/docs/actor/actor.client.rst @@ -1,20 +1,20 @@ -actor.client package -==================== - -Submodules ----------- - - -.. automodule:: actor.client.proxy - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: actor.client - :members: - :undoc-members: - :show-inheritance: +actor.client package +==================== + +Submodules +---------- + + +.. automodule:: actor.client.proxy + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: actor.client + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/actor/actor.rst b/docs/actor/actor.rst index 2db8addfd..d7847d544 100644 --- a/docs/actor/actor.rst +++ b/docs/actor/actor.rst @@ -1,35 +1,35 @@ -actor package -============= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - actor.client - actor.runtime - -Submodules ----------- - - -.. automodule:: actor.actor_interface - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.id - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: actor - :members: - :undoc-members: - :show-inheritance: +actor package +============= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + actor.client + actor.runtime + +Submodules +---------- + + +.. automodule:: actor.actor_interface + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.id + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: actor + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/actor/actor.runtime.rst b/docs/actor/actor.runtime.rst index ce88961d6..5ff8f9bd1 100644 --- a/docs/actor/actor.runtime.rst +++ b/docs/actor/actor.runtime.rst @@ -1,98 +1,98 @@ -actor.runtime package -===================== - -Submodules ----------- - - -.. automodule:: actor.runtime.actor - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime._call_type - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.config - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.context - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.manager - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime._method_context - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.method_dispatcher - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.remindable - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime._reminder_data - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.runtime - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.state_change - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime.state_manager - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime._state_provider - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: actor.runtime._timer_data - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: actor.runtime - :members: - :undoc-members: - :show-inheritance: +actor.runtime package +===================== + +Submodules +---------- + + +.. automodule:: actor.runtime.actor + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime._call_type + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.config + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.context + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.manager + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime._method_context + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.method_dispatcher + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.remindable + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime._reminder_data + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.runtime + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.state_change + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime.state_manager + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime._state_provider + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: actor.runtime._timer_data + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: actor.runtime + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/actor/modules.rst b/docs/actor/modules.rst index 30f2b912f..1cf9f2bd1 100644 --- a/docs/actor/modules.rst +++ b/docs/actor/modules.rst @@ -1,7 +1,7 @@ -actor -===== - -.. toctree:: - :maxdepth: 4 - - actor +actor +===== + +.. toctree:: + :maxdepth: 4 + + actor diff --git a/docs/clients/clients.http.rst b/docs/clients/clients.http.rst index b9ca97369..5c96e55a9 100644 --- a/docs/clients/clients.http.rst +++ b/docs/clients/clients.http.rst @@ -1,20 +1,20 @@ -clients.http package -==================== - -Submodules ----------- - - -.. automodule:: clients.http.dapr_actor_http_client - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: clients.http - :members: - :undoc-members: - :show-inheritance: +clients.http package +==================== + +Submodules +---------- + + +.. automodule:: clients.http.dapr_actor_http_client + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: clients.http + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/clients/clients.rst b/docs/clients/clients.rst index 277d71b57..17e15dcf6 100644 --- a/docs/clients/clients.rst +++ b/docs/clients/clients.rst @@ -1,34 +1,34 @@ -clients package -=============== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - clients.http - -Submodules ----------- - - -.. automodule:: clients.base - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: clients.exceptions - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: clients - :members: - :undoc-members: - :show-inheritance: +clients package +=============== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + clients.http + +Submodules +---------- + + +.. automodule:: clients.base + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: clients.exceptions + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: clients + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/clients/modules.rst b/docs/clients/modules.rst index a663de4c4..c3b98239b 100644 --- a/docs/clients/modules.rst +++ b/docs/clients/modules.rst @@ -1,7 +1,7 @@ -clients -======= - -.. toctree:: - :maxdepth: 4 - - clients +clients +======= + +.. toctree:: + :maxdepth: 4 + + clients diff --git a/docs/conf.py b/docs/conf.py index 50b67a0a0..332b713ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,70 +1,70 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - -sys.path.insert(0, os.path.abspath('../dapr')) - - -# -- Project information ----------------------------------------------------- - -project = 'dapr-python-sdk' -copyright = '2020, dapr' -author = 'dapr' - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.ifconfig', - 'sphinx.ext.napoleon', -] - -# Napoleon settings -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = True -napoleon_include_private_with_doc = True -napoleon_include_special_with_doc = True -napoleon_use_admonition_for_examples = False -napoleon_use_admonition_for_notes = False -napoleon_use_admonition_for_references = False -napoleon_use_ivar = False -napoleon_use_param = True -napoleon_use_rtype = True -autodoc_mock_imports = ['dapr'] -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'classic' - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath('../dapr')) + + +# -- Project information ----------------------------------------------------- + +project = 'dapr-python-sdk' +copyright = '2020, dapr' +author = 'dapr' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.ifconfig', + 'sphinx.ext.napoleon', +] + +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = True +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True +autodoc_mock_imports = ['dapr'] +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'classic' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst index 0f4531d2b..f81809ce2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,24 +1,24 @@ -.. dapr-python-sdk documentation master file, created by - sphinx-quickstart on Mon May 25 22:52:51 2020. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -============================ - Welcome to Dapr-Python-Sdk -============================ -.. toctree:: - :maxdepth: 4 - :caption: Contents: - - actor/modules - client/modules - proto/modules - serializers/modules - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. dapr-python-sdk documentation master file, created by + sphinx-quickstart on Mon May 25 22:52:51 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +============================ + Welcome to Dapr-Python-Sdk +============================ +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + actor/modules + client/modules + proto/modules + serializers/modules + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/proto/modules.rst b/docs/proto/modules.rst index 443cf338e..76139d9b5 100644 --- a/docs/proto/modules.rst +++ b/docs/proto/modules.rst @@ -1,7 +1,7 @@ -proto -===== - -.. toctree:: - :maxdepth: 4 - - proto +proto +===== + +.. toctree:: + :maxdepth: 4 + + proto diff --git a/docs/proto/proto.common.rst b/docs/proto/proto.common.rst index 582ca0d84..02547e18c 100644 --- a/docs/proto/proto.common.rst +++ b/docs/proto/proto.common.rst @@ -1,18 +1,18 @@ -proto.common package -==================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - proto.common.v1 - -Module contents ---------------- - -.. automodule:: proto.common - :members: - :undoc-members: - :show-inheritance: +proto.common package +==================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + proto.common.v1 + +Module contents +--------------- + +.. automodule:: proto.common + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/proto/proto.common.v1.rst b/docs/proto/proto.common.v1.rst index 3377d501e..a0e7d747f 100644 --- a/docs/proto/proto.common.v1.rst +++ b/docs/proto/proto.common.v1.rst @@ -1,26 +1,26 @@ -proto.common.v1 package -======================= - -Submodules ----------- - - -.. automodule:: proto.common.v1.common_pb2 - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: proto.common.v1.common_pb2_grpc - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: proto.common.v1 - :members: - :undoc-members: - :show-inheritance: +proto.common.v1 package +======================= + +Submodules +---------- + + +.. automodule:: proto.common.v1.common_pb2 + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: proto.common.v1.common_pb2_grpc + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: proto.common.v1 + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/proto/proto.dapr.rst b/docs/proto/proto.dapr.rst index 6de0fe035..3020282a1 100644 --- a/docs/proto/proto.dapr.rst +++ b/docs/proto/proto.dapr.rst @@ -1,18 +1,18 @@ -proto.dapr package -================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - proto.dapr.v1 - -Module contents ---------------- - -.. automodule:: proto.dapr - :members: - :undoc-members: - :show-inheritance: +proto.dapr package +================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + proto.dapr.v1 + +Module contents +--------------- + +.. automodule:: proto.dapr + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/proto/proto.dapr.v1.rst b/docs/proto/proto.dapr.v1.rst index 8e659b606..c02acba44 100644 --- a/docs/proto/proto.dapr.v1.rst +++ b/docs/proto/proto.dapr.v1.rst @@ -1,26 +1,26 @@ -proto.dapr.v1 package -===================== - -Submodules ----------- - - -.. automodule:: proto.dapr.v1.dapr_pb2 - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: proto.dapr.v1.dapr_pb2_grpc - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: proto.dapr.v1 - :members: - :undoc-members: - :show-inheritance: +proto.dapr.v1 package +===================== + +Submodules +---------- + + +.. automodule:: proto.dapr.v1.dapr_pb2 + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: proto.dapr.v1.dapr_pb2_grpc + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: proto.dapr.v1 + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/proto/proto.daprclient.rst b/docs/proto/proto.daprclient.rst index e2980373f..2f14d6d1e 100644 --- a/docs/proto/proto.daprclient.rst +++ b/docs/proto/proto.daprclient.rst @@ -1,18 +1,18 @@ -proto.daprclient package -======================== - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - proto.daprclient.v1 - -Module contents ---------------- - -.. automodule:: proto.daprclient - :members: - :undoc-members: - :show-inheritance: +proto.daprclient package +======================== + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + proto.daprclient.v1 + +Module contents +--------------- + +.. automodule:: proto.daprclient + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/proto/proto.daprclient.v1.rst b/docs/proto/proto.daprclient.v1.rst index 243579d9e..e8b69341d 100644 --- a/docs/proto/proto.daprclient.v1.rst +++ b/docs/proto/proto.daprclient.v1.rst @@ -1,26 +1,26 @@ -proto.daprclient.v1 package -=========================== - -Submodules ----------- - - -.. automodule:: proto.daprclient.v1.daprclient_pb2 - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: proto.daprclient.v1.daprclient_pb2_grpc - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: proto.daprclient.v1 - :members: - :undoc-members: - :show-inheritance: +proto.daprclient.v1 package +=========================== + +Submodules +---------- + + +.. automodule:: proto.daprclient.v1.daprclient_pb2 + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: proto.daprclient.v1.daprclient_pb2_grpc + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: proto.daprclient.v1 + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/proto/proto.rst b/docs/proto/proto.rst index dec03f1e0..96852cc8b 100644 --- a/docs/proto/proto.rst +++ b/docs/proto/proto.rst @@ -1,20 +1,20 @@ -proto package -============= - -Subpackages ------------ - -.. toctree:: - :maxdepth: 4 - - proto.common - proto.dapr - proto.daprclient - -Module contents ---------------- - -.. automodule:: proto - :members: - :undoc-members: - :show-inheritance: +proto package +============= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + proto.common + proto.dapr + proto.daprclient + +Module contents +--------------- + +.. automodule:: proto + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/serializers/modules.rst b/docs/serializers/modules.rst index e0d9eed09..ec5088ca3 100644 --- a/docs/serializers/modules.rst +++ b/docs/serializers/modules.rst @@ -1,7 +1,7 @@ -serializers -=========== - -.. toctree:: - :maxdepth: 4 - - serializers +serializers +=========== + +.. toctree:: + :maxdepth: 4 + + serializers diff --git a/docs/serializers/serializers.rst b/docs/serializers/serializers.rst index 088c06fc9..60fc67463 100644 --- a/docs/serializers/serializers.rst +++ b/docs/serializers/serializers.rst @@ -1,32 +1,32 @@ -serializers package -=================== - -Submodules ----------- - - -.. automodule:: serializers.base - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: serializers.json - :members: - :undoc-members: - :show-inheritance: - - -.. automodule:: serializers.util - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: serializers - :members: - :undoc-members: - :show-inheritance: +serializers package +=================== + +Submodules +---------- + + +.. automodule:: serializers.base + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: serializers.json + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: serializers.util + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: serializers + :members: + :undoc-members: + :show-inheritance: diff --git a/examples/README.md b/examples/README.md index c5f6604d7..0ca498209 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,22 +1,22 @@ -# Dapr Python SDK examples - -These examples demonstrate how to use the Dapr Python SDK: - -| Example | Description | -|-------------------------------------------------------|-------------| -| [Service invocation](./invoke-simple) | Invoke service by passing bytes data -| [Service invocation (advanced)](./invoke-custom-data) | Invoke service by using custom protobuf message -| [State management](./state_store) | Save and get state to/from the state store -| [Publish & subscribe](./pubsub-simple) | Publish and subscribe to events -| [Error handling](./error_handling) | Error handling -| [Bindings](./invoke-binding) | Invoke an output binding to interact with external resources -| [Virtual actors](./demo_actor) | Try Dapr virtual actor features -| [Secrets](./secret_store) | Get secrets from a defined secret store -| [Distributed tracing](./w3c-tracing) | Leverage Dapr's built-in tracing support -| [Distributed lock](./distributed_lock) | Keep your application safe from race conditions by using distributed locks -| [Workflow](./demo_workflow) | Run a workflow to simulate an order processor -| [Cryptography](./crypto) | Perform cryptographic operations without exposing keys to your application - -## More information - -- [Dapr Python SDK docs](https://docs.dapr.io/developing-applications/sdks/python) +# Dapr Python SDK examples + +These examples demonstrate how to use the Dapr Python SDK: + +| Example | Description | +|-------------------------------------------------------|-------------| +| [Service invocation](./invoke-simple) | Invoke service by passing bytes data +| [Service invocation (advanced)](./invoke-custom-data) | Invoke service by using custom protobuf message +| [State management](./state_store) | Save and get state to/from the state store +| [Publish & subscribe](./pubsub-simple) | Publish and subscribe to events +| [Error handling](./error_handling) | Error handling +| [Bindings](./invoke-binding) | Invoke an output binding to interact with external resources +| [Virtual actors](./demo_actor) | Try Dapr virtual actor features +| [Secrets](./secret_store) | Get secrets from a defined secret store +| [Distributed tracing](./w3c-tracing) | Leverage Dapr's built-in tracing support +| [Distributed lock](./distributed_lock) | Keep your application safe from race conditions by using distributed locks +| [Workflow](./demo_workflow) | Run a workflow to simulate an order processor +| [Cryptography](./crypto) | Perform cryptographic operations without exposing keys to your application + +## More information + +- [Dapr Python SDK docs](https://docs.dapr.io/developing-applications/sdks/python) diff --git a/examples/configuration/README.md b/examples/configuration/README.md index 3ecfddd85..03a6d90ab 100644 --- a/examples/configuration/README.md +++ b/examples/configuration/README.md @@ -1,81 +1,81 @@ -# Example - Get Configuration - -This example demonstrates the Configuration APIs in Dapr. -It demonstrates the following APIs: -- **configuration**: Get configuration from statestore - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Store the configuration in configurationstore - - -```bash -docker exec dapr_redis redis-cli SET orderId1 "100||1" -docker exec dapr_redis redis-cli SET orderId2 "200||1" -``` - - - -## Run configuration example - -Change directory to this folder: -```bash -cd examples/configuration -``` - -To run this example, use the following command: - - - -```bash -dapr run --app-id configexample --resources-path components/ -- python3 configuration.py -``` - - - - -```bash -docker exec dapr_redis redis-cli SET orderId2 "210||2" -``` - - -You should be able to see the following output: -``` -== APP == Got key=orderId1 value=100 version=1 -== APP == Got key=orderId2 value=200 version=1 -== APP == Subscribe key=orderId2 value=210 version=2 -``` +# Example - Get Configuration + +This example demonstrates the Configuration APIs in Dapr. +It demonstrates the following APIs: +- **configuration**: Get configuration from statestore + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Store the configuration in configurationstore + + +```bash +docker exec dapr_redis redis-cli SET orderId1 "100||1" +docker exec dapr_redis redis-cli SET orderId2 "200||1" +``` + + + +## Run configuration example + +Change directory to this folder: +```bash +cd examples/configuration +``` + +To run this example, use the following command: + + + +```bash +dapr run --app-id configexample --resources-path components/ -- python3 configuration.py +``` + + + + +```bash +docker exec dapr_redis redis-cli SET orderId2 "210||2" +``` + + +You should be able to see the following output: +``` +== APP == Got key=orderId1 value=100 version=1 +== APP == Got key=orderId2 value=200 version=1 +== APP == Subscribe key=orderId2 value=210 version=2 +``` diff --git a/examples/configuration/components/configurationstore.yaml b/examples/configuration/components/configurationstore.yaml index 85c6d2418..9a0c5c0ee 100644 --- a/examples/configuration/components/configurationstore.yaml +++ b/examples/configuration/components/configurationstore.yaml @@ -1,12 +1,12 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: configurationstore - namespace: default -spec: - type: configuration.redis - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: configurationstore + namespace: default +spec: + type: configuration.redis + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword value: "" \ No newline at end of file diff --git a/examples/configuration/configuration.py b/examples/configuration/configuration.py index caf676e6b..7b2db9be9 100644 --- a/examples/configuration/configuration.py +++ b/examples/configuration/configuration.py @@ -1,54 +1,54 @@ -""" -dapr run --app-id configexample --resources-path components/ -- python3 configuration.py -""" - -import asyncio -from time import sleep -from dapr.clients import DaprClient -from dapr.clients.grpc._response import ConfigurationWatcher, ConfigurationResponse - -configuration: ConfigurationWatcher = ConfigurationWatcher() - - -def handler(id: str, resp: ConfigurationResponse): - for key in resp.items: - print( - f'Subscribe key={key} value={resp.items[key].value} ' - f'version={resp.items[key].version} ' - f'metadata={resp.items[key].metadata}', - flush=True, - ) - - -async def executeConfiguration(): - with DaprClient() as d: - storeName = 'configurationstore' - - keys = ['orderId1', 'orderId2'] - - global configuration - - # Get one configuration by key. - configuration = d.get_configuration(store_name=storeName, keys=keys, config_metadata={}) - for key in configuration.items: - print( - f'Got key={key} ' - f'value={configuration.items[key].value} ' - f'version={configuration.items[key].version} ' - f'metadata={configuration.items[key].metadata}', - flush=True, - ) - - # Subscribe to configuration for keys {orderId1,orderId2}. - id = d.subscribe_configuration( - store_name=storeName, keys=keys, handler=handler, config_metadata={} - ) - print('Subscription ID is', id, flush=True) - sleep(10) - - # Unsubscribe from configuration - isSuccess = d.unsubscribe_configuration(store_name=storeName, id=id) - print(f'Unsubscribed successfully? {isSuccess}', flush=True) - - -asyncio.run(executeConfiguration()) +""" +dapr run --app-id configexample --resources-path components/ -- python3 configuration.py +""" + +import asyncio +from time import sleep +from dapr.clients import DaprClient +from dapr.clients.grpc._response import ConfigurationWatcher, ConfigurationResponse + +configuration: ConfigurationWatcher = ConfigurationWatcher() + + +def handler(id: str, resp: ConfigurationResponse): + for key in resp.items: + print( + f'Subscribe key={key} value={resp.items[key].value} ' + f'version={resp.items[key].version} ' + f'metadata={resp.items[key].metadata}', + flush=True, + ) + + +async def executeConfiguration(): + with DaprClient() as d: + storeName = 'configurationstore' + + keys = ['orderId1', 'orderId2'] + + global configuration + + # Get one configuration by key. + configuration = d.get_configuration(store_name=storeName, keys=keys, config_metadata={}) + for key in configuration.items: + print( + f'Got key={key} ' + f'value={configuration.items[key].value} ' + f'version={configuration.items[key].version} ' + f'metadata={configuration.items[key].metadata}', + flush=True, + ) + + # Subscribe to configuration for keys {orderId1,orderId2}. + id = d.subscribe_configuration( + store_name=storeName, keys=keys, handler=handler, config_metadata={} + ) + print('Subscription ID is', id, flush=True) + sleep(10) + + # Unsubscribe from configuration + isSuccess = d.unsubscribe_configuration(store_name=storeName, id=id) + print(f'Unsubscribed successfully? {isSuccess}', flush=True) + + +asyncio.run(executeConfiguration()) diff --git a/examples/crypto/README.md b/examples/crypto/README.md index 2202982a6..105ae2e86 100644 --- a/examples/crypto/README.md +++ b/examples/crypto/README.md @@ -1,121 +1,121 @@ -# Example - Cryptography - -This example demonstrates the [cryptography component] APIs in Dapr. -It demonstrates the following APIs: -- **encrypt**: Encrypt a string/file with keys from the local store -- **decrypt**: Decrypt a string/file with keys from the local store - -It creates a client using `DaprClient`, uses a local store defined in -[`./components/crypto-localstorage.yaml`](./components/crypto-localstorage.yaml) and invokes cryptography API methods available as example. - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -> In order to run this sample, make sure that OpenSSL is available on your system. - -### Run the example - -1. This sample requires a private RSA key and a 256-bit symmetric (AES) key. We will generate them using OpenSSL: - - - -```bash -mkdir -p keys -# Generate a private RSA key, 4096-bit keys -openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem -# Generate a 256-bit key for AES -openssl rand -out keys/symmetric-key-256 32 -``` - - - -2. Run the Python service app with Dapr - crypto: - - - -```bash -dapr run --app-id crypto --resources-path ./components/ -- python3 crypto.py -``` - - - -3. Run the Python service app with Dapr - async crypto: - - - -```bash -dapr run --app-id crypto-async --resources-path ./components/ -- python3 crypto-async.py -``` - - - -### Cleanup - - - -```bash -rm -r keys -rm encrypted.out -rm decrypted.out.jpg -``` - - - -## Result - -The output should be as follows: - -```shell -== APP == Running gRPC client synchronous API -== APP == Running encrypt/decrypt operation on string -== APP == Encrypted the message, got 856 bytes -== APP == Decrypted the message, got 24 bytes -== APP == b'The secret is "passw0rd"' -== APP == Running encrypt/decrypt operation on file -== APP == Wrote encrypted data to encrypted.out -== APP == Wrote decrypted data to decrypted.out.jpg -== APP == Running gRPC client asynchronous API -== APP == Running encrypt/decrypt operation on string -== APP == Encrypted the message, got 856 bytes -== APP == Decrypted the message, got 24 bytes -== APP == b'The secret is "passw0rd"' -== APP == Running encrypt/decrypt operation on file -== APP == Wrote encrypted data to encrypted.out -== APP == Wrote decrypted data to decrypted.out.jpg -``` +# Example - Cryptography + +This example demonstrates the [cryptography component] APIs in Dapr. +It demonstrates the following APIs: +- **encrypt**: Encrypt a string/file with keys from the local store +- **decrypt**: Decrypt a string/file with keys from the local store + +It creates a client using `DaprClient`, uses a local store defined in +[`./components/crypto-localstorage.yaml`](./components/crypto-localstorage.yaml) and invokes cryptography API methods available as example. + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +> In order to run this sample, make sure that OpenSSL is available on your system. + +### Run the example + +1. This sample requires a private RSA key and a 256-bit symmetric (AES) key. We will generate them using OpenSSL: + + + +```bash +mkdir -p keys +# Generate a private RSA key, 4096-bit keys +openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem +# Generate a 256-bit key for AES +openssl rand -out keys/symmetric-key-256 32 +``` + + + +2. Run the Python service app with Dapr - crypto: + + + +```bash +dapr run --app-id crypto --resources-path ./components/ -- python3 crypto.py +``` + + + +3. Run the Python service app with Dapr - async crypto: + + + +```bash +dapr run --app-id crypto-async --resources-path ./components/ -- python3 crypto-async.py +``` + + + +### Cleanup + + + +```bash +rm -r keys +rm encrypted.out +rm decrypted.out.jpg +``` + + + +## Result + +The output should be as follows: + +```shell +== APP == Running gRPC client synchronous API +== APP == Running encrypt/decrypt operation on string +== APP == Encrypted the message, got 856 bytes +== APP == Decrypted the message, got 24 bytes +== APP == b'The secret is "passw0rd"' +== APP == Running encrypt/decrypt operation on file +== APP == Wrote encrypted data to encrypted.out +== APP == Wrote decrypted data to decrypted.out.jpg +== APP == Running gRPC client asynchronous API +== APP == Running encrypt/decrypt operation on string +== APP == Encrypted the message, got 856 bytes +== APP == Decrypted the message, got 24 bytes +== APP == b'The secret is "passw0rd"' +== APP == Running encrypt/decrypt operation on file +== APP == Wrote encrypted data to encrypted.out +== APP == Wrote decrypted data to decrypted.out.jpg +``` diff --git a/examples/crypto/components/crypto-localstorage.yaml b/examples/crypto/components/crypto-localstorage.yaml index dc6debd96..580a9ee52 100644 --- a/examples/crypto/components/crypto-localstorage.yaml +++ b/examples/crypto/components/crypto-localstorage.yaml @@ -1,11 +1,11 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: crypto-localstorage -spec: - type: crypto.dapr.localstorage - version: v1 - metadata: - - name: path - # Path is relative to the folder where the example is located +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: crypto-localstorage +spec: + type: crypto.dapr.localstorage + version: v1 + metadata: + - name: path + # Path is relative to the folder where the example is located value: ./keys \ No newline at end of file diff --git a/examples/crypto/crypto-async.py b/examples/crypto/crypto-async.py index 0946e9bbb..8f37258fd 100644 --- a/examples/crypto/crypto-async.py +++ b/examples/crypto/crypto-async.py @@ -1,110 +1,110 @@ -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -import asyncio - -from dapr.aio.clients import DaprClient -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions - -# Name of the crypto component to use -CRYPTO_COMPONENT_NAME = 'crypto-localstorage' -# Name of the RSA private key to use -RSA_KEY_NAME = 'rsa-private-key.pem' -# Name of the symmetric (AES) key to use -SYMMETRIC_KEY_NAME = 'symmetric-key-256' - - -async def main(): - print('Running gRPC client asynchronous API') - - async with DaprClient() as dapr: - # Step 1: encrypt a string using the RSA key, then decrypt it and show the output in the terminal - print('Running encrypt/decrypt operation on string') - await encrypt_decrypt_string_async(dapr) - - # Step 2: encrypt a large file and then decrypt it, using the AES key - print('Running encrypt/decrypt operation on file') - await encrypt_decrypt_file_async(dapr) - - -async def encrypt_decrypt_string_async(dapr: DaprClient): - message = 'The secret is "passw0rd"' - - # Encrypt the message - resp = await dapr.encrypt( - data=message.encode(), - options=EncryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=RSA_KEY_NAME, - key_wrap_algorithm='RSA', - ), - ) - - # The method returns a readable stream, which we read in full in memory - encrypt_bytes = await resp.read() - print(f'Encrypted the message, got {len(encrypt_bytes)} bytes') - - # Decrypt the encrypted data - resp = await dapr.decrypt( - data=encrypt_bytes, - options=DecryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=RSA_KEY_NAME, - ), - ) - - # The method returns a readable stream, which we read in full in memory - decrypt_bytes = await resp.read() - print(f'Decrypted the message, got {len(decrypt_bytes)} bytes') - - print(decrypt_bytes.decode()) - assert message == decrypt_bytes.decode() - - -async def encrypt_decrypt_file_async(dapr: DaprClient): - file_name = 'desert.jpg' - - # Encrypt the file - with open(file_name, 'r+b') as target_file: - encrypt_stream = await dapr.encrypt( - data=target_file.read(), - options=EncryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=SYMMETRIC_KEY_NAME, - key_wrap_algorithm='AES', - ), - ) - - # Write the encrypted data to a file "encrypted.out" - with open('encrypted.out', 'w+b') as encrypted_file: - encrypted_file.write(await encrypt_stream.read()) - print('Wrote encrypted data to encrypted.out') - - # Decrypt the encrypted data - with open('encrypted.out', 'r+b') as encrypted_file: - decrypt_stream = await dapr.decrypt( - data=encrypted_file.read(), - options=DecryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=SYMMETRIC_KEY_NAME, - ), - ) - - # Write the decrypted data to a file "decrypted.out.jpg" - with open('decrypted.out.jpg', 'w+b') as decrypted_file: - decrypted_file.write(await decrypt_stream.read()) - print('Wrote decrypted data to decrypted.out.jpg') - - -if __name__ == '__main__': - asyncio.run(main()) +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +import asyncio + +from dapr.aio.clients import DaprClient +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions + +# Name of the crypto component to use +CRYPTO_COMPONENT_NAME = 'crypto-localstorage' +# Name of the RSA private key to use +RSA_KEY_NAME = 'rsa-private-key.pem' +# Name of the symmetric (AES) key to use +SYMMETRIC_KEY_NAME = 'symmetric-key-256' + + +async def main(): + print('Running gRPC client asynchronous API') + + async with DaprClient() as dapr: + # Step 1: encrypt a string using the RSA key, then decrypt it and show the output in the terminal + print('Running encrypt/decrypt operation on string') + await encrypt_decrypt_string_async(dapr) + + # Step 2: encrypt a large file and then decrypt it, using the AES key + print('Running encrypt/decrypt operation on file') + await encrypt_decrypt_file_async(dapr) + + +async def encrypt_decrypt_string_async(dapr: DaprClient): + message = 'The secret is "passw0rd"' + + # Encrypt the message + resp = await dapr.encrypt( + data=message.encode(), + options=EncryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=RSA_KEY_NAME, + key_wrap_algorithm='RSA', + ), + ) + + # The method returns a readable stream, which we read in full in memory + encrypt_bytes = await resp.read() + print(f'Encrypted the message, got {len(encrypt_bytes)} bytes') + + # Decrypt the encrypted data + resp = await dapr.decrypt( + data=encrypt_bytes, + options=DecryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=RSA_KEY_NAME, + ), + ) + + # The method returns a readable stream, which we read in full in memory + decrypt_bytes = await resp.read() + print(f'Decrypted the message, got {len(decrypt_bytes)} bytes') + + print(decrypt_bytes.decode()) + assert message == decrypt_bytes.decode() + + +async def encrypt_decrypt_file_async(dapr: DaprClient): + file_name = 'desert.jpg' + + # Encrypt the file + with open(file_name, 'r+b') as target_file: + encrypt_stream = await dapr.encrypt( + data=target_file.read(), + options=EncryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=SYMMETRIC_KEY_NAME, + key_wrap_algorithm='AES', + ), + ) + + # Write the encrypted data to a file "encrypted.out" + with open('encrypted.out', 'w+b') as encrypted_file: + encrypted_file.write(await encrypt_stream.read()) + print('Wrote encrypted data to encrypted.out') + + # Decrypt the encrypted data + with open('encrypted.out', 'r+b') as encrypted_file: + decrypt_stream = await dapr.decrypt( + data=encrypted_file.read(), + options=DecryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=SYMMETRIC_KEY_NAME, + ), + ) + + # Write the decrypted data to a file "decrypted.out.jpg" + with open('decrypted.out.jpg', 'w+b') as decrypted_file: + decrypted_file.write(await decrypt_stream.read()) + print('Wrote decrypted data to decrypted.out.jpg') + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/examples/crypto/crypto.py b/examples/crypto/crypto.py index a282ba453..493b60114 100644 --- a/examples/crypto/crypto.py +++ b/examples/crypto/crypto.py @@ -1,108 +1,108 @@ -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -from dapr.clients import DaprClient -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions - -# Name of the crypto component to use -CRYPTO_COMPONENT_NAME = 'crypto-localstorage' -# Name of the RSA private key to use -RSA_KEY_NAME = 'rsa-private-key.pem' -# Name of the symmetric (AES) key to use -SYMMETRIC_KEY_NAME = 'symmetric-key-256' - - -def main(): - print('Running gRPC client synchronous API') - - with DaprClient() as dapr: - # Step 1: encrypt a string using the RSA key, then decrypt it and show the output in the terminal - print('Running encrypt/decrypt operation on string') - encrypt_decrypt_string(dapr) - - # Step 2: encrypt a large file and then decrypt it, using the AES key - print('Running encrypt/decrypt operation on file') - encrypt_decrypt_file(dapr) - - -def encrypt_decrypt_string(dapr: DaprClient): - message = 'The secret is "passw0rd"' - - # Encrypt the message - resp = dapr.encrypt( - data=message.encode(), - options=EncryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=RSA_KEY_NAME, - key_wrap_algorithm='RSA', - ), - ) - - # The method returns a readable stream, which we read in full in memory - encrypt_bytes = resp.read() - print(f'Encrypted the message, got {len(encrypt_bytes)} bytes') - - # Decrypt the encrypted data - resp = dapr.decrypt( - data=encrypt_bytes, - options=DecryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=RSA_KEY_NAME, - ), - ) - - # The method returns a readable stream, which we read in full in memory - decrypt_bytes = resp.read() - print(f'Decrypted the message, got {len(decrypt_bytes)} bytes') - - print(decrypt_bytes.decode()) - assert message == decrypt_bytes.decode() - - -def encrypt_decrypt_file(dapr: DaprClient): - file_name = 'desert.jpg' - - # Encrypt the file - with open(file_name, 'r+b') as target_file: - encrypt_stream = dapr.encrypt( - data=target_file.read(), - options=EncryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=SYMMETRIC_KEY_NAME, - key_wrap_algorithm='AES', - ), - ) - - # Write the encrypted data to a file "encrypted.out" - with open('encrypted.out', 'w+b') as encrypted_file: - encrypted_file.write(encrypt_stream.read()) - print('Wrote encrypted data to encrypted.out') - - # Decrypt the encrypted data - with open('encrypted.out', 'r+b') as encrypted_file: - decrypt_stream = dapr.decrypt( - data=encrypted_file.read(), - options=DecryptOptions( - component_name=CRYPTO_COMPONENT_NAME, - key_name=SYMMETRIC_KEY_NAME, - ), - ) - - # Write the decrypted data to a file "decrypted.out.jpg" - with open('decrypted.out.jpg', 'w+b') as decrypted_file: - decrypted_file.write(decrypt_stream.read()) - print('Wrote decrypted data to decrypted.out.jpg') - - -if __name__ == '__main__': - main() +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +from dapr.clients import DaprClient +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions + +# Name of the crypto component to use +CRYPTO_COMPONENT_NAME = 'crypto-localstorage' +# Name of the RSA private key to use +RSA_KEY_NAME = 'rsa-private-key.pem' +# Name of the symmetric (AES) key to use +SYMMETRIC_KEY_NAME = 'symmetric-key-256' + + +def main(): + print('Running gRPC client synchronous API') + + with DaprClient() as dapr: + # Step 1: encrypt a string using the RSA key, then decrypt it and show the output in the terminal + print('Running encrypt/decrypt operation on string') + encrypt_decrypt_string(dapr) + + # Step 2: encrypt a large file and then decrypt it, using the AES key + print('Running encrypt/decrypt operation on file') + encrypt_decrypt_file(dapr) + + +def encrypt_decrypt_string(dapr: DaprClient): + message = 'The secret is "passw0rd"' + + # Encrypt the message + resp = dapr.encrypt( + data=message.encode(), + options=EncryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=RSA_KEY_NAME, + key_wrap_algorithm='RSA', + ), + ) + + # The method returns a readable stream, which we read in full in memory + encrypt_bytes = resp.read() + print(f'Encrypted the message, got {len(encrypt_bytes)} bytes') + + # Decrypt the encrypted data + resp = dapr.decrypt( + data=encrypt_bytes, + options=DecryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=RSA_KEY_NAME, + ), + ) + + # The method returns a readable stream, which we read in full in memory + decrypt_bytes = resp.read() + print(f'Decrypted the message, got {len(decrypt_bytes)} bytes') + + print(decrypt_bytes.decode()) + assert message == decrypt_bytes.decode() + + +def encrypt_decrypt_file(dapr: DaprClient): + file_name = 'desert.jpg' + + # Encrypt the file + with open(file_name, 'r+b') as target_file: + encrypt_stream = dapr.encrypt( + data=target_file.read(), + options=EncryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=SYMMETRIC_KEY_NAME, + key_wrap_algorithm='AES', + ), + ) + + # Write the encrypted data to a file "encrypted.out" + with open('encrypted.out', 'w+b') as encrypted_file: + encrypted_file.write(encrypt_stream.read()) + print('Wrote encrypted data to encrypted.out') + + # Decrypt the encrypted data + with open('encrypted.out', 'r+b') as encrypted_file: + decrypt_stream = dapr.decrypt( + data=encrypted_file.read(), + options=DecryptOptions( + component_name=CRYPTO_COMPONENT_NAME, + key_name=SYMMETRIC_KEY_NAME, + ), + ) + + # Write the decrypted data to a file "decrypted.out.jpg" + with open('decrypted.out.jpg', 'w+b') as decrypted_file: + decrypted_file.write(decrypt_stream.read()) + print('Wrote decrypted data to decrypted.out.jpg') + + +if __name__ == '__main__': + main() diff --git a/examples/demo_actor/README.md b/examples/demo_actor/README.md index c5ece71ea..4767b5d16 100644 --- a/examples/demo_actor/README.md +++ b/examples/demo_actor/README.md @@ -1,185 +1,185 @@ -# Example - Dapr virtual actors - -This document describes how to create an Actor(DemoActor) and invoke its methods on the client application. - -- **The actor interface(demo_actor_interface.py).** This contains the interface definition for the actor. Actor interfaces can be defined with any name. The interface defines the actor contract that is shared by the actor implementation and the clients calling the actor. Because client may depend on it, it typically makes sense to define it in an assembly that is separate from the actor implementation. -- **The actor service(demo_actor_service.py).** This implements FastAPI service that is going to host the actor. It contains the implementation of the actor, `demo_actor.py`. An actor implementation is a class that derives from the base type `Actor` and implements the interfaces defined in `demo_actor_interface.py`. -- **The actor service for flask(demo_actor_flask.py).** This implements Flask web service that is going to host the actor. -- **The actor client(demo_actor_client.py)** This contains the implementation of the actor client which calls DemoActor's method defined in Actor Interfaces. - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -### Install requirements - -You can install dapr SDK package using pip command: - - - -```sh -pip3 install -r demo_actor/requirements.txt -``` - - - -## Run in self-hosted mode - - - -1. Run Demo Actor service in new terminal window - - - ```bash - cd demo_actor - dapr run --app-id demo-actor --app-port 3000 -- uvicorn --port 3000 demo_actor_service:app - ``` - - Expected output: - ``` - ... - == APP == Activate DemoActor actor! - == APP == has_value: False - == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK - == APP == has_value: False - == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK - == APP == set_my_data: {'data': 'new_data'} - == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetMyData HTTP/1.1" 200 OK - == APP == has_value: True - == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK - == APP == set reminder to True - == APP == set reminder is done - == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetReminder HTTP/1.1" 200 OK - == APP == set_timer to True - == APP == set_timer is done - == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetTimer HTTP/1.1" 200 OK - == APP == receive_reminder is called - demo_reminder reminder - b'reminder_state' - == APP == clear_my_data - ... - ``` - - - - - -2. Run Demo client in new terminal window - - - ```bash - # Run actor client - cd demo_actor - dapr run --app-id demo-client python3 demo_actor_client.py - ``` - - Expected output: - ``` - ... - == APP == call actor method via proxy.invoke_method() - == APP == b'null' - == APP == call actor method using rpc style - == APP == None - == APP == Actor reentrancy enabled: True - == APP == call SetMyData actor method to save the state - == APP == call GetMyData actor method to get the state - == APP == {'data': 'new_data', 'ts': datetime.datetime(2020, 11, 13, 0, 38, 36, 163000, tzinfo=tzutc())} - == APP == Register reminder - == APP == Register timer - == APP == waiting for 30 seconds - == APP == stop reminder - == APP == stop timer - == APP == clear actor state - ``` - - - -## Run DemoActor on Kubernetes - -1. Build and push docker image - - ``` - $ cd examples/demo_actor/demo_actor - $ docker build -t [docker registry]/demo_actor:latest . - $ docker push [docker registry]/demo_actor:latest - $ cd .. - ``` - -> For example, [docker registry] is docker hub account. - -2. Follow [these steps](https://docs.dapr.io/getting-started/tutorials/configure-state-pubsub/#step-1-create-a-redis-store) to create a Redis store. - -3. Once your store is created, confirm validate `redis.yml` file in the `deploy` directory. - > **Note:** the `redis.yml` uses the secret created by `bitmany/redis` Helm chat to securely inject the password. - -4. Apply the `redis.yml` file: `kubectl apply -f ./deploy/redis.yml` and observe that your state store was successfully configured! - - ```bash - component.dapr.io/statestore configured - ``` - -5. Update docker image location in `./deploy/demo_actor_client.yml` and `./deploy/demo_actor_service.yml` - -6. Deploy actor service and clients - - ``` - kubectl apply -f ./deploy/demo_actor_service.yml - kubectl apply -f ./deploy/demo_actor_client.yml - ``` - -7. See logs for actor service and client - - Logs for actor service sidecar: - ``` - dapr logs -a demoactor -k - ``` - - Logs for actor service app: - ``` - kubectl logs -l app="demoactor" -c demoactor - ``` - - Logs for actor client sidecar: - ``` - dapr logs -a demoactor-client -k - ``` - - Logs for actor service app: - ``` - kubectl logs -l app="demoactor-client" -c demoactor-client - ``` - +# Example - Dapr virtual actors + +This document describes how to create an Actor(DemoActor) and invoke its methods on the client application. + +- **The actor interface(demo_actor_interface.py).** This contains the interface definition for the actor. Actor interfaces can be defined with any name. The interface defines the actor contract that is shared by the actor implementation and the clients calling the actor. Because client may depend on it, it typically makes sense to define it in an assembly that is separate from the actor implementation. +- **The actor service(demo_actor_service.py).** This implements FastAPI service that is going to host the actor. It contains the implementation of the actor, `demo_actor.py`. An actor implementation is a class that derives from the base type `Actor` and implements the interfaces defined in `demo_actor_interface.py`. +- **The actor service for flask(demo_actor_flask.py).** This implements Flask web service that is going to host the actor. +- **The actor client(demo_actor_client.py)** This contains the implementation of the actor client which calls DemoActor's method defined in Actor Interfaces. + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +### Install requirements + +You can install dapr SDK package using pip command: + + + +```sh +pip3 install -r demo_actor/requirements.txt +``` + + + +## Run in self-hosted mode + + + +1. Run Demo Actor service in new terminal window + + + ```bash + cd demo_actor + dapr run --app-id demo-actor --app-port 3000 -- uvicorn --port 3000 demo_actor_service:app + ``` + + Expected output: + ``` + ... + == APP == Activate DemoActor actor! + == APP == has_value: False + == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK + == APP == has_value: False + == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK + == APP == set_my_data: {'data': 'new_data'} + == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetMyData HTTP/1.1" 200 OK + == APP == has_value: True + == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/GetMyData HTTP/1.1" 200 OK + == APP == set reminder to True + == APP == set reminder is done + == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetReminder HTTP/1.1" 200 OK + == APP == set_timer to True + == APP == set_timer is done + == APP == INFO: 127.0.0.1:50739 - "PUT /actors/DemoActor/1/method/SetTimer HTTP/1.1" 200 OK + == APP == receive_reminder is called - demo_reminder reminder - b'reminder_state' + == APP == clear_my_data + ... + ``` + + + + + +2. Run Demo client in new terminal window + + + ```bash + # Run actor client + cd demo_actor + dapr run --app-id demo-client python3 demo_actor_client.py + ``` + + Expected output: + ``` + ... + == APP == call actor method via proxy.invoke_method() + == APP == b'null' + == APP == call actor method using rpc style + == APP == None + == APP == Actor reentrancy enabled: True + == APP == call SetMyData actor method to save the state + == APP == call GetMyData actor method to get the state + == APP == {'data': 'new_data', 'ts': datetime.datetime(2020, 11, 13, 0, 38, 36, 163000, tzinfo=tzutc())} + == APP == Register reminder + == APP == Register timer + == APP == waiting for 30 seconds + == APP == stop reminder + == APP == stop timer + == APP == clear actor state + ``` + + + +## Run DemoActor on Kubernetes + +1. Build and push docker image + + ``` + $ cd examples/demo_actor/demo_actor + $ docker build -t [docker registry]/demo_actor:latest . + $ docker push [docker registry]/demo_actor:latest + $ cd .. + ``` + +> For example, [docker registry] is docker hub account. + +2. Follow [these steps](https://docs.dapr.io/getting-started/tutorials/configure-state-pubsub/#step-1-create-a-redis-store) to create a Redis store. + +3. Once your store is created, confirm validate `redis.yml` file in the `deploy` directory. + > **Note:** the `redis.yml` uses the secret created by `bitmany/redis` Helm chat to securely inject the password. + +4. Apply the `redis.yml` file: `kubectl apply -f ./deploy/redis.yml` and observe that your state store was successfully configured! + + ```bash + component.dapr.io/statestore configured + ``` + +5. Update docker image location in `./deploy/demo_actor_client.yml` and `./deploy/demo_actor_service.yml` + +6. Deploy actor service and clients + + ``` + kubectl apply -f ./deploy/demo_actor_service.yml + kubectl apply -f ./deploy/demo_actor_client.yml + ``` + +7. See logs for actor service and client + + Logs for actor service sidecar: + ``` + dapr logs -a demoactor -k + ``` + + Logs for actor service app: + ``` + kubectl logs -l app="demoactor" -c demoactor + ``` + + Logs for actor client sidecar: + ``` + dapr logs -a demoactor-client -k + ``` + + Logs for actor service app: + ``` + kubectl logs -l app="demoactor-client" -c demoactor-client + ``` + diff --git a/examples/demo_actor/demo_actor/Dockerfile b/examples/demo_actor/demo_actor/Dockerfile index 22a42d259..26a9c3cb9 100644 --- a/examples/demo_actor/demo_actor/Dockerfile +++ b/examples/demo_actor/demo_actor/Dockerfile @@ -1,20 +1,20 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -FROM python:3.9-slim-buster - -WORKDIR /app -COPY . . - -RUN pip install -r requirements.txt - -ENTRYPOINT ["python"] -CMD ["demo_actor_service.py"] +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM python:3.9-slim-buster + +WORKDIR /app +COPY . . + +RUN pip install -r requirements.txt + +ENTRYPOINT ["python"] +CMD ["demo_actor_service.py"] diff --git a/examples/demo_actor/demo_actor/demo_actor.py b/examples/demo_actor/demo_actor/demo_actor.py index 0d65d57d2..300b50e00 100644 --- a/examples/demo_actor/demo_actor/demo_actor.py +++ b/examples/demo_actor/demo_actor/demo_actor.py @@ -1,128 +1,128 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import datetime - -from dapr.actor import Actor, Remindable -from demo_actor_interface import DemoActorInterface -from typing import Optional - - -class DemoActor(Actor, DemoActorInterface, Remindable): - """Implements DemoActor actor service - - This shows the usage of the below actor features: - - 1. Actor method invocation - 2. Actor state store management - 3. Actor reminder - 4. Actor timer - """ - - def __init__(self, ctx, actor_id): - super(DemoActor, self).__init__(ctx, actor_id) - - async def _on_activate(self) -> None: - """An callback which will be called whenever actor is activated.""" - print(f'Activate {self.__class__.__name__} actor!', flush=True) - - async def _on_deactivate(self) -> None: - """An callback which will be called whenever actor is deactivated.""" - print(f'Deactivate {self.__class__.__name__} actor!', flush=True) - - async def get_my_data(self) -> object: - """An actor method which gets mydata state value.""" - has_value, val = await self._state_manager.try_get_state('mydata') - print(f'has_value: {has_value}', flush=True) - return val - - async def set_my_data(self, data) -> None: - """An actor method which set mydata state value.""" - print(f'set_my_data: {data}', flush=True) - data['ts'] = datetime.datetime.now(datetime.timezone.utc) - await self._state_manager.set_state('mydata', data) - await self._state_manager.save_state() - - async def clear_my_data(self) -> None: - print('clear_my_data', flush=True) - await self._state_manager.remove_state('mydata') - await self._state_manager.save_state() - - async def set_reminder(self, enabled) -> None: - """Enables and disables a reminder. - - Args: - enabled (bool): the flag to enable and disable demo_reminder. - """ - print(f'set reminder to {enabled}', flush=True) - if enabled: - # Register 'demo_reminder' reminder and call receive_reminder method - await self.register_reminder( - 'demo_reminder', # reminder name - b'reminder_state', # user_state (bytes) - # The amount of time to delay before firing the reminder - datetime.timedelta(seconds=5), - datetime.timedelta(seconds=5), # The time interval between firing of reminders - datetime.timedelta(seconds=5), - ) - else: - # Unregister 'demo_reminder' - await self.unregister_reminder('demo_reminder') - print('set reminder is done', flush=True) - - async def set_timer(self, enabled) -> None: - """Enables and disables a timer. - - Args: - enabled (bool): the flag to enable and disable demo_timer. - """ - print(f'set_timer to {enabled}', flush=True) - if enabled: - # Register 'demo_timer' timer and call timer_callback method - await self.register_timer( - 'demo_timer', # timer name - self.timer_callback, # Callback method - 'timer_state', # Parameter to pass to the callback method - # Amount of time to delay before the callback is invoked - datetime.timedelta(seconds=5), - datetime.timedelta(seconds=5), # Time interval between invocations - datetime.timedelta(seconds=5), - ) - else: - # Unregister 'demo_timer' - await self.unregister_timer('demo_timer') - print('set_timer is done', flush=True) - - async def timer_callback(self, state) -> None: - """A callback which will be called whenever timer is triggered. - - Args: - state (object): an object which is defined when timer is registered. - """ - print(f'time_callback is called - {state}', flush=True) - - async def receive_reminder( - self, - name: str, - state: bytes, - due_time: datetime.timedelta, - period: datetime.timedelta, - ttl: Optional[datetime.timedelta] = None, - ) -> None: - """A callback which will be called when reminder is triggered.""" - print(f'receive_reminder is called - {name} reminder - {str(state)}', flush=True) - - async def get_reentrancy_status(self) -> bool: - """For Testing Only: An actor method which gets reentrancy status.""" - from dapr.actor.runtime.reentrancy_context import reentrancy_ctx - - return reentrancy_ctx.get(None) is not None +# -*- coding: utf-8 -*- +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +from dapr.actor import Actor, Remindable +from demo_actor_interface import DemoActorInterface +from typing import Optional + + +class DemoActor(Actor, DemoActorInterface, Remindable): + """Implements DemoActor actor service + + This shows the usage of the below actor features: + + 1. Actor method invocation + 2. Actor state store management + 3. Actor reminder + 4. Actor timer + """ + + def __init__(self, ctx, actor_id): + super(DemoActor, self).__init__(ctx, actor_id) + + async def _on_activate(self) -> None: + """An callback which will be called whenever actor is activated.""" + print(f'Activate {self.__class__.__name__} actor!', flush=True) + + async def _on_deactivate(self) -> None: + """An callback which will be called whenever actor is deactivated.""" + print(f'Deactivate {self.__class__.__name__} actor!', flush=True) + + async def get_my_data(self) -> object: + """An actor method which gets mydata state value.""" + has_value, val = await self._state_manager.try_get_state('mydata') + print(f'has_value: {has_value}', flush=True) + return val + + async def set_my_data(self, data) -> None: + """An actor method which set mydata state value.""" + print(f'set_my_data: {data}', flush=True) + data['ts'] = datetime.datetime.now(datetime.timezone.utc) + await self._state_manager.set_state('mydata', data) + await self._state_manager.save_state() + + async def clear_my_data(self) -> None: + print('clear_my_data', flush=True) + await self._state_manager.remove_state('mydata') + await self._state_manager.save_state() + + async def set_reminder(self, enabled) -> None: + """Enables and disables a reminder. + + Args: + enabled (bool): the flag to enable and disable demo_reminder. + """ + print(f'set reminder to {enabled}', flush=True) + if enabled: + # Register 'demo_reminder' reminder and call receive_reminder method + await self.register_reminder( + 'demo_reminder', # reminder name + b'reminder_state', # user_state (bytes) + # The amount of time to delay before firing the reminder + datetime.timedelta(seconds=5), + datetime.timedelta(seconds=5), # The time interval between firing of reminders + datetime.timedelta(seconds=5), + ) + else: + # Unregister 'demo_reminder' + await self.unregister_reminder('demo_reminder') + print('set reminder is done', flush=True) + + async def set_timer(self, enabled) -> None: + """Enables and disables a timer. + + Args: + enabled (bool): the flag to enable and disable demo_timer. + """ + print(f'set_timer to {enabled}', flush=True) + if enabled: + # Register 'demo_timer' timer and call timer_callback method + await self.register_timer( + 'demo_timer', # timer name + self.timer_callback, # Callback method + 'timer_state', # Parameter to pass to the callback method + # Amount of time to delay before the callback is invoked + datetime.timedelta(seconds=5), + datetime.timedelta(seconds=5), # Time interval between invocations + datetime.timedelta(seconds=5), + ) + else: + # Unregister 'demo_timer' + await self.unregister_timer('demo_timer') + print('set_timer is done', flush=True) + + async def timer_callback(self, state) -> None: + """A callback which will be called whenever timer is triggered. + + Args: + state (object): an object which is defined when timer is registered. + """ + print(f'time_callback is called - {state}', flush=True) + + async def receive_reminder( + self, + name: str, + state: bytes, + due_time: datetime.timedelta, + period: datetime.timedelta, + ttl: Optional[datetime.timedelta] = None, + ) -> None: + """A callback which will be called when reminder is triggered.""" + print(f'receive_reminder is called - {name} reminder - {str(state)}', flush=True) + + async def get_reentrancy_status(self) -> bool: + """For Testing Only: An actor method which gets reentrancy status.""" + from dapr.actor.runtime.reentrancy_context import reentrancy_ctx + + return reentrancy_ctx.get(None) is not None diff --git a/examples/demo_actor/demo_actor/demo_actor_client.py b/examples/demo_actor/demo_actor/demo_actor_client.py index df0e9f737..448e3912d 100644 --- a/examples/demo_actor/demo_actor/demo_actor_client.py +++ b/examples/demo_actor/demo_actor/demo_actor_client.py @@ -1,80 +1,80 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import asyncio - -from dapr.actor import ActorProxy, ActorId, ActorProxyFactory -from dapr.clients.retry import RetryPolicy -from demo_actor_interface import DemoActorInterface - - -async def main(): - # Create proxy client - factory = ActorProxyFactory(retry_policy=RetryPolicy(max_attempts=3)) - proxy = ActorProxy.create('DemoActor', ActorId('1'), DemoActorInterface, factory) - - # ----------------------------------------------- - # Actor invocation demo - # ----------------------------------------------- - # non-remoting actor invocation - print('call actor method via proxy.invoke_method()', flush=True) - rtn_bytes = await proxy.invoke_method('GetMyData') - print(rtn_bytes, flush=True) - # RPC style using python duck-typing - print('call actor method using rpc style', flush=True) - rtn_obj = await proxy.GetMyData() - print(rtn_obj, flush=True) - # Check actor is reentrant - is_reentrant = await proxy.invoke_method('GetReentrancyStatus') - print(f'Actor reentrancy enabled: {str(is_reentrant)}', flush=True) - - # ----------------------------------------------- - # Actor state management demo - # ----------------------------------------------- - # Invoke SetMyData actor method to save the state - print('call SetMyData actor method to save the state', flush=True) - await proxy.SetMyData({'data': 'new_data'}) - # Invoke GetMyData actor method to get the state - print('call GetMyData actor method to get the state', flush=True) - rtn_obj = await proxy.GetMyData() - print(rtn_obj, flush=True) - - # ----------------------------------------------- - # Actor reminder demo - # ----------------------------------------------- - # Invoke SetReminder actor method to set actor reminder - print('Register reminder', flush=True) - await proxy.SetReminder(True) - - # ----------------------------------------------- - # Actor timer demo - # ----------------------------------------------- - # Invoke SetTimer to set actor timer - print('Register timer', flush=True) - await proxy.SetTimer(True) - - # Wait for 30 seconds to see reminder and timer is triggered - print('waiting for 30 seconds', flush=True) - await asyncio.sleep(30) - - # Stop reminder and timer - print('stop reminder', flush=True) - await proxy.SetReminder(False) - print('stop timer', flush=True) - await proxy.SetTimer(False) - - # Clear actor state - print('clear actor state', flush=True) - await proxy.ClearMyData() - - -asyncio.run(main()) +# -*- coding: utf-8 -*- +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio + +from dapr.actor import ActorProxy, ActorId, ActorProxyFactory +from dapr.clients.retry import RetryPolicy +from demo_actor_interface import DemoActorInterface + + +async def main(): + # Create proxy client + factory = ActorProxyFactory(retry_policy=RetryPolicy(max_attempts=3)) + proxy = ActorProxy.create('DemoActor', ActorId('1'), DemoActorInterface, factory) + + # ----------------------------------------------- + # Actor invocation demo + # ----------------------------------------------- + # non-remoting actor invocation + print('call actor method via proxy.invoke_method()', flush=True) + rtn_bytes = await proxy.invoke_method('GetMyData') + print(rtn_bytes, flush=True) + # RPC style using python duck-typing + print('call actor method using rpc style', flush=True) + rtn_obj = await proxy.GetMyData() + print(rtn_obj, flush=True) + # Check actor is reentrant + is_reentrant = await proxy.invoke_method('GetReentrancyStatus') + print(f'Actor reentrancy enabled: {str(is_reentrant)}', flush=True) + + # ----------------------------------------------- + # Actor state management demo + # ----------------------------------------------- + # Invoke SetMyData actor method to save the state + print('call SetMyData actor method to save the state', flush=True) + await proxy.SetMyData({'data': 'new_data'}) + # Invoke GetMyData actor method to get the state + print('call GetMyData actor method to get the state', flush=True) + rtn_obj = await proxy.GetMyData() + print(rtn_obj, flush=True) + + # ----------------------------------------------- + # Actor reminder demo + # ----------------------------------------------- + # Invoke SetReminder actor method to set actor reminder + print('Register reminder', flush=True) + await proxy.SetReminder(True) + + # ----------------------------------------------- + # Actor timer demo + # ----------------------------------------------- + # Invoke SetTimer to set actor timer + print('Register timer', flush=True) + await proxy.SetTimer(True) + + # Wait for 30 seconds to see reminder and timer is triggered + print('waiting for 30 seconds', flush=True) + await asyncio.sleep(30) + + # Stop reminder and timer + print('stop reminder', flush=True) + await proxy.SetReminder(False) + print('stop timer', flush=True) + await proxy.SetTimer(False) + + # Clear actor state + print('clear actor state', flush=True) + await proxy.ClearMyData() + + +asyncio.run(main()) diff --git a/examples/demo_actor/demo_actor/demo_actor_flask.py b/examples/demo_actor/demo_actor/demo_actor_flask.py index 5715d23d8..322943d8f 100644 --- a/examples/demo_actor/demo_actor/demo_actor_flask.py +++ b/examples/demo_actor/demo_actor/demo_actor_flask.py @@ -1,44 +1,44 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from flask import Flask, jsonify -from flask_dapr.actor import DaprActor - -from dapr.conf import settings -from dapr.actor.runtime.config import ActorRuntimeConfig, ActorTypeConfig, ActorReentrancyConfig -from dapr.actor.runtime.runtime import ActorRuntime -from demo_actor import DemoActor - -app = Flask(f'{DemoActor.__name__}Service') - -# This is an optional advanced configuration which enables reentrancy only for the -# specified actor type. By default reentrancy is not enabled for all actor types. -config = ActorRuntimeConfig() # init with default values -config.update_actor_type_configs( - [ActorTypeConfig(actor_type=DemoActor.__name__, reentrancy=ActorReentrancyConfig(enabled=True))] -) -ActorRuntime.set_actor_config(config) - -# Enable DaprActor Flask extension -actor = DaprActor(app) -# Register DemoActor -actor.register_actor(DemoActor) - - -# This route is optional. -@app.route('/') -def index(): - return jsonify({'status': 'ok'}), 200 - - -if __name__ == '__main__': - app.run(port=settings.HTTP_APP_PORT) +# -*- coding: utf-8 -*- +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from flask import Flask, jsonify +from flask_dapr.actor import DaprActor + +from dapr.conf import settings +from dapr.actor.runtime.config import ActorRuntimeConfig, ActorTypeConfig, ActorReentrancyConfig +from dapr.actor.runtime.runtime import ActorRuntime +from demo_actor import DemoActor + +app = Flask(f'{DemoActor.__name__}Service') + +# This is an optional advanced configuration which enables reentrancy only for the +# specified actor type. By default reentrancy is not enabled for all actor types. +config = ActorRuntimeConfig() # init with default values +config.update_actor_type_configs( + [ActorTypeConfig(actor_type=DemoActor.__name__, reentrancy=ActorReentrancyConfig(enabled=True))] +) +ActorRuntime.set_actor_config(config) + +# Enable DaprActor Flask extension +actor = DaprActor(app) +# Register DemoActor +actor.register_actor(DemoActor) + + +# This route is optional. +@app.route('/') +def index(): + return jsonify({'status': 'ok'}), 200 + + +if __name__ == '__main__': + app.run(port=settings.HTTP_APP_PORT) diff --git a/examples/demo_actor/demo_actor/demo_actor_interface.py b/examples/demo_actor/demo_actor/demo_actor_interface.py index be43c2ed6..5155c7cc3 100644 --- a/examples/demo_actor/demo_actor/demo_actor_interface.py +++ b/examples/demo_actor/demo_actor/demo_actor_interface.py @@ -1,47 +1,47 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from abc import abstractmethod - -from dapr.actor import ActorInterface, actormethod - - -class DemoActorInterface(ActorInterface): - @abstractmethod - @actormethod(name='GetMyData') - async def get_my_data(self) -> object: - ... - - @abstractmethod - @actormethod(name='SetMyData') - async def set_my_data(self, data: object) -> None: - ... - - @abstractmethod - @actormethod(name='ClearMyData') - async def clear_my_data(self) -> None: - ... - - @abstractmethod - @actormethod(name='SetReminder') - async def set_reminder(self, enabled: bool) -> None: - ... - - @abstractmethod - @actormethod(name='SetTimer') - async def set_timer(self, enabled: bool) -> None: - ... - - @abstractmethod - @actormethod(name='GetReentrancyStatus') - async def get_reentrancy_status(self) -> bool: - ... +# -*- coding: utf-8 -*- +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from abc import abstractmethod + +from dapr.actor import ActorInterface, actormethod + + +class DemoActorInterface(ActorInterface): + @abstractmethod + @actormethod(name='GetMyData') + async def get_my_data(self) -> object: + ... + + @abstractmethod + @actormethod(name='SetMyData') + async def set_my_data(self, data: object) -> None: + ... + + @abstractmethod + @actormethod(name='ClearMyData') + async def clear_my_data(self) -> None: + ... + + @abstractmethod + @actormethod(name='SetReminder') + async def set_reminder(self, enabled: bool) -> None: + ... + + @abstractmethod + @actormethod(name='SetTimer') + async def set_timer(self, enabled: bool) -> None: + ... + + @abstractmethod + @actormethod(name='GetReentrancyStatus') + async def get_reentrancy_status(self) -> bool: + ... diff --git a/examples/demo_actor/demo_actor/demo_actor_service.py b/examples/demo_actor/demo_actor/demo_actor_service.py index c53d06e25..4deb4d68a 100644 --- a/examples/demo_actor/demo_actor/demo_actor_service.py +++ b/examples/demo_actor/demo_actor/demo_actor_service.py @@ -1,37 +1,37 @@ -# -*- coding: utf-8 -*- -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from fastapi import FastAPI # type: ignore -from dapr.actor.runtime.config import ActorRuntimeConfig, ActorTypeConfig, ActorReentrancyConfig -from dapr.actor.runtime.runtime import ActorRuntime -from dapr.ext.fastapi import DaprActor # type: ignore -from demo_actor import DemoActor - - -app = FastAPI(title=f'{DemoActor.__name__}Service') - -# This is an optional advanced configuration which enables reentrancy only for the -# specified actor type. By default reentrancy is not enabled for all actor types. -config = ActorRuntimeConfig() # init with default values -config.update_actor_type_configs( - [ActorTypeConfig(actor_type=DemoActor.__name__, reentrancy=ActorReentrancyConfig(enabled=True))] -) -ActorRuntime.set_actor_config(config) - -# Add Dapr Actor Extension -actor = DaprActor(app) - - -@app.on_event('startup') -async def startup_event(): - # Register DemoActor - await actor.register_actor(DemoActor) +# -*- coding: utf-8 -*- +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from fastapi import FastAPI # type: ignore +from dapr.actor.runtime.config import ActorRuntimeConfig, ActorTypeConfig, ActorReentrancyConfig +from dapr.actor.runtime.runtime import ActorRuntime +from dapr.ext.fastapi import DaprActor # type: ignore +from demo_actor import DemoActor + + +app = FastAPI(title=f'{DemoActor.__name__}Service') + +# This is an optional advanced configuration which enables reentrancy only for the +# specified actor type. By default reentrancy is not enabled for all actor types. +config = ActorRuntimeConfig() # init with default values +config.update_actor_type_configs( + [ActorTypeConfig(actor_type=DemoActor.__name__, reentrancy=ActorReentrancyConfig(enabled=True))] +) +ActorRuntime.set_actor_config(config) + +# Add Dapr Actor Extension +actor = DaprActor(app) + + +@app.on_event('startup') +async def startup_event(): + # Register DemoActor + await actor.register_actor(DemoActor) diff --git a/examples/demo_actor/demo_actor/requirements.txt b/examples/demo_actor/demo_actor/requirements.txt index 7c8493530..713debabc 100644 --- a/examples/demo_actor/demo_actor/requirements.txt +++ b/examples/demo_actor/demo_actor/requirements.txt @@ -1 +1 @@ -dapr-ext-fastapi-dev>=1.13.0rc1.dev +dapr-ext-fastapi-dev>=1.13.0rc1.dev diff --git a/examples/demo_actor/deploy/demo_actor_client.yml b/examples/demo_actor/deploy/demo_actor_client.yml index 135258f10..21ac27539 100644 --- a/examples/demo_actor/deploy/demo_actor_client.yml +++ b/examples/demo_actor/deploy/demo_actor_client.yml @@ -1,36 +1,36 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: demoactor-client-app - labels: - app: demoactor-client -spec: - replicas: 1 - selector: - matchLabels: - app: demoactor-client - template: - metadata: - labels: - app: demoactor-client - annotations: - dapr.io/enabled: "true" - dapr.io/app-id: "demoactor-client" - spec: - containers: - - name: demoactor-client - image: demo_actor:latest # EDIT HERE: Replace the image nmae with [docker registry]/demo_actor:latest - command: ["python"] - args: ["/app/demo_actor_client.py"] - imagePullPolicy: Always +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demoactor-client-app + labels: + app: demoactor-client +spec: + replicas: 1 + selector: + matchLabels: + app: demoactor-client + template: + metadata: + labels: + app: demoactor-client + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "demoactor-client" + spec: + containers: + - name: demoactor-client + image: demo_actor:latest # EDIT HERE: Replace the image nmae with [docker registry]/demo_actor:latest + command: ["python"] + args: ["/app/demo_actor_client.py"] + imagePullPolicy: Always diff --git a/examples/demo_actor/deploy/demo_actor_service.yml b/examples/demo_actor/deploy/demo_actor_service.yml index b0a99ae9b..0fe1b69fa 100644 --- a/examples/demo_actor/deploy/demo_actor_service.yml +++ b/examples/demo_actor/deploy/demo_actor_service.yml @@ -1,39 +1,39 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: demoactorapp - labels: - app: demoactor -spec: - replicas: 1 - selector: - matchLabels: - app: demoactor - template: - metadata: - labels: - app: demoactor - annotations: - dapr.io/enabled: "true" - dapr.io/app-id: "demoactor" - dapr.io/app-port: "3000" - spec: - containers: - - name: demoactor - image: demo_actor:latest # EDIT HERE: Replace the image nmae with [docker registry]/demo_actor:latest - command: ["uvicorn"] - args: ["--port", "3000", "--reload-dir", "/app", "demo_actor_service:app"] - ports: - - containerPort: 3000 - imagePullPolicy: Always +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: demoactorapp + labels: + app: demoactor +spec: + replicas: 1 + selector: + matchLabels: + app: demoactor + template: + metadata: + labels: + app: demoactor + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "demoactor" + dapr.io/app-port: "3000" + spec: + containers: + - name: demoactor + image: demo_actor:latest # EDIT HERE: Replace the image nmae with [docker registry]/demo_actor:latest + command: ["uvicorn"] + args: ["--port", "3000", "--reload-dir", "/app", "demo_actor_service:app"] + ports: + - containerPort: 3000 + imagePullPolicy: Always diff --git a/examples/demo_actor/deploy/redis.yml b/examples/demo_actor/deploy/redis.yml index 0eb42c732..a136aa394 100644 --- a/examples/demo_actor/deploy/redis.yml +++ b/examples/demo_actor/deploy/redis.yml @@ -1,17 +1,17 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: statestore - namespace: default -spec: - type: state.redis - version: v1 - metadata: - - name: redisHost - value: redis-master.default.svc.cluster.local:6379 - - name: redisPassword - secretKeyRef: - name: redis - key: redis-password - - name: actorStateStore - value: "true" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore + namespace: default +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: redis-master.default.svc.cluster.local:6379 + - name: redisPassword + secretKeyRef: + name: redis + key: redis-password + - name: actorStateStore + value: "true" diff --git a/examples/demo_workflow/README.md b/examples/demo_workflow/README.md index 12b84ae2c..717de49ea 100644 --- a/examples/demo_workflow/README.md +++ b/examples/demo_workflow/README.md @@ -1,89 +1,89 @@ -# Example - Dapr Workflow Authoring - -This document describes how to register a workflow and activities inside it and start running it. -It demonstrates the following APIs: -- **start_workflow**: Start an instance of a workflow -- **get_workflow**: Get information on a single workflow -- **terminate_workflow**: Terminate or stop a particular instance of a workflow -- **raise_event**: Raise an event on a workflow -- **pause_workflow**: Pauses or suspends a workflow instance that can later be resumed -- **resume_workflow**: Resumes a paused workflow instance -- **purge_workflow**: Removes all metadata related to a specific workflow instance from the state store -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -### Install requirements - -You can install dapr SDK package using pip command: - - - -```sh -pip3 install -r demo_workflow/requirements.txt -``` - - - - - -```sh -dapr run --app-id orderapp --app-protocol grpc --dapr-grpc-port 50001 --resources-path components --placement-host-address localhost:50005 -- python3 app.py -``` - - - -You should be able to see the following output: -``` -== APP == Hi Counter! -== APP == New counter value is: 1! -== APP == New counter value is: 11! -== APP == Retry count value is: 0! -== APP == Retry count value is: 1! This print statement verifies retry -== APP == Appending 1 to child_orchestrator_string! -== APP == Appending a to child_orchestrator_string! -== APP == Appending a to child_orchestrator_string! -== APP == Appending 2 to child_orchestrator_string! -== APP == Appending b to child_orchestrator_string! -== APP == Appending b to child_orchestrator_string! -== APP == Appending 3 to child_orchestrator_string! -== APP == Appending c to child_orchestrator_string! -== APP == Appending c to child_orchestrator_string! -== APP == Get response from hello_world_wf after pause call: Suspended -== APP == Get response from hello_world_wf after resume call: Running -== APP == New counter value is: 111! -== APP == New counter value is: 1111! -== APP == Instance Successfully Purged -== APP == Get response from hello_world_wf after terminate call: Terminated -== APP == Get response from child_wf after terminate call: Terminated -``` +# Example - Dapr Workflow Authoring + +This document describes how to register a workflow and activities inside it and start running it. +It demonstrates the following APIs: +- **start_workflow**: Start an instance of a workflow +- **get_workflow**: Get information on a single workflow +- **terminate_workflow**: Terminate or stop a particular instance of a workflow +- **raise_event**: Raise an event on a workflow +- **pause_workflow**: Pauses or suspends a workflow instance that can later be resumed +- **resume_workflow**: Resumes a paused workflow instance +- **purge_workflow**: Removes all metadata related to a specific workflow instance from the state store +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +### Install requirements + +You can install dapr SDK package using pip command: + + + +```sh +pip3 install -r demo_workflow/requirements.txt +``` + + + + + +```sh +dapr run --app-id orderapp --app-protocol grpc --dapr-grpc-port 50001 --resources-path components --placement-host-address localhost:50005 -- python3 app.py +``` + + + +You should be able to see the following output: +``` +== APP == Hi Counter! +== APP == New counter value is: 1! +== APP == New counter value is: 11! +== APP == Retry count value is: 0! +== APP == Retry count value is: 1! This print statement verifies retry +== APP == Appending 1 to child_orchestrator_string! +== APP == Appending a to child_orchestrator_string! +== APP == Appending a to child_orchestrator_string! +== APP == Appending 2 to child_orchestrator_string! +== APP == Appending b to child_orchestrator_string! +== APP == Appending b to child_orchestrator_string! +== APP == Appending 3 to child_orchestrator_string! +== APP == Appending c to child_orchestrator_string! +== APP == Appending c to child_orchestrator_string! +== APP == Get response from hello_world_wf after pause call: Suspended +== APP == Get response from hello_world_wf after resume call: Running +== APP == New counter value is: 111! +== APP == New counter value is: 1111! +== APP == Instance Successfully Purged +== APP == Get response from hello_world_wf after terminate call: Terminated +== APP == Get response from child_wf after terminate call: Terminated +``` diff --git a/examples/demo_workflow/app.py b/examples/demo_workflow/app.py index 739655627..17999fb63 100644 --- a/examples/demo_workflow/app.py +++ b/examples/demo_workflow/app.py @@ -1,212 +1,212 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from datetime import timedelta -from time import sleep -from dapr.ext.workflow import ( - WorkflowRuntime, - DaprWorkflowContext, - WorkflowActivityContext, - RetryPolicy, -) -from dapr.conf import Settings -from dapr.clients import DaprClient -from dapr.clients.exceptions import DaprInternalError - -settings = Settings() - -counter = 0 -retry_count = 0 -child_orchestrator_count = 0 -child_orchestrator_string = '' -child_act_retry_count = 0 -instance_id = 'exampleInstanceID' -child_instance_id = 'childInstanceID' -workflow_component = 'dapr' -workflow_name = 'hello_world_wf' -child_workflow_name = 'child_wf' -input_data = 'Hi Counter!' -workflow_options = dict() -workflow_options['task_queue'] = 'testQueue' -event_name = 'event1' -event_data = 'eventData' -non_existent_id_error = 'no such instance exists' - -retry_policy = RetryPolicy( - first_retry_interval=timedelta(seconds=1), - max_number_of_attempts=3, - backoff_coefficient=2, - max_retry_interval=timedelta(seconds=10), - retry_timeout=timedelta(seconds=100), -) - - -def hello_world_wf(ctx: DaprWorkflowContext, wf_input): - print(f'{wf_input}') - yield ctx.call_activity(hello_act, input=1) - yield ctx.call_activity(hello_act, input=10) - yield ctx.call_activity(hello_retryable_act, retry_policy=retry_policy) - yield ctx.call_child_workflow(child_retryable_wf, retry_policy=retry_policy) - yield ctx.call_child_workflow(child_wf, instance_id=child_instance_id) - yield ctx.call_activity(hello_act, input=100) - yield ctx.call_activity(hello_act, input=1000) - - -def child_wf(ctx: DaprWorkflowContext): - yield ctx.wait_for_external_event('event1') - - -def hello_act(ctx: WorkflowActivityContext, wf_input): - global counter - counter += wf_input - print(f'New counter value is: {counter}!', flush=True) - - -def hello_retryable_act(ctx: WorkflowActivityContext): - global retry_count - if (retry_count % 2) == 0: - print(f'Retry count value is: {retry_count}!', flush=True) - retry_count += 1 - raise ValueError('Retryable Error') - print(f'Retry count value is: {retry_count}! This print statement verifies retry', flush=True) - retry_count += 1 - - -def child_retryable_wf(ctx: DaprWorkflowContext): - global child_orchestrator_string, child_orchestrator_count - if not ctx.is_replaying: - child_orchestrator_count += 1 - print(f'Appending {child_orchestrator_count} to child_orchestrator_string!', flush=True) - child_orchestrator_string += str(child_orchestrator_count) - yield ctx.call_activity( - act_for_child_wf, input=child_orchestrator_count, retry_policy=retry_policy - ) - if child_orchestrator_count < 3: - raise ValueError('Retryable Error') - - -def act_for_child_wf(ctx: WorkflowActivityContext, inp): - global child_orchestrator_string, child_act_retry_count - inp_char = chr(96 + inp) - print(f'Appending {inp_char} to child_orchestrator_string!', flush=True) - child_orchestrator_string += inp_char - if child_act_retry_count % 2 == 0: - child_act_retry_count += 1 - raise ValueError('Retryable Error') - child_act_retry_count += 1 - - -def main(): - with DaprClient() as d: - workflow_runtime = WorkflowRuntime() - workflow_runtime.register_workflow(hello_world_wf) - workflow_runtime.register_workflow(child_retryable_wf) - workflow_runtime.register_workflow(child_wf) - workflow_runtime.register_activity(hello_act) - workflow_runtime.register_activity(hello_retryable_act) - workflow_runtime.register_activity(act_for_child_wf) - workflow_runtime.start() - - sleep(2) - - print('==========Start Counter Increase as per Input:==========') - start_resp = d.start_workflow( - instance_id=instance_id, - workflow_component=workflow_component, - workflow_name=workflow_name, - input=input_data, - workflow_options=workflow_options, - ) - print(f'start_resp {start_resp.instance_id}') - - # Sleep for a while to let the workflow run - sleep(12) - assert counter == 11 - assert retry_count == 2 - assert child_orchestrator_string == '1aa2bb3cc' - - # Pause Test - d.pause_workflow(instance_id=instance_id, workflow_component=workflow_component) - get_response = d.get_workflow( - instance_id=instance_id, workflow_component=workflow_component - ) - print(f'Get response from {workflow_name} after pause call: {get_response.runtime_status}') - - # Resume Test - d.resume_workflow(instance_id=instance_id, workflow_component=workflow_component) - get_response = d.get_workflow( - instance_id=instance_id, workflow_component=workflow_component - ) - print(f'Get response from {workflow_name} after resume call: {get_response.runtime_status}') - - sleep(1) - # Raise event - d.raise_workflow_event( - instance_id=child_instance_id, - workflow_component=workflow_component, - event_name=event_name, - event_data=event_data, - ) - - sleep(5) - # Purge Test - d.purge_workflow(instance_id=instance_id, workflow_component=workflow_component) - try: - d.get_workflow(instance_id=instance_id, workflow_component=workflow_component) - except DaprInternalError as err: - if non_existent_id_error in err._message: - print('Instance Successfully Purged') - - # Kick off another workflow for termination purposes - # This will also test using the same instance ID on a new workflow after - # the old instance was purged - start_resp = d.start_workflow( - instance_id=instance_id, - workflow_component=workflow_component, - workflow_name=workflow_name, - input=input_data, - workflow_options=workflow_options, - ) - print(f'start_resp {start_resp.instance_id}') - - sleep(5) - # Terminate Test - d.terminate_workflow(instance_id=instance_id, workflow_component=workflow_component) - sleep(1) - get_response = d.get_workflow( - instance_id=instance_id, workflow_component=workflow_component - ) - print( - f'Get response from {workflow_name} ' - f'after terminate call: {get_response.runtime_status}' - ) - child_get_response = d.get_workflow( - instance_id=child_instance_id, workflow_component=workflow_component - ) - print( - f'Get response from {child_workflow_name} ' - f'after terminate call: {child_get_response.runtime_status}' - ) - - # Purge Test - d.purge_workflow(instance_id=instance_id, workflow_component=workflow_component) - try: - d.get_workflow(instance_id=instance_id, workflow_component=workflow_component) - except DaprInternalError as err: - if non_existent_id_error in err._message: - print('Instance Successfully Purged') - - workflow_runtime.shutdown() - - -if __name__ == '__main__': - main() +# -*- coding: utf-8 -*- +# Copyright 2023 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import timedelta +from time import sleep +from dapr.ext.workflow import ( + WorkflowRuntime, + DaprWorkflowContext, + WorkflowActivityContext, + RetryPolicy, +) +from dapr.conf import Settings +from dapr.clients import DaprClient +from dapr.clients.exceptions import DaprInternalError + +settings = Settings() + +counter = 0 +retry_count = 0 +child_orchestrator_count = 0 +child_orchestrator_string = '' +child_act_retry_count = 0 +instance_id = 'exampleInstanceID' +child_instance_id = 'childInstanceID' +workflow_component = 'dapr' +workflow_name = 'hello_world_wf' +child_workflow_name = 'child_wf' +input_data = 'Hi Counter!' +workflow_options = dict() +workflow_options['task_queue'] = 'testQueue' +event_name = 'event1' +event_data = 'eventData' +non_existent_id_error = 'no such instance exists' + +retry_policy = RetryPolicy( + first_retry_interval=timedelta(seconds=1), + max_number_of_attempts=3, + backoff_coefficient=2, + max_retry_interval=timedelta(seconds=10), + retry_timeout=timedelta(seconds=100), +) + + +def hello_world_wf(ctx: DaprWorkflowContext, wf_input): + print(f'{wf_input}') + yield ctx.call_activity(hello_act, input=1) + yield ctx.call_activity(hello_act, input=10) + yield ctx.call_activity(hello_retryable_act, retry_policy=retry_policy) + yield ctx.call_child_workflow(child_retryable_wf, retry_policy=retry_policy) + yield ctx.call_child_workflow(child_wf, instance_id=child_instance_id) + yield ctx.call_activity(hello_act, input=100) + yield ctx.call_activity(hello_act, input=1000) + + +def child_wf(ctx: DaprWorkflowContext): + yield ctx.wait_for_external_event('event1') + + +def hello_act(ctx: WorkflowActivityContext, wf_input): + global counter + counter += wf_input + print(f'New counter value is: {counter}!', flush=True) + + +def hello_retryable_act(ctx: WorkflowActivityContext): + global retry_count + if (retry_count % 2) == 0: + print(f'Retry count value is: {retry_count}!', flush=True) + retry_count += 1 + raise ValueError('Retryable Error') + print(f'Retry count value is: {retry_count}! This print statement verifies retry', flush=True) + retry_count += 1 + + +def child_retryable_wf(ctx: DaprWorkflowContext): + global child_orchestrator_string, child_orchestrator_count + if not ctx.is_replaying: + child_orchestrator_count += 1 + print(f'Appending {child_orchestrator_count} to child_orchestrator_string!', flush=True) + child_orchestrator_string += str(child_orchestrator_count) + yield ctx.call_activity( + act_for_child_wf, input=child_orchestrator_count, retry_policy=retry_policy + ) + if child_orchestrator_count < 3: + raise ValueError('Retryable Error') + + +def act_for_child_wf(ctx: WorkflowActivityContext, inp): + global child_orchestrator_string, child_act_retry_count + inp_char = chr(96 + inp) + print(f'Appending {inp_char} to child_orchestrator_string!', flush=True) + child_orchestrator_string += inp_char + if child_act_retry_count % 2 == 0: + child_act_retry_count += 1 + raise ValueError('Retryable Error') + child_act_retry_count += 1 + + +def main(): + with DaprClient() as d: + workflow_runtime = WorkflowRuntime() + workflow_runtime.register_workflow(hello_world_wf) + workflow_runtime.register_workflow(child_retryable_wf) + workflow_runtime.register_workflow(child_wf) + workflow_runtime.register_activity(hello_act) + workflow_runtime.register_activity(hello_retryable_act) + workflow_runtime.register_activity(act_for_child_wf) + workflow_runtime.start() + + sleep(2) + + print('==========Start Counter Increase as per Input:==========') + start_resp = d.start_workflow( + instance_id=instance_id, + workflow_component=workflow_component, + workflow_name=workflow_name, + input=input_data, + workflow_options=workflow_options, + ) + print(f'start_resp {start_resp.instance_id}') + + # Sleep for a while to let the workflow run + sleep(12) + assert counter == 11 + assert retry_count == 2 + assert child_orchestrator_string == '1aa2bb3cc' + + # Pause Test + d.pause_workflow(instance_id=instance_id, workflow_component=workflow_component) + get_response = d.get_workflow( + instance_id=instance_id, workflow_component=workflow_component + ) + print(f'Get response from {workflow_name} after pause call: {get_response.runtime_status}') + + # Resume Test + d.resume_workflow(instance_id=instance_id, workflow_component=workflow_component) + get_response = d.get_workflow( + instance_id=instance_id, workflow_component=workflow_component + ) + print(f'Get response from {workflow_name} after resume call: {get_response.runtime_status}') + + sleep(1) + # Raise event + d.raise_workflow_event( + instance_id=child_instance_id, + workflow_component=workflow_component, + event_name=event_name, + event_data=event_data, + ) + + sleep(5) + # Purge Test + d.purge_workflow(instance_id=instance_id, workflow_component=workflow_component) + try: + d.get_workflow(instance_id=instance_id, workflow_component=workflow_component) + except DaprInternalError as err: + if non_existent_id_error in err._message: + print('Instance Successfully Purged') + + # Kick off another workflow for termination purposes + # This will also test using the same instance ID on a new workflow after + # the old instance was purged + start_resp = d.start_workflow( + instance_id=instance_id, + workflow_component=workflow_component, + workflow_name=workflow_name, + input=input_data, + workflow_options=workflow_options, + ) + print(f'start_resp {start_resp.instance_id}') + + sleep(5) + # Terminate Test + d.terminate_workflow(instance_id=instance_id, workflow_component=workflow_component) + sleep(1) + get_response = d.get_workflow( + instance_id=instance_id, workflow_component=workflow_component + ) + print( + f'Get response from {workflow_name} ' + f'after terminate call: {get_response.runtime_status}' + ) + child_get_response = d.get_workflow( + instance_id=child_instance_id, workflow_component=workflow_component + ) + print( + f'Get response from {child_workflow_name} ' + f'after terminate call: {child_get_response.runtime_status}' + ) + + # Purge Test + d.purge_workflow(instance_id=instance_id, workflow_component=workflow_component) + try: + d.get_workflow(instance_id=instance_id, workflow_component=workflow_component) + except DaprInternalError as err: + if non_existent_id_error in err._message: + print('Instance Successfully Purged') + + workflow_runtime.shutdown() + + +if __name__ == '__main__': + main() diff --git a/examples/demo_workflow/components/state_redis.yaml b/examples/demo_workflow/components/state_redis.yaml index 6d568d969..5289c0185 100644 --- a/examples/demo_workflow/components/state_redis.yaml +++ b/examples/demo_workflow/components/state_redis.yaml @@ -1,15 +1,15 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: statestore-actors -spec: - type: state.redis - version: v1 - initTimeout: 1m - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" - - name: actorStateStore +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore-actors +spec: + type: state.redis + version: v1 + initTimeout: 1m + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore value: "true" \ No newline at end of file diff --git a/examples/demo_workflow/demo_workflow/requirements.txt b/examples/demo_workflow/demo_workflow/requirements.txt index 61f7f5c82..33b89fd11 100644 --- a/examples/demo_workflow/demo_workflow/requirements.txt +++ b/examples/demo_workflow/demo_workflow/requirements.txt @@ -1 +1 @@ -dapr-ext-workflow-dev>=0.0.1rc1.dev +dapr-ext-workflow-dev>=0.0.1rc1.dev diff --git a/examples/distributed_lock/README.md b/examples/distributed_lock/README.md index 80696c13e..e2afb3fd2 100644 --- a/examples/distributed_lock/README.md +++ b/examples/distributed_lock/README.md @@ -1,63 +1,63 @@ -# Example - Acquire and release distributed locks - -This example demonstrates the [Distributed Lock component] APIs in Dapr. -It demonstrates the following APIs: -- **try_lock**: Attempts to acquire a distributed lock from the lock store. -- **unlock**: Attempts to release (a previously acquired) distributed lock - -It creates a client using `DaprClient`, uses a local lock store defined in -[`./components/lockstore.yaml`](./components/lockstore.yaml) and invokes -all the distributed lock API methods available as example. - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run the example - -To run this example, the following code can be utilized: - - - -```bash -dapr run --app-id=locksapp --app-protocol grpc --resources-path components/ python3 lock.py -``` - - -The output should be as follows: - -``` -== APP == Will try to acquire a lock from lock store named [lockstore] -== APP == The lock is for a resource named [example-lock-resource] -== APP == The client identifier is [example-client-id] -== APP == The lock will will expire in 60 seconds. -== APP == Lock acquired successfully!!! -== APP == We already released the lock so unlocking will not work. -== APP == We tried to unlock it anyway and got back [UnlockResponseStatus.lock_does_not_exist] -``` - -## Error Handling - -The Dapr python-sdk will pass through errors that it receives from the Dapr runtime. - +# Example - Acquire and release distributed locks + +This example demonstrates the [Distributed Lock component] APIs in Dapr. +It demonstrates the following APIs: +- **try_lock**: Attempts to acquire a distributed lock from the lock store. +- **unlock**: Attempts to release (a previously acquired) distributed lock + +It creates a client using `DaprClient`, uses a local lock store defined in +[`./components/lockstore.yaml`](./components/lockstore.yaml) and invokes +all the distributed lock API methods available as example. + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run the example + +To run this example, the following code can be utilized: + + + +```bash +dapr run --app-id=locksapp --app-protocol grpc --resources-path components/ python3 lock.py +``` + + +The output should be as follows: + +``` +== APP == Will try to acquire a lock from lock store named [lockstore] +== APP == The lock is for a resource named [example-lock-resource] +== APP == The client identifier is [example-client-id] +== APP == The lock will will expire in 60 seconds. +== APP == Lock acquired successfully!!! +== APP == We already released the lock so unlocking will not work. +== APP == We tried to unlock it anyway and got back [UnlockResponseStatus.lock_does_not_exist] +``` + +## Error Handling + +The Dapr python-sdk will pass through errors that it receives from the Dapr runtime. + [Distributed Lock component]: https://docs.dapr.io/developing-applications/building-blocks/distributed-lock/ \ No newline at end of file diff --git a/examples/distributed_lock/components/lockstore.yaml b/examples/distributed_lock/components/lockstore.yaml index 32cf674bf..c3d2ee2ac 100644 --- a/examples/distributed_lock/components/lockstore.yaml +++ b/examples/distributed_lock/components/lockstore.yaml @@ -1,12 +1,12 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: lockstore - namespace: default -spec: - type: lock.redis - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: lockstore + namespace: default +spec: + type: lock.redis + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" diff --git a/examples/distributed_lock/lock.py b/examples/distributed_lock/lock.py index d18d955f6..b01da276e 100644 --- a/examples/distributed_lock/lock.py +++ b/examples/distributed_lock/lock.py @@ -1,44 +1,44 @@ -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -from dapr.clients import DaprClient -import warnings - - -def main(): - # Lock parameters - store_name = 'lockstore' # as defined in components/lockstore.yaml - resource_id = 'example-lock-resource' - client_id = 'example-client-id' - expiry_in_seconds = 60 - - with DaprClient() as dapr: - print('Will try to acquire a lock from lock store named [%s]' % store_name) - print('The lock is for a resource named [%s]' % resource_id) - print('The client identifier is [%s]' % client_id) - print('The lock will will expire in %s seconds.' % expiry_in_seconds) - - with dapr.try_lock(store_name, resource_id, client_id, expiry_in_seconds) as lock_result: - assert lock_result.success, 'Failed to acquire the lock. Aborting.' - print('Lock acquired successfully!!!') - - # At this point the lock was released - by magic of the `with` clause ;) - unlock_result = dapr.unlock(store_name, resource_id, client_id) - print('We already released the lock so unlocking will not work.') - print('We tried to unlock it anyway and got back [%s]' % unlock_result.status) - - -if __name__ == '__main__': - # Suppress "The Distributed Lock API is an Alpha" warnings - warnings.simplefilter('ignore') - main() +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +from dapr.clients import DaprClient +import warnings + + +def main(): + # Lock parameters + store_name = 'lockstore' # as defined in components/lockstore.yaml + resource_id = 'example-lock-resource' + client_id = 'example-client-id' + expiry_in_seconds = 60 + + with DaprClient() as dapr: + print('Will try to acquire a lock from lock store named [%s]' % store_name) + print('The lock is for a resource named [%s]' % resource_id) + print('The client identifier is [%s]' % client_id) + print('The lock will will expire in %s seconds.' % expiry_in_seconds) + + with dapr.try_lock(store_name, resource_id, client_id, expiry_in_seconds) as lock_result: + assert lock_result.success, 'Failed to acquire the lock. Aborting.' + print('Lock acquired successfully!!!') + + # At this point the lock was released - by magic of the `with` clause ;) + unlock_result = dapr.unlock(store_name, resource_id, client_id) + print('We already released the lock so unlocking will not work.') + print('We tried to unlock it anyway and got back [%s]' % unlock_result.status) + + +if __name__ == '__main__': + # Suppress "The Distributed Lock API is an Alpha" warnings + warnings.simplefilter('ignore') + main() diff --git a/examples/error_handling/README.md b/examples/error_handling/README.md index 1d24bc823..e54f998aa 100644 --- a/examples/error_handling/README.md +++ b/examples/error_handling/README.md @@ -1,57 +1,57 @@ -# Example - Error handling - -This guide demonstrates handling `DaprGrpcError` errors when using the Dapr python-SDK. It's important to note that not all Dapr gRPC status errors are currently captured and transformed into a `DaprGrpcError` by the SDK. Efforts are ongoing to enhance this aspect, and contributions are welcome. For detailed information on error handling in Dapr, refer to the [official documentation](https://docs.dapr.io/reference/errors). - -The example involves creating a DaprClient and invoking the save_state method. -It uses the default configuration from Dapr init in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run the example - -To run this example, the following code can be used: - - - -```bash -dapr run -- python3 error_handling.py -``` - - -The output should be as follows: - -``` -== APP == Status code: INVALID_ARGUMENT -== APP == Message: input key/keyPrefix 'key||' can't contain '||' -== APP == Error code: DAPR_STATE_ILLEGAL_KEY -== APP == Error info(reason): DAPR_STATE_ILLEGAL_KEY -== APP == Resource info (resource type): state -== APP == Resource info (resource name): statestore -== APP == Bad request (field): key|| -== APP == Bad request (description): input key/keyPrefix 'key||' can't contain '||' -== APP == JSON: {"status_code": "INVALID_ARGUMENT", "message": "input key/keyPrefix 'key||' can't contain '||'", "error_code": "DAPR_STATE_ILLEGAL_KEY", "details": {"error_info": {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "DAPR_STATE_ILLEGAL_KEY", "domain": "dapr.io"}, "retry_info": null, "debug_info": null, "quota_failure": null, "precondition_failure": null, "bad_request": {"@type": "type.googleapis.com/google.rpc.BadRequest", "field_violations": [{"field": "key||", "description": "input key/keyPrefix 'key||' can't contain '||'"}]}, "request_info": null, "resource_info": {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resource_type": "state", "resource_name": "statestore"}, "help": null, "localized_message": null}} -``` +# Example - Error handling + +This guide demonstrates handling `DaprGrpcError` errors when using the Dapr python-SDK. It's important to note that not all Dapr gRPC status errors are currently captured and transformed into a `DaprGrpcError` by the SDK. Efforts are ongoing to enhance this aspect, and contributions are welcome. For detailed information on error handling in Dapr, refer to the [official documentation](https://docs.dapr.io/reference/errors). + +The example involves creating a DaprClient and invoking the save_state method. +It uses the default configuration from Dapr init in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run the example + +To run this example, the following code can be used: + + + +```bash +dapr run -- python3 error_handling.py +``` + + +The output should be as follows: + +``` +== APP == Status code: INVALID_ARGUMENT +== APP == Message: input key/keyPrefix 'key||' can't contain '||' +== APP == Error code: DAPR_STATE_ILLEGAL_KEY +== APP == Error info(reason): DAPR_STATE_ILLEGAL_KEY +== APP == Resource info (resource type): state +== APP == Resource info (resource name): statestore +== APP == Bad request (field): key|| +== APP == Bad request (description): input key/keyPrefix 'key||' can't contain '||' +== APP == JSON: {"status_code": "INVALID_ARGUMENT", "message": "input key/keyPrefix 'key||' can't contain '||'", "error_code": "DAPR_STATE_ILLEGAL_KEY", "details": {"error_info": {"@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "DAPR_STATE_ILLEGAL_KEY", "domain": "dapr.io"}, "retry_info": null, "debug_info": null, "quota_failure": null, "precondition_failure": null, "bad_request": {"@type": "type.googleapis.com/google.rpc.BadRequest", "field_violations": [{"field": "key||", "description": "input key/keyPrefix 'key||' can't contain '||'"}]}, "request_info": null, "resource_info": {"@type": "type.googleapis.com/google.rpc.ResourceInfo", "resource_type": "state", "resource_name": "statestore"}, "help": null, "localized_message": null}} +``` diff --git a/examples/error_handling/error_handling.py b/examples/error_handling/error_handling.py index b75ebed97..a6c4b3402 100644 --- a/examples/error_handling/error_handling.py +++ b/examples/error_handling/error_handling.py @@ -1,39 +1,39 @@ -from dapr.clients import DaprClient -from dapr.clients.exceptions import DaprGrpcError - - -with DaprClient() as d: - storeName = 'statestore' - - key = 'key||' - value = 'value_1' - - # Save single state. - try: - d.save_state(store_name=storeName, key=key, value=value) - except DaprGrpcError as err: - print(f'Status code: {err.code()}', flush=True) - print(f'Message: {err.details()}', flush=True) - print(f'Error code: {err.error_code()}', flush=True) - - if err.status_details().error_info is not None: - print(f'Error info(reason): {err.status_details().error_info["reason"]}', flush=True) - if err.status_details().resource_info is not None: - print( - f'Resource info (resource type): {err.status_details().resource_info["resource_type"]}', - flush=True, - ) - print( - f'Resource info (resource name): {err.status_details().resource_info["resource_name"]}', - flush=True, - ) - if err.status_details().bad_request is not None: - print( - f'Bad request (field): {err.status_details().bad_request["field_violations"][0]["field"]}', - flush=True, - ) - print( - f'Bad request (description): {err.status_details().bad_request["field_violations"][0]["description"]}', - flush=True, - ) - print(f'JSON: {err.json()}', flush=True) +from dapr.clients import DaprClient +from dapr.clients.exceptions import DaprGrpcError + + +with DaprClient() as d: + storeName = 'statestore' + + key = 'key||' + value = 'value_1' + + # Save single state. + try: + d.save_state(store_name=storeName, key=key, value=value) + except DaprGrpcError as err: + print(f'Status code: {err.code()}', flush=True) + print(f'Message: {err.details()}', flush=True) + print(f'Error code: {err.error_code()}', flush=True) + + if err.status_details().error_info is not None: + print(f'Error info(reason): {err.status_details().error_info["reason"]}', flush=True) + if err.status_details().resource_info is not None: + print( + f'Resource info (resource type): {err.status_details().resource_info["resource_type"]}', + flush=True, + ) + print( + f'Resource info (resource name): {err.status_details().resource_info["resource_name"]}', + flush=True, + ) + if err.status_details().bad_request is not None: + print( + f'Bad request (field): {err.status_details().bad_request["field_violations"][0]["field"]}', + flush=True, + ) + print( + f'Bad request (description): {err.status_details().bad_request["field_violations"][0]["description"]}', + flush=True, + ) + print(f'JSON: {err.json()}', flush=True) diff --git a/examples/grpc_proxying/README.md b/examples/grpc_proxying/README.md index e31913deb..9499f4f13 100644 --- a/examples/grpc_proxying/README.md +++ b/examples/grpc_proxying/README.md @@ -1,114 +1,114 @@ -# Example - Add an existing gRPC service to the Python SDK gRPC App extension - -This example creates a gRPC service using the protobuf file and adds it to the Python SDK gRPC App extension. Using add_external_service, we can add the `HelloWorld` servicer. This way we can use gRPC Proxying feature of Dapr and at the same time have a full access to other features that come with Python SDK, such as pubsub. - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Running in self-hosted mode - -Run the following command in a terminal/command-prompt: - - - -```bash -# 1. Start Receiver (expose gRPC server receiver on port 50051) -dapr run --app-id invoke-receiver --app-protocol grpc --app-port 50051 --config config.yaml -- python invoke-receiver.py -``` - - - -In another terminal/command prompt run: - - - - - -```bash -# 2. Start Caller -dapr run --app-id invoke-caller --dapr-grpc-port 50007 --config config.yaml -- python invoke-caller.py -``` - - - -## Cleanup - - - -```bash -dapr stop --app-id invoke-receiver -``` - - - -## Running in Kubernetes mode - -1. Build docker image - - ``` - docker build -t [your registry]/invokegrpcproxy:latest . - ``` - -2. Push docker image - - ``` - docker push [your registry]/invokegrpcproxy:latest - ``` - -3. Edit image name to `[your registry]/invokegrpcproxy:latest` in deploy/*.yaml - -4. Deploy applications - - ``` - kubectl apply -f ./deploy/ - ``` - -5. See logs for the apps and sidecars - - Logs for caller sidecar: - ``` - dapr logs -a invoke-caller -k - ``` - - Logs for caller app: - ``` - kubectl logs -l app="invokecaller" -c invokecaller - ``` - - Logs for receiver sidecar: - ``` - dapr logs -a invoke-receiver -k - ``` - - Logs for receiver app: - ``` - kubectl logs -l app="invokereceiver" -c invokereceiver - ``` +# Example - Add an existing gRPC service to the Python SDK gRPC App extension + +This example creates a gRPC service using the protobuf file and adds it to the Python SDK gRPC App extension. Using add_external_service, we can add the `HelloWorld` servicer. This way we can use gRPC Proxying feature of Dapr and at the same time have a full access to other features that come with Python SDK, such as pubsub. + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Running in self-hosted mode + +Run the following command in a terminal/command-prompt: + + + +```bash +# 1. Start Receiver (expose gRPC server receiver on port 50051) +dapr run --app-id invoke-receiver --app-protocol grpc --app-port 50051 --config config.yaml -- python invoke-receiver.py +``` + + + +In another terminal/command prompt run: + + + + + +```bash +# 2. Start Caller +dapr run --app-id invoke-caller --dapr-grpc-port 50007 --config config.yaml -- python invoke-caller.py +``` + + + +## Cleanup + + + +```bash +dapr stop --app-id invoke-receiver +``` + + + +## Running in Kubernetes mode + +1. Build docker image + + ``` + docker build -t [your registry]/invokegrpcproxy:latest . + ``` + +2. Push docker image + + ``` + docker push [your registry]/invokegrpcproxy:latest + ``` + +3. Edit image name to `[your registry]/invokegrpcproxy:latest` in deploy/*.yaml + +4. Deploy applications + + ``` + kubectl apply -f ./deploy/ + ``` + +5. See logs for the apps and sidecars + + Logs for caller sidecar: + ``` + dapr logs -a invoke-caller -k + ``` + + Logs for caller app: + ``` + kubectl logs -l app="invokecaller" -c invokecaller + ``` + + Logs for receiver sidecar: + ``` + dapr logs -a invoke-receiver -k + ``` + + Logs for receiver app: + ``` + kubectl logs -l app="invokereceiver" -c invokereceiver + ``` diff --git a/examples/grpc_proxying/config.yaml b/examples/grpc_proxying/config.yaml index f2a80ee1e..c9fc15914 100644 --- a/examples/grpc_proxying/config.yaml +++ b/examples/grpc_proxying/config.yaml @@ -1,12 +1,12 @@ -apiVersion: dapr.io/v1alpha1 -kind: Configuration -metadata: - name: serverconfig -spec: - tracing: - samplingRate: "1" - zipkin: - endpointAddress: http://localhost:9411/api/v2/spans - features: - - name: proxy.grpc +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: serverconfig +spec: + tracing: + samplingRate: "1" + zipkin: + endpointAddress: http://localhost:9411/api/v2/spans + features: + - name: proxy.grpc enabled: true \ No newline at end of file diff --git a/examples/grpc_proxying/deploy/invoke-caller.yaml b/examples/grpc_proxying/deploy/invoke-caller.yaml index 819152eb3..f04d4226c 100644 --- a/examples/grpc_proxying/deploy/invoke-caller.yaml +++ b/examples/grpc_proxying/deploy/invoke-caller.yaml @@ -1,37 +1,37 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: invokecaller - labels: - app: invokecaller -spec: - replicas: 1 - selector: - matchLabels: - app: invokecaller - template: - metadata: - labels: - app: invokecaller - annotations: - dapr.io/enabled: "true" - dapr.io/app-id: "invoke-caller" - dapr.io/app-protocol: "grpc" - spec: - containers: - - name: invokecaller - image: invokegrpcproxy:latest # EDIT HERE: Replace the image name - command: ["python"] - args: ["/app/invoke-caller.py"] - imagePullPolicy: Always +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: invokecaller + labels: + app: invokecaller +spec: + replicas: 1 + selector: + matchLabels: + app: invokecaller + template: + metadata: + labels: + app: invokecaller + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "invoke-caller" + dapr.io/app-protocol: "grpc" + spec: + containers: + - name: invokecaller + image: invokegrpcproxy:latest # EDIT HERE: Replace the image name + command: ["python"] + args: ["/app/invoke-caller.py"] + imagePullPolicy: Always diff --git a/examples/grpc_proxying/deploy/invoke-receiver.yaml b/examples/grpc_proxying/deploy/invoke-receiver.yaml index 3f7958f3d..536835dba 100644 --- a/examples/grpc_proxying/deploy/invoke-receiver.yaml +++ b/examples/grpc_proxying/deploy/invoke-receiver.yaml @@ -1,40 +1,40 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: invokereceiver - labels: - app: invokereceiver -spec: - replicas: 1 - selector: - matchLabels: - app: invokereceiver - template: - metadata: - labels: - app: invokereceiver - annotations: - dapr.io/enabled: "true" - dapr.io/app-id: "invoke-receiver" - dapr.io/app-protocol: "grpc" - dapr.io/app-port: "50051" - spec: - containers: - - name: invokereceiver - image: invokegrpcproxy:latest # EDIT HERE: Replace the image name - command: ["python"] - args: ["/app/invoke-receiver.py"] - ports: - - containerPort: 50051 - imagePullPolicy: Always +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: invokereceiver + labels: + app: invokereceiver +spec: + replicas: 1 + selector: + matchLabels: + app: invokereceiver + template: + metadata: + labels: + app: invokereceiver + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "invoke-receiver" + dapr.io/app-protocol: "grpc" + dapr.io/app-port: "50051" + spec: + containers: + - name: invokereceiver + image: invokegrpcproxy:latest # EDIT HERE: Replace the image name + command: ["python"] + args: ["/app/invoke-receiver.py"] + ports: + - containerPort: 50051 + imagePullPolicy: Always diff --git a/examples/grpc_proxying/helloworld_service_pb2.py b/examples/grpc_proxying/helloworld_service_pb2.py index e05049653..69e555ac2 100644 --- a/examples/grpc_proxying/helloworld_service_pb2.py +++ b/examples/grpc_proxying/helloworld_service_pb2.py @@ -1,53 +1,53 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: helloworld_service.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x18helloworld_service.proto"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2=\n\x11HelloWorldService\x12(\n\x08SayHello\x12\r.HelloRequest\x1a\x0b.HelloReply"\x00\x62\x06proto3' -) - - -_HELLOREQUEST = DESCRIPTOR.message_types_by_name['HelloRequest'] -_HELLOREPLY = DESCRIPTOR.message_types_by_name['HelloReply'] -HelloRequest = _reflection.GeneratedProtocolMessageType( - 'HelloRequest', - (_message.Message,), - { - 'DESCRIPTOR': _HELLOREQUEST, - '__module__': 'helloworld_service_pb2', - # @@protoc_insertion_point(class_scope:HelloRequest) - }, -) -_sym_db.RegisterMessage(HelloRequest) - -HelloReply = _reflection.GeneratedProtocolMessageType( - 'HelloReply', - (_message.Message,), - { - 'DESCRIPTOR': _HELLOREPLY, - '__module__': 'helloworld_service_pb2', - # @@protoc_insertion_point(class_scope:HelloReply) - }, -) -_sym_db.RegisterMessage(HelloReply) - -_HELLOWORLDSERVICE = DESCRIPTOR.services_by_name['HelloWorldService'] -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _HELLOREQUEST._serialized_start = 28 - _HELLOREQUEST._serialized_end = 56 - _HELLOREPLY._serialized_start = 58 - _HELLOREPLY._serialized_end = 87 - _HELLOWORLDSERVICE._serialized_start = 89 - _HELLOWORLDSERVICE._serialized_end = 150 -# @@protoc_insertion_point(module_scope) +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: helloworld_service.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x18helloworld_service.proto"\x1c\n\x0cHelloRequest\x12\x0c\n\x04name\x18\x01 \x01(\t"\x1d\n\nHelloReply\x12\x0f\n\x07message\x18\x01 \x01(\t2=\n\x11HelloWorldService\x12(\n\x08SayHello\x12\r.HelloRequest\x1a\x0b.HelloReply"\x00\x62\x06proto3' +) + + +_HELLOREQUEST = DESCRIPTOR.message_types_by_name['HelloRequest'] +_HELLOREPLY = DESCRIPTOR.message_types_by_name['HelloReply'] +HelloRequest = _reflection.GeneratedProtocolMessageType( + 'HelloRequest', + (_message.Message,), + { + 'DESCRIPTOR': _HELLOREQUEST, + '__module__': 'helloworld_service_pb2', + # @@protoc_insertion_point(class_scope:HelloRequest) + }, +) +_sym_db.RegisterMessage(HelloRequest) + +HelloReply = _reflection.GeneratedProtocolMessageType( + 'HelloReply', + (_message.Message,), + { + 'DESCRIPTOR': _HELLOREPLY, + '__module__': 'helloworld_service_pb2', + # @@protoc_insertion_point(class_scope:HelloReply) + }, +) +_sym_db.RegisterMessage(HelloReply) + +_HELLOWORLDSERVICE = DESCRIPTOR.services_by_name['HelloWorldService'] +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _HELLOREQUEST._serialized_start = 28 + _HELLOREQUEST._serialized_end = 56 + _HELLOREPLY._serialized_start = 58 + _HELLOREPLY._serialized_end = 87 + _HELLOWORLDSERVICE._serialized_start = 89 + _HELLOWORLDSERVICE._serialized_end = 150 +# @@protoc_insertion_point(module_scope) diff --git a/examples/grpc_proxying/helloworld_service_pb2_grpc.py b/examples/grpc_proxying/helloworld_service_pb2_grpc.py index b5403111b..177ea0b8a 100644 --- a/examples/grpc_proxying/helloworld_service_pb2_grpc.py +++ b/examples/grpc_proxying/helloworld_service_pb2_grpc.py @@ -1,77 +1,77 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc - -import helloworld_service_pb2 as helloworld__service__pb2 - - -class HelloWorldServiceStub(object): - """The greeting service definition.""" - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.SayHello = channel.unary_unary( - '/HelloWorldService/SayHello', - request_serializer=helloworld__service__pb2.HelloRequest.SerializeToString, - response_deserializer=helloworld__service__pb2.HelloReply.FromString, - ) - - -class HelloWorldServiceServicer(object): - """The greeting service definition.""" - - def SayHello(self, request, context): - """Sends a greeting""" - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_HelloWorldServiceServicer_to_server(servicer, server): - rpc_method_handlers = { - 'SayHello': grpc.unary_unary_rpc_method_handler( - servicer.SayHello, - request_deserializer=helloworld__service__pb2.HelloRequest.FromString, - response_serializer=helloworld__service__pb2.HelloReply.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler('HelloWorldService', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) - - -# This class is part of an EXPERIMENTAL API. -class HelloWorldService(object): - """The greeting service definition.""" - - @staticmethod - def SayHello( - request, - target, - options=(), - channel_credentials=None, - call_credentials=None, - insecure=False, - compression=None, - wait_for_ready=None, - timeout=None, - metadata=None, - ): - return grpc.experimental.unary_unary( - request, - target, - '/HelloWorldService/SayHello', - helloworld__service__pb2.HelloRequest.SerializeToString, - helloworld__service__pb2.HelloReply.FromString, - options, - channel_credentials, - insecure, - call_credentials, - compression, - wait_for_ready, - timeout, - metadata, - ) +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import helloworld_service_pb2 as helloworld__service__pb2 + + +class HelloWorldServiceStub(object): + """The greeting service definition.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.SayHello = channel.unary_unary( + '/HelloWorldService/SayHello', + request_serializer=helloworld__service__pb2.HelloRequest.SerializeToString, + response_deserializer=helloworld__service__pb2.HelloReply.FromString, + ) + + +class HelloWorldServiceServicer(object): + """The greeting service definition.""" + + def SayHello(self, request, context): + """Sends a greeting""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_HelloWorldServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'SayHello': grpc.unary_unary_rpc_method_handler( + servicer.SayHello, + request_deserializer=helloworld__service__pb2.HelloRequest.FromString, + response_serializer=helloworld__service__pb2.HelloReply.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler('HelloWorldService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + +# This class is part of an EXPERIMENTAL API. +class HelloWorldService(object): + """The greeting service definition.""" + + @staticmethod + def SayHello( + request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None, + ): + return grpc.experimental.unary_unary( + request, + target, + '/HelloWorldService/SayHello', + helloworld__service__pb2.HelloRequest.SerializeToString, + helloworld__service__pb2.HelloReply.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + ) diff --git a/examples/grpc_proxying/invoke-caller.py b/examples/grpc_proxying/invoke-caller.py index a07298448..44483fdfb 100644 --- a/examples/grpc_proxying/invoke-caller.py +++ b/examples/grpc_proxying/invoke-caller.py @@ -1,21 +1,21 @@ -import asyncio -import logging - -import grpc -import helloworld_service_pb2_grpc -from helloworld_service_pb2 import HelloRequest, HelloReply -import json, time - - -async def run() -> None: - async with grpc.aio.insecure_channel('127.0.0.1:50007') as channel: - metadata = (('dapr-app-id', 'invoke-receiver'),) - stub = helloworld_service_pb2_grpc.HelloWorldServiceStub(channel) - response = await stub.SayHello(request=HelloRequest(name='you'), metadata=metadata) - print('Greeter client received: ' + response.message) - - -if __name__ == '__main__': - print('I am in main') - logging.basicConfig() - asyncio.run(run()) +import asyncio +import logging + +import grpc +import helloworld_service_pb2_grpc +from helloworld_service_pb2 import HelloRequest, HelloReply +import json, time + + +async def run() -> None: + async with grpc.aio.insecure_channel('127.0.0.1:50007') as channel: + metadata = (('dapr-app-id', 'invoke-receiver'),) + stub = helloworld_service_pb2_grpc.HelloWorldServiceStub(channel) + response = await stub.SayHello(request=HelloRequest(name='you'), metadata=metadata) + print('Greeter client received: ' + response.message) + + +if __name__ == '__main__': + print('I am in main') + logging.basicConfig() + asyncio.run(run()) diff --git a/examples/grpc_proxying/invoke-receiver.py b/examples/grpc_proxying/invoke-receiver.py index ec36b036c..144e55788 100644 --- a/examples/grpc_proxying/invoke-receiver.py +++ b/examples/grpc_proxying/invoke-receiver.py @@ -1,24 +1,24 @@ -import logging - -import grpc -import helloworld_service_pb2_grpc -from helloworld_service_pb2 import HelloRequest, HelloReply -from dapr.ext.grpc import App -import json - - -class HelloWorldService(helloworld_service_pb2_grpc.HelloWorldService): - def SayHello(self, request: HelloRequest, context: grpc.aio.ServicerContext) -> HelloReply: - logging.info(request) - return HelloReply(message='Hello, %s!' % request.name) - - -app = App() - -if __name__ == '__main__': - print('starting the HelloWorld Service') - logging.basicConfig(level=logging.INFO) - app.add_external_service( - helloworld_service_pb2_grpc.add_HelloWorldServiceServicer_to_server, HelloWorldService() - ) - app.run(50051) +import logging + +import grpc +import helloworld_service_pb2_grpc +from helloworld_service_pb2 import HelloRequest, HelloReply +from dapr.ext.grpc import App +import json + + +class HelloWorldService(helloworld_service_pb2_grpc.HelloWorldService): + def SayHello(self, request: HelloRequest, context: grpc.aio.ServicerContext) -> HelloReply: + logging.info(request) + return HelloReply(message='Hello, %s!' % request.name) + + +app = App() + +if __name__ == '__main__': + print('starting the HelloWorld Service') + logging.basicConfig(level=logging.INFO) + app.add_external_service( + helloworld_service_pb2_grpc.add_HelloWorldServiceServicer_to_server, HelloWorldService() + ) + app.run(50051) diff --git a/examples/grpc_proxying/proto/helloworld_service.proto b/examples/grpc_proxying/proto/helloworld_service.proto index 8b21210f5..641cdf6d5 100644 --- a/examples/grpc_proxying/proto/helloworld_service.proto +++ b/examples/grpc_proxying/proto/helloworld_service.proto @@ -1,16 +1,16 @@ -syntax = "proto3"; - -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; -} - -// The greeting service definition. -service HelloWorldService { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} +syntax = "proto3"; + +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} + +// The greeting service definition. +service HelloWorldService { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} } \ No newline at end of file diff --git a/examples/invoke-binding/README.md b/examples/invoke-binding/README.md index d006f76da..7be94db4c 100644 --- a/examples/invoke-binding/README.md +++ b/examples/invoke-binding/README.md @@ -1,97 +1,97 @@ -# Example - Dapr bindings - -This example utilizes a publisher and a receiver for the InvokeBinding / OnBindingEvent / ListInputBindings functionality. It will create a gRPC server and bind the OnBindingEvent method, which gets called after a publisher sends a message to a kafka binding. - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run example - -Run the following commands in a terminal/command-prompt: - - - -1. Start the kafka containers using docker compose - -```bash -docker compose -f ./docker-compose-single-kafka.yml up -d -``` - - - - - -2. Start Receiver (expose gRPC server receiver on port 50051) - -```bash -dapr run --app-id receiver --app-protocol grpc --app-port 50051 --resources-path ./components python3 invoke-input-binding.py -``` - - - -3. Start Publisher - -In another terminal/command-prompt run: - - - -```bash -dapr run --app-id publisher --app-protocol grpc --resources-path ./components python3 invoke-output-binding.py -``` - - - -## Cleanup - - - -The dapr apps can be stopped by calling stop or terminating the process: - -```bash -dapr stop --app-id publisher -dapr stop --app-id receiver -``` - -For kafka cleanup, run the following code: - -```bash -docker compose -f ./docker-compose-single-kafka.yml down -``` - - +# Example - Dapr bindings + +This example utilizes a publisher and a receiver for the InvokeBinding / OnBindingEvent / ListInputBindings functionality. It will create a gRPC server and bind the OnBindingEvent method, which gets called after a publisher sends a message to a kafka binding. + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run example + +Run the following commands in a terminal/command-prompt: + + + +1. Start the kafka containers using docker compose + +```bash +docker compose -f ./docker-compose-single-kafka.yml up -d +``` + + + + + +2. Start Receiver (expose gRPC server receiver on port 50051) + +```bash +dapr run --app-id receiver --app-protocol grpc --app-port 50051 --resources-path ./components python3 invoke-input-binding.py +``` + + + +3. Start Publisher + +In another terminal/command-prompt run: + + + +```bash +dapr run --app-id publisher --app-protocol grpc --resources-path ./components python3 invoke-output-binding.py +``` + + + +## Cleanup + + + +The dapr apps can be stopped by calling stop or terminating the process: + +```bash +dapr stop --app-id publisher +dapr stop --app-id receiver +``` + +For kafka cleanup, run the following code: + +```bash +docker compose -f ./docker-compose-single-kafka.yml down +``` + + diff --git a/examples/invoke-binding/components/kafka-binding.yaml b/examples/invoke-binding/components/kafka-binding.yaml index f80ff4e79..d887c35e5 100644 --- a/examples/invoke-binding/components/kafka-binding.yaml +++ b/examples/invoke-binding/components/kafka-binding.yaml @@ -1,20 +1,20 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: kafkaBinding -spec: - type: bindings.kafka - metadata: - # Kafka broker connection setting - - name: brokers - value: localhost:9092 - # consumer configuration: topic and consumer group - - name: topics - value: sample - - name: consumerGroup - value: group1 - # publisher configuration: topic - - name: publishTopic - value: sample - - name: authRequired - value: "false" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: kafkaBinding +spec: + type: bindings.kafka + metadata: + # Kafka broker connection setting + - name: brokers + value: localhost:9092 + # consumer configuration: topic and consumer group + - name: topics + value: sample + - name: consumerGroup + value: group1 + # publisher configuration: topic + - name: publishTopic + value: sample + - name: authRequired + value: "false" diff --git a/examples/invoke-binding/components/pubsub.yaml b/examples/invoke-binding/components/pubsub.yaml index bfa12af59..e3e316473 100644 --- a/examples/invoke-binding/components/pubsub.yaml +++ b/examples/invoke-binding/components/pubsub.yaml @@ -1,11 +1,11 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: pubsub -spec: - type: pubsub.redis - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: pubsub +spec: + type: pubsub.redis + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" diff --git a/examples/invoke-binding/components/statestore.yaml b/examples/invoke-binding/components/statestore.yaml index 064922915..0efbca01d 100644 --- a/examples/invoke-binding/components/statestore.yaml +++ b/examples/invoke-binding/components/statestore.yaml @@ -1,13 +1,13 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: statestore -spec: - type: state.redis - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" - - name: actorStateStore - value: "true" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" diff --git a/examples/invoke-binding/docker-compose-single-kafka.yml b/examples/invoke-binding/docker-compose-single-kafka.yml index 01dfa3946..d244a82d0 100644 --- a/examples/invoke-binding/docker-compose-single-kafka.yml +++ b/examples/invoke-binding/docker-compose-single-kafka.yml @@ -1,27 +1,27 @@ -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -version: '2' -services: - zookeeper: - image: wurstmeister/zookeeper:latest - ports: - - "2181:2181" - kafka: - image: wurstmeister/kafka:latest - ports: - - "9092:9092" - environment: - KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 - KAFKA_CREATE_TOPICS: "sample:1:1" - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +version: '2' +services: + zookeeper: + image: wurstmeister/zookeeper:latest + ports: + - "2181:2181" + kafka: + image: wurstmeister/kafka:latest + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1 + KAFKA_CREATE_TOPICS: "sample:1:1" + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 diff --git a/examples/invoke-binding/invoke-input-binding.py b/examples/invoke-binding/invoke-input-binding.py index f87011691..644f3a92f 100644 --- a/examples/invoke-binding/invoke-input-binding.py +++ b/examples/invoke-binding/invoke-input-binding.py @@ -1,11 +1,11 @@ -from dapr.ext.grpc import App, BindingRequest - -app = App() - - -@app.binding('kafkaBinding') -def binding(request: BindingRequest): - print(request.text(), flush=True) - - -app.run(50051) +from dapr.ext.grpc import App, BindingRequest + +app = App() + + +@app.binding('kafkaBinding') +def binding(request: BindingRequest): + print(request.text(), flush=True) + + +app.run(50051) diff --git a/examples/invoke-binding/invoke-output-binding.py b/examples/invoke-binding/invoke-output-binding.py index c4ce999dd..df5e44121 100644 --- a/examples/invoke-binding/invoke-output-binding.py +++ b/examples/invoke-binding/invoke-output-binding.py @@ -1,17 +1,17 @@ -import json -import time - -from dapr.clients import DaprClient - -with DaprClient() as d: - n = 0 - while True: - n += 1 - req_data = {'id': n, 'message': 'hello world'} - - print(f'Sending message id: {req_data["id"]}, message "{req_data["message"]}"', flush=True) - - # Create a typed message with content type and body - resp = d.invoke_binding('kafkaBinding', 'create', json.dumps(req_data)) - - time.sleep(1) +import json +import time + +from dapr.clients import DaprClient + +with DaprClient() as d: + n = 0 + while True: + n += 1 + req_data = {'id': n, 'message': 'hello world'} + + print(f'Sending message id: {req_data["id"]}, message "{req_data["message"]}"', flush=True) + + # Create a typed message with content type and body + resp = d.invoke_binding('kafkaBinding', 'create', json.dumps(req_data)) + + time.sleep(1) diff --git a/examples/invoke-custom-data/README.md b/examples/invoke-custom-data/README.md index e72d74a74..63a84e304 100644 --- a/examples/invoke-custom-data/README.md +++ b/examples/invoke-custom-data/README.md @@ -1,94 +1,94 @@ -# Example - Invoke a service with custom data - -This example utilizes a receiver and a caller for the OnInvoke / Invoke functionality. It will create a gRPC server and bind the OnInvoke method, which gets called after a client sends a direct method invocation. - -> **Note:** Make sure to use the latest proto bindings and have them available under `dapr_pb2` and `daprclient_pb2` - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## How To - Run Example - -To run this example, the following steps should be followed: - - -1. Compile Protobuf for Custom Response - - ```bash - python3 -m grpc_tools.protoc --proto_path=./proto/ --python_out=./proto/ --grpc_python_out=./proto/ ./proto/response.proto - ``` - -2. Start Receiver (expose gRPC server receiver on port 50051) - - - - ```bash - dapr run --app-id invoke-receiver --app-protocol grpc --app-port 50051 python3 invoke-receiver.py - ``` - - - -3. Start Caller - - - - ```bash - dapr run --app-id invoke-caller --app-protocol grpc python3 invoke-caller.py - ``` - - - -Expected output from caller: - - ``` - == APP == isSuccess: true - == APP == code: 200 - == APP == message: "Hello World - Success!" - == APP == - ``` - -Expected output from receiver: - - ``` - == APP == {'user-agent': ['grpc-go/1.33.1'], 'x-forwarded-host': ['MyPC'], 'x-forwarded-for': ['192. 168.1.3'], 'forwarded': ['for=192.168.1.3;by=192.168.1.3; host=MyPC'], 'grpc-trace-bin': [b'\x00\x00\x90Zc\x17\xaav?5)L\xcd]>. \x88>\x01\x81\xe9\x9c\xbd\x01x\xfc\xc5\x02\x01']} - == APP == SOME_DATA - ``` - -4. Cleanup - - - -```bash -dapr stop --app-id invoke-receiver -``` - - +# Example - Invoke a service with custom data + +This example utilizes a receiver and a caller for the OnInvoke / Invoke functionality. It will create a gRPC server and bind the OnInvoke method, which gets called after a client sends a direct method invocation. + +> **Note:** Make sure to use the latest proto bindings and have them available under `dapr_pb2` and `daprclient_pb2` + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## How To - Run Example + +To run this example, the following steps should be followed: + + +1. Compile Protobuf for Custom Response + + ```bash + python3 -m grpc_tools.protoc --proto_path=./proto/ --python_out=./proto/ --grpc_python_out=./proto/ ./proto/response.proto + ``` + +2. Start Receiver (expose gRPC server receiver on port 50051) + + + + ```bash + dapr run --app-id invoke-receiver --app-protocol grpc --app-port 50051 python3 invoke-receiver.py + ``` + + + +3. Start Caller + + + + ```bash + dapr run --app-id invoke-caller --app-protocol grpc python3 invoke-caller.py + ``` + + + +Expected output from caller: + + ``` + == APP == isSuccess: true + == APP == code: 200 + == APP == message: "Hello World - Success!" + == APP == + ``` + +Expected output from receiver: + + ``` + == APP == {'user-agent': ['grpc-go/1.33.1'], 'x-forwarded-host': ['MyPC'], 'x-forwarded-for': ['192. 168.1.3'], 'forwarded': ['for=192.168.1.3;by=192.168.1.3; host=MyPC'], 'grpc-trace-bin': [b'\x00\x00\x90Zc\x17\xaav?5)L\xcd]>. \x88>\x01\x81\xe9\x9c\xbd\x01x\xfc\xc5\x02\x01']} + == APP == SOME_DATA + ``` + +4. Cleanup + + + +```bash +dapr stop --app-id invoke-receiver +``` + + diff --git a/examples/invoke-custom-data/invoke-caller.py b/examples/invoke-custom-data/invoke-caller.py index 27dabd4de..57aff50b6 100644 --- a/examples/invoke-custom-data/invoke-caller.py +++ b/examples/invoke-custom-data/invoke-caller.py @@ -1,18 +1,18 @@ -from dapr.clients import DaprClient - -import proto.response_pb2 as response_messages - -with DaprClient() as d: - # Create a typed message with content type and body - resp = d.invoke_method( - app_id='invoke-receiver', - method_name='my_method', - data=b'SOME_DATA', - content_type='text/plain; charset=UTF-8', - ) - - res = response_messages.CustomResponse() - resp.unpack(res) - - # Print Result - print(res, flush=True) +from dapr.clients import DaprClient + +import proto.response_pb2 as response_messages + +with DaprClient() as d: + # Create a typed message with content type and body + resp = d.invoke_method( + app_id='invoke-receiver', + method_name='my_method', + data=b'SOME_DATA', + content_type='text/plain; charset=UTF-8', + ) + + res = response_messages.CustomResponse() + resp.unpack(res) + + # Print Result + print(res, flush=True) diff --git a/examples/invoke-custom-data/invoke-receiver.py b/examples/invoke-custom-data/invoke-receiver.py index e2ad83ce5..8e33cb988 100644 --- a/examples/invoke-custom-data/invoke-receiver.py +++ b/examples/invoke-custom-data/invoke-receiver.py @@ -1,18 +1,18 @@ -from dapr.ext.grpc import App, InvokeMethodRequest - -import proto.response_pb2 as response_messages - -app = App() - - -@app.method('my_method') -def mymethod(request: InvokeMethodRequest): - print(request.metadata, flush=True) - print(request.text(), flush=True) - - return response_messages.CustomResponse( - isSuccess=True, code=200, message='Hello World - Success!' - ) - - -app.run(50051) +from dapr.ext.grpc import App, InvokeMethodRequest + +import proto.response_pb2 as response_messages + +app = App() + + +@app.method('my_method') +def mymethod(request: InvokeMethodRequest): + print(request.metadata, flush=True) + print(request.text(), flush=True) + + return response_messages.CustomResponse( + isSuccess=True, code=200, message='Hello World - Success!' + ) + + +app.run(50051) diff --git a/examples/invoke-custom-data/proto/response.proto b/examples/invoke-custom-data/proto/response.proto index 76797e9fb..1fc4780a0 100644 --- a/examples/invoke-custom-data/proto/response.proto +++ b/examples/invoke-custom-data/proto/response.proto @@ -1,7 +1,7 @@ -syntax = "proto3"; - -message CustomResponse { - bool isSuccess = 1; - int32 code = 2; - string message = 3; +syntax = "proto3"; + +message CustomResponse { + bool isSuccess = 1; + int32 code = 2; + string message = 3; } \ No newline at end of file diff --git a/examples/invoke-custom-data/proto/response_pb2.py b/examples/invoke-custom-data/proto/response_pb2.py index 373ce113f..6aa6faa35 100644 --- a/examples/invoke-custom-data/proto/response_pb2.py +++ b/examples/invoke-custom-data/proto/response_pb2.py @@ -1,36 +1,36 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: response.proto -"""Generated protocol buffer code.""" -from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( - b'\n\x0eresponse.proto"B\n\x0e\x43ustomResponse\x12\x11\n\tisSuccess\x18\x01 \x01(\x08\x12\x0c\n\x04\x63ode\x18\x02 \x01(\x05\x12\x0f\n\x07message\x18\x03 \x01(\tb\x06proto3' -) - - -_CUSTOMRESPONSE = DESCRIPTOR.message_types_by_name['CustomResponse'] -CustomResponse = _reflection.GeneratedProtocolMessageType( - 'CustomResponse', - (_message.Message,), - { - 'DESCRIPTOR': _CUSTOMRESPONSE, - '__module__': 'response_pb2', - # @@protoc_insertion_point(class_scope:CustomResponse) - }, -) -_sym_db.RegisterMessage(CustomResponse) - -if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _CUSTOMRESPONSE._serialized_start = 18 - _CUSTOMRESPONSE._serialized_end = 84 -# @@protoc_insertion_point(module_scope) +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: response.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x0eresponse.proto"B\n\x0e\x43ustomResponse\x12\x11\n\tisSuccess\x18\x01 \x01(\x08\x12\x0c\n\x04\x63ode\x18\x02 \x01(\x05\x12\x0f\n\x07message\x18\x03 \x01(\tb\x06proto3' +) + + +_CUSTOMRESPONSE = DESCRIPTOR.message_types_by_name['CustomResponse'] +CustomResponse = _reflection.GeneratedProtocolMessageType( + 'CustomResponse', + (_message.Message,), + { + 'DESCRIPTOR': _CUSTOMRESPONSE, + '__module__': 'response_pb2', + # @@protoc_insertion_point(class_scope:CustomResponse) + }, +) +_sym_db.RegisterMessage(CustomResponse) + +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _CUSTOMRESPONSE._serialized_start = 18 + _CUSTOMRESPONSE._serialized_end = 84 +# @@protoc_insertion_point(module_scope) diff --git a/examples/invoke-custom-data/proto/response_pb2_grpc.py b/examples/invoke-custom-data/proto/response_pb2_grpc.py index 8a9393943..21976f4c6 100644 --- a/examples/invoke-custom-data/proto/response_pb2_grpc.py +++ b/examples/invoke-custom-data/proto/response_pb2_grpc.py @@ -1,3 +1,3 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -"""Client and server classes corresponding to protobuf-defined services.""" -import grpc +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc diff --git a/examples/invoke-http/invoke-caller.py b/examples/invoke-http/invoke-caller.py index 6e2c3120d..2057366c8 100644 --- a/examples/invoke-http/invoke-caller.py +++ b/examples/invoke-http/invoke-caller.py @@ -1,23 +1,23 @@ -import json -import time - -from dapr.clients import DaprClient - -with DaprClient() as d: - req_data = {'id': 1, 'message': 'hello world'} - - while True: - # Create a typed message with content type and body - resp = d.invoke_method( - 'invoke-receiver', - 'my-method', - http_verb='POST', - data=json.dumps(req_data), - ) - - # Print the response - print(resp.content_type, flush=True) - print(resp.text(), flush=True) - print(str(resp.status_code), flush=True) - - time.sleep(2) +import json +import time + +from dapr.clients import DaprClient + +with DaprClient() as d: + req_data = {'id': 1, 'message': 'hello world'} + + while True: + # Create a typed message with content type and body + resp = d.invoke_method( + 'invoke-receiver', + 'my-method', + http_verb='POST', + data=json.dumps(req_data), + ) + + # Print the response + print(resp.content_type, flush=True) + print(resp.text(), flush=True) + print(str(resp.status_code), flush=True) + + time.sleep(2) diff --git a/examples/invoke-http/invoke-receiver.py b/examples/invoke-http/invoke-receiver.py index 95ac31929..46dee2131 100644 --- a/examples/invoke-http/invoke-receiver.py +++ b/examples/invoke-http/invoke-receiver.py @@ -1,15 +1,15 @@ -# from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse -from flask import Flask, request -import json - -app = Flask(__name__) - - -@app.route('/my-method', methods=['POST']) -def getOrder(): - data = request.json - print('Order received : ' + json.dumps(data), flush=True) - return json.dumps({'success': True}), 200, {'ContentType': 'application/json'} - - -app.run(port=8088) +# from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse +from flask import Flask, request +import json + +app = Flask(__name__) + + +@app.route('/my-method', methods=['POST']) +def getOrder(): + data = request.json + print('Order received : ' + json.dumps(data), flush=True) + return json.dumps({'success': True}), 200, {'ContentType': 'application/json'} + + +app.run(port=8088) diff --git a/examples/invoke-simple/Dockerfile b/examples/invoke-simple/Dockerfile index 892d3f624..0e58edeb3 100644 --- a/examples/invoke-simple/Dockerfile +++ b/examples/invoke-simple/Dockerfile @@ -1,10 +1,10 @@ -FROM python:3.9-slim - -WORKDIR /app - -ADD requirements.txt . -RUN pip install -r requirements.txt - -COPY *.py /app/ - -CMD [ "python", "invoke-receiver.py" ] +FROM python:3.9-slim + +WORKDIR /app + +ADD requirements.txt . +RUN pip install -r requirements.txt + +COPY *.py /app/ + +CMD [ "python", "invoke-receiver.py" ] diff --git a/examples/invoke-simple/README.md b/examples/invoke-simple/README.md index 7d78f0cce..3727300dd 100644 --- a/examples/invoke-simple/README.md +++ b/examples/invoke-simple/README.md @@ -1,118 +1,118 @@ -# Example - Invoke a service - -This example utilizes a receiver and a caller for the OnInvoke / Invoke functionality. It will create a gRPC server and bind the OnInvoke method, which gets called after a client sends a direct method invocation. - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Running in self-hosted mode - -Run the following command in a terminal/command-prompt: - - - -```bash -# 1. Start Receiver (expose gRPC server receiver on port 50051) -dapr run --app-id invoke-receiver --app-protocol grpc --app-port 50051 python3 invoke-receiver.py -``` - - - -In another terminal/command prompt run: - - - -```bash -# 2. Start Caller -dapr run --app-id invoke-caller --app-protocol grpc --dapr-http-port 3500 python3 invoke-caller.py -``` - - - -## Cleanup - - - -```bash -dapr stop --app-id invoke-caller -dapr stop --app-id invoke-receiver -``` - - - -## Running in Kubernetes mode - -1. Build docker image - - ``` - docker build -t [your registry]/invokesimple:latest . - ``` - -2. Push docker image - - ``` - docker push [your registry]/invokesimple:latest - ``` - -3. Edit image name to `[your registry]/invokesimple:latest` in deploy/*.yaml - -4. Deploy applications - - ``` - kubectl apply -f ./deploy/ - ``` - -5. See logs for the apps and sidecars - - Logs for caller sidecar: - ``` - dapr logs -a invoke-caller -k - ``` - - Logs for caller app: - ``` - kubectl logs -l app="invokecaller" -c invokecaller - ``` - - Logs for receiver sidecar: - ``` - dapr logs -a invoke-receiver -k - ``` - - Logs for receiver app: - ``` - kubectl logs -l app="invokereceiver" -c invokereceiver - ``` +# Example - Invoke a service + +This example utilizes a receiver and a caller for the OnInvoke / Invoke functionality. It will create a gRPC server and bind the OnInvoke method, which gets called after a client sends a direct method invocation. + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Running in self-hosted mode + +Run the following command in a terminal/command-prompt: + + + +```bash +# 1. Start Receiver (expose gRPC server receiver on port 50051) +dapr run --app-id invoke-receiver --app-protocol grpc --app-port 50051 python3 invoke-receiver.py +``` + + + +In another terminal/command prompt run: + + + +```bash +# 2. Start Caller +dapr run --app-id invoke-caller --app-protocol grpc --dapr-http-port 3500 python3 invoke-caller.py +``` + + + +## Cleanup + + + +```bash +dapr stop --app-id invoke-caller +dapr stop --app-id invoke-receiver +``` + + + +## Running in Kubernetes mode + +1. Build docker image + + ``` + docker build -t [your registry]/invokesimple:latest . + ``` + +2. Push docker image + + ``` + docker push [your registry]/invokesimple:latest + ``` + +3. Edit image name to `[your registry]/invokesimple:latest` in deploy/*.yaml + +4. Deploy applications + + ``` + kubectl apply -f ./deploy/ + ``` + +5. See logs for the apps and sidecars + + Logs for caller sidecar: + ``` + dapr logs -a invoke-caller -k + ``` + + Logs for caller app: + ``` + kubectl logs -l app="invokecaller" -c invokecaller + ``` + + Logs for receiver sidecar: + ``` + dapr logs -a invoke-receiver -k + ``` + + Logs for receiver app: + ``` + kubectl logs -l app="invokereceiver" -c invokereceiver + ``` diff --git a/examples/invoke-simple/deploy/invoke-caller.yaml b/examples/invoke-simple/deploy/invoke-caller.yaml index 0c60fd6f8..8ee10b9be 100644 --- a/examples/invoke-simple/deploy/invoke-caller.yaml +++ b/examples/invoke-simple/deploy/invoke-caller.yaml @@ -1,37 +1,37 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: invokecaller - labels: - app: invokecaller -spec: - replicas: 1 - selector: - matchLabels: - app: invokecaller - template: - metadata: - labels: - app: invokecaller - annotations: - dapr.io/enabled: "true" - dapr.io/app-id: "invoke-caller" - dapr.io/app-protocol: "grpc" - spec: - containers: - - name: invokecaller - image: invokesimple:latest # EDIT HERE: Replace the image name - command: ["python"] - args: ["/app/invoke-caller.py"] - imagePullPolicy: Always +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: invokecaller + labels: + app: invokecaller +spec: + replicas: 1 + selector: + matchLabels: + app: invokecaller + template: + metadata: + labels: + app: invokecaller + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "invoke-caller" + dapr.io/app-protocol: "grpc" + spec: + containers: + - name: invokecaller + image: invokesimple:latest # EDIT HERE: Replace the image name + command: ["python"] + args: ["/app/invoke-caller.py"] + imagePullPolicy: Always diff --git a/examples/invoke-simple/deploy/invoke-receiver.yaml b/examples/invoke-simple/deploy/invoke-receiver.yaml index 07cd4804a..4b4cb0688 100644 --- a/examples/invoke-simple/deploy/invoke-receiver.yaml +++ b/examples/invoke-simple/deploy/invoke-receiver.yaml @@ -1,40 +1,40 @@ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: invokereceiver - labels: - app: invokereceiver -spec: - replicas: 1 - selector: - matchLabels: - app: invokereceiver - template: - metadata: - labels: - app: invokereceiver - annotations: - dapr.io/enabled: "true" - dapr.io/app-id: "invoke-receiver" - dapr.io/app-protocol: "grpc" - dapr.io/app-port: "50051" - spec: - containers: - - name: invokereceiver - image: invokesimple:latest # EDIT HERE: Replace the image name - command: ["python"] - args: ["/app/invoke-receiver.py"] - ports: - - containerPort: 3000 - imagePullPolicy: Always +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: invokereceiver + labels: + app: invokereceiver +spec: + replicas: 1 + selector: + matchLabels: + app: invokereceiver + template: + metadata: + labels: + app: invokereceiver + annotations: + dapr.io/enabled: "true" + dapr.io/app-id: "invoke-receiver" + dapr.io/app-protocol: "grpc" + dapr.io/app-port: "50051" + spec: + containers: + - name: invokereceiver + image: invokesimple:latest # EDIT HERE: Replace the image name + command: ["python"] + args: ["/app/invoke-receiver.py"] + ports: + - containerPort: 3000 + imagePullPolicy: Always diff --git a/examples/invoke-simple/invoke-caller.py b/examples/invoke-simple/invoke-caller.py index 5c5773ea2..f756a9038 100644 --- a/examples/invoke-simple/invoke-caller.py +++ b/examples/invoke-simple/invoke-caller.py @@ -1,22 +1,22 @@ -import json -import time - -from dapr.clients import DaprClient - -with DaprClient() as d: - req_data = {'id': 1, 'message': 'hello world'} - - while True: - # Create a typed message with content type and body - resp = d.invoke_method( - 'invoke-receiver', - 'my-method', - data=json.dumps(req_data), - ) - - # Print the response - print(resp.content_type, flush=True) - print(resp.text(), flush=True) - print(str(resp.status_code), flush=True) - - time.sleep(2) +import json +import time + +from dapr.clients import DaprClient + +with DaprClient() as d: + req_data = {'id': 1, 'message': 'hello world'} + + while True: + # Create a typed message with content type and body + resp = d.invoke_method( + 'invoke-receiver', + 'my-method', + data=json.dumps(req_data), + ) + + # Print the response + print(resp.content_type, flush=True) + print(resp.text(), flush=True) + print(str(resp.status_code), flush=True) + + time.sleep(2) diff --git a/examples/invoke-simple/invoke-receiver.py b/examples/invoke-simple/invoke-receiver.py index 2d1f0c2ee..f7098e831 100644 --- a/examples/invoke-simple/invoke-receiver.py +++ b/examples/invoke-simple/invoke-receiver.py @@ -1,14 +1,14 @@ -from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse - -app = App() - - -@app.method(name='my-method') -def mymethod(request: InvokeMethodRequest) -> InvokeMethodResponse: - print(request.metadata, flush=True) - print(request.text(), flush=True) - - return InvokeMethodResponse(b'INVOKE_RECEIVED', 'text/plain; charset=UTF-8') - - -app.run(50051) +from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse + +app = App() + + +@app.method(name='my-method') +def mymethod(request: InvokeMethodRequest) -> InvokeMethodResponse: + print(request.metadata, flush=True) + print(request.text(), flush=True) + + return InvokeMethodResponse(b'INVOKE_RECEIVED', 'text/plain; charset=UTF-8') + + +app.run(50051) diff --git a/examples/invoke-simple/requirements.txt b/examples/invoke-simple/requirements.txt index 0ba2b12d4..928c96e76 100644 --- a/examples/invoke-simple/requirements.txt +++ b/examples/invoke-simple/requirements.txt @@ -1,2 +1,2 @@ -dapr-ext-grpc-dev >= 1.13.0rc1.dev -dapr-dev >= 1.13.0rc1.dev +dapr-ext-grpc-dev >= 1.13.0rc1.dev +dapr-dev >= 1.13.0rc1.dev diff --git a/examples/metadata/README.md b/examples/metadata/README.md index 3f913285b..1c3e51783 100644 --- a/examples/metadata/README.md +++ b/examples/metadata/README.md @@ -1,77 +1,77 @@ -# Example - Inspect Dapr runtime metadata - -This example demonstrates the usage of Dapr [Metadata API] and of the two -two methods in that API: -1. **get_metadata**: Gets the Dapr sidecar information provided by the Metadata - Endpoint. -2. **set_metadata**: Adds a custom label to the Dapr sidecar information stored - by the Metadata endpoint. - -It creates a client using `DaprClient`, uses a set of components defined in the -[`./components/`](./components/) folder and invokes the two APIs from -[Metadata API]. - - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run the example - -To run this example, the following code can be utilized: - - - -```bash -dapr run --app-id=my-metadata-app --app-protocol grpc --resources-path components/ python3 app.py -``` - - -The output should be as follows: - -``` -== APP == First, we will assign a new custom label to Dapr sidecar -== APP == Now, we will fetch the sidecar's metadata -== APP == And this is what we got: -== APP == application_id: my-metadata-app -== APP == active_actors_count: {} -== APP == registered_components: -== APP == name=lockstore type=lock.redis version= capabilities=[] -== APP == name=pubsub type=pubsub.redis version=v1 capabilities=[] -== APP == name=statestore type=state.redis version=v1 capabilities=['ACTOR', 'ETAG', 'TRANSACTIONAL', 'TTL'] -== APP == We will update our custom label value and check it was persisted -== APP == We added a custom label named [is-this-our-metadata-example] -== APP == Its old value was [yes] but now it is [You bet it is!] -``` - -## Error Handling - -The Dapr python-sdk will pass through errors that it receives from the Dapr runtime. - -[Metadata API]: https://docs.dapr.io/reference/api/metadata_api/ +# Example - Inspect Dapr runtime metadata + +This example demonstrates the usage of Dapr [Metadata API] and of the two +two methods in that API: +1. **get_metadata**: Gets the Dapr sidecar information provided by the Metadata + Endpoint. +2. **set_metadata**: Adds a custom label to the Dapr sidecar information stored + by the Metadata endpoint. + +It creates a client using `DaprClient`, uses a set of components defined in the +[`./components/`](./components/) folder and invokes the two APIs from +[Metadata API]. + + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run the example + +To run this example, the following code can be utilized: + + + +```bash +dapr run --app-id=my-metadata-app --app-protocol grpc --resources-path components/ python3 app.py +``` + + +The output should be as follows: + +``` +== APP == First, we will assign a new custom label to Dapr sidecar +== APP == Now, we will fetch the sidecar's metadata +== APP == And this is what we got: +== APP == application_id: my-metadata-app +== APP == active_actors_count: {} +== APP == registered_components: +== APP == name=lockstore type=lock.redis version= capabilities=[] +== APP == name=pubsub type=pubsub.redis version=v1 capabilities=[] +== APP == name=statestore type=state.redis version=v1 capabilities=['ACTOR', 'ETAG', 'TRANSACTIONAL', 'TTL'] +== APP == We will update our custom label value and check it was persisted +== APP == We added a custom label named [is-this-our-metadata-example] +== APP == Its old value was [yes] but now it is [You bet it is!] +``` + +## Error Handling + +The Dapr python-sdk will pass through errors that it receives from the Dapr runtime. + +[Metadata API]: https://docs.dapr.io/reference/api/metadata_api/ diff --git a/examples/metadata/app.py b/examples/metadata/app.py index df1b285d2..2a7c38869 100644 --- a/examples/metadata/app.py +++ b/examples/metadata/app.py @@ -1,49 +1,49 @@ -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -from dapr.clients import DaprClient - - -def main(): - extended_attribute_name = 'is-this-our-metadata-example' - - with DaprClient() as dapr: - print('First, we will assign a new custom label to Dapr sidecar') - # We do this so example can be made deterministic across - # multiple invocations. - original_value = 'yes' - dapr.set_metadata(extended_attribute_name, original_value) - - print("Now, we will fetch the sidecar's metadata") - metadata = dapr.get_metadata() - old_value = metadata.extended_metadata[extended_attribute_name] - - print('And this is what we got:') - print(f' application_id: {metadata.application_id}') - print(f' active_actors_count: {metadata.active_actors_count}') - print(' registered_components:') - for name, type, version, caps in sorted(metadata.registered_components): - print(f' name={name} type={type} version={version} capabilities={sorted(caps)}') - - print('We will update our custom label value and check it was persisted') - dapr.set_metadata(extended_attribute_name, 'You bet it is!') - metadata = dapr.get_metadata() - new_value = metadata.extended_metadata[extended_attribute_name] - print('We added a custom label named [%s]' % extended_attribute_name) - print('Its old value was [%s] but now it is [%s]' % (old_value, new_value)) - - print('And we are done 👋', flush=True) - - -if __name__ == '__main__': - main() +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +from dapr.clients import DaprClient + + +def main(): + extended_attribute_name = 'is-this-our-metadata-example' + + with DaprClient() as dapr: + print('First, we will assign a new custom label to Dapr sidecar') + # We do this so example can be made deterministic across + # multiple invocations. + original_value = 'yes' + dapr.set_metadata(extended_attribute_name, original_value) + + print("Now, we will fetch the sidecar's metadata") + metadata = dapr.get_metadata() + old_value = metadata.extended_metadata[extended_attribute_name] + + print('And this is what we got:') + print(f' application_id: {metadata.application_id}') + print(f' active_actors_count: {metadata.active_actors_count}') + print(' registered_components:') + for name, type, version, caps in sorted(metadata.registered_components): + print(f' name={name} type={type} version={version} capabilities={sorted(caps)}') + + print('We will update our custom label value and check it was persisted') + dapr.set_metadata(extended_attribute_name, 'You bet it is!') + metadata = dapr.get_metadata() + new_value = metadata.extended_metadata[extended_attribute_name] + print('We added a custom label named [%s]' % extended_attribute_name) + print('Its old value was [%s] but now it is [%s]' % (old_value, new_value)) + + print('And we are done 👋', flush=True) + + +if __name__ == '__main__': + main() diff --git a/examples/metadata/components/lockstore.yaml b/examples/metadata/components/lockstore.yaml index 32cf674bf..c3d2ee2ac 100644 --- a/examples/metadata/components/lockstore.yaml +++ b/examples/metadata/components/lockstore.yaml @@ -1,12 +1,12 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: lockstore - namespace: default -spec: - type: lock.redis - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: lockstore + namespace: default +spec: + type: lock.redis + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" diff --git a/examples/metadata/components/pubsub.yaml b/examples/metadata/components/pubsub.yaml index 18764d8ce..d52f73673 100644 --- a/examples/metadata/components/pubsub.yaml +++ b/examples/metadata/components/pubsub.yaml @@ -1,12 +1,12 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: pubsub -spec: - type: pubsub.redis - version: v1 - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: pubsub +spec: + type: pubsub.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" diff --git a/examples/metadata/components/statestore.yaml b/examples/metadata/components/statestore.yaml index 2f676bff8..d92614765 100644 --- a/examples/metadata/components/statestore.yaml +++ b/examples/metadata/components/statestore.yaml @@ -1,14 +1,14 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: statestore -spec: - type: state.redis - version: v1 - metadata: - - name: redisHost - value: localhost:6379 - - name: redisPassword - value: "" - - name: actorStateStore - value: "true" +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" diff --git a/examples/pubsub-simple/README.md b/examples/pubsub-simple/README.md index 97af9a4a9..e3fe32795 100644 --- a/examples/pubsub-simple/README.md +++ b/examples/pubsub-simple/README.md @@ -1,86 +1,86 @@ -# Example - Publish and subscribe to messages - -This example utilizes a publisher and a subscriber to show the pubsub pattern, it also shows `PublishEvent`, `OnTopicEvent`, `GetTopicSubscriptions`, and `TopicEventResponse` functionality. -It creates a publisher and calls the `publish_event` method in the `DaprClient`. -It will create a gRPC subscriber and bind the `OnTopicEvent` method, which gets triggered after a message is published to the subscribed topic. -The subscriber will tell dapr to retry delivery of the first message it receives, logging that the message will be retried, and printing it at least once to standard output. - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run the example - -Run the following command in a terminal/command prompt: - - - -```bash -# 1. Start Subscriber (expose gRPC server receiver on port 50051) -dapr run --app-id python-subscriber --app-protocol grpc --app-port 50051 python3 subscriber.py -``` - - - -In another terminal/command prompt run: - - - -```bash -# 2. Start Publisher -dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check python3 publisher.py -``` - - - -## Cleanup - - - -```bash -dapr stop --app-id python-subscriber -``` - - +# Example - Publish and subscribe to messages + +This example utilizes a publisher and a subscriber to show the pubsub pattern, it also shows `PublishEvent`, `OnTopicEvent`, `GetTopicSubscriptions`, and `TopicEventResponse` functionality. +It creates a publisher and calls the `publish_event` method in the `DaprClient`. +It will create a gRPC subscriber and bind the `OnTopicEvent` method, which gets triggered after a message is published to the subscribed topic. +The subscriber will tell dapr to retry delivery of the first message it receives, logging that the message will be retried, and printing it at least once to standard output. + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run the example + +Run the following command in a terminal/command prompt: + + + +```bash +# 1. Start Subscriber (expose gRPC server receiver on port 50051) +dapr run --app-id python-subscriber --app-protocol grpc --app-port 50051 python3 subscriber.py +``` + + + +In another terminal/command prompt run: + + + +```bash +# 2. Start Publisher +dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check python3 publisher.py +``` + + + +## Cleanup + + + +```bash +dapr stop --app-id python-subscriber +``` + + diff --git a/examples/pubsub-simple/publisher.py b/examples/pubsub-simple/publisher.py index f6681f9ea..7bc864f22 100644 --- a/examples/pubsub-simple/publisher.py +++ b/examples/pubsub-simple/publisher.py @@ -1,68 +1,68 @@ -# ------------------------------------------------------------ -# Copyright 2022 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -import json -import time - -from dapr.clients import DaprClient - -with DaprClient() as d: - id = 0 - while id < 3: - id += 1 - req_data = {'id': id, 'message': 'hello world'} - - # Create a typed message with content type and body - resp = d.publish_event( - pubsub_name='pubsub', - topic_name='TOPIC_A', - data=json.dumps(req_data), - data_content_type='application/json', - ) - - # Print the request - print(req_data, flush=True) - - time.sleep(1) - - # we can publish events to different topics but handle them with the same method - # by disabling topic validation in the subscriber - - id = 3 - while id < 6: - id += 1 - req_data = {'id': id, 'message': 'hello world'} - resp = d.publish_event( - pubsub_name='pubsub', - topic_name=f'topic/{id}', - data=json.dumps(req_data), - data_content_type='application/json', - ) - - # Print the request - print(req_data, flush=True) - - time.sleep(0.5) - - # This topic will fail - initiate a retry which gets routed to the dead letter topic - req_data['id'] = 7 - resp = d.publish_event( - pubsub_name='pubsub', - topic_name='TOPIC_D', - data=json.dumps(req_data), - data_content_type='application/json', - publish_metadata={'custommeta': 'somevalue'}, - ) - - # Print the request - print(req_data, flush=True) +# ------------------------------------------------------------ +# Copyright 2022 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +import json +import time + +from dapr.clients import DaprClient + +with DaprClient() as d: + id = 0 + while id < 3: + id += 1 + req_data = {'id': id, 'message': 'hello world'} + + # Create a typed message with content type and body + resp = d.publish_event( + pubsub_name='pubsub', + topic_name='TOPIC_A', + data=json.dumps(req_data), + data_content_type='application/json', + ) + + # Print the request + print(req_data, flush=True) + + time.sleep(1) + + # we can publish events to different topics but handle them with the same method + # by disabling topic validation in the subscriber + + id = 3 + while id < 6: + id += 1 + req_data = {'id': id, 'message': 'hello world'} + resp = d.publish_event( + pubsub_name='pubsub', + topic_name=f'topic/{id}', + data=json.dumps(req_data), + data_content_type='application/json', + ) + + # Print the request + print(req_data, flush=True) + + time.sleep(0.5) + + # This topic will fail - initiate a retry which gets routed to the dead letter topic + req_data['id'] = 7 + resp = d.publish_event( + pubsub_name='pubsub', + topic_name='TOPIC_D', + data=json.dumps(req_data), + data_content_type='application/json', + publish_metadata={'custommeta': 'somevalue'}, + ) + + # Print the request + print(req_data, flush=True) diff --git a/examples/pubsub-simple/subscriber.py b/examples/pubsub-simple/subscriber.py index b905aaa6f..abd9c7a0d 100644 --- a/examples/pubsub-simple/subscriber.py +++ b/examples/pubsub-simple/subscriber.py @@ -1,93 +1,93 @@ -# ------------------------------------------------------------ -# Copyright 2022 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -from time import sleep -from cloudevents.sdk.event import v1 -from dapr.ext.grpc import App -from dapr.clients.grpc._response import TopicEventResponse -from dapr.proto import appcallback_v1 - -import json - -app = App() -should_retry = True # To control whether dapr should retry sending a message - - -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A') -def mytopic(event: v1.Event) -> TopicEventResponse: - global should_retry - data = json.loads(event.Data()) - print( - f'Subscriber received: id={data["id"]}, message="{data["message"]}", ' - f'content_type="{event.content_type}"', - flush=True, - ) - # event.Metadata() contains a dictionary of cloud event extensions and publish metadata - if should_retry: - should_retry = False # we only retry once in this example - sleep(0.5) # add some delay to help with ordering of expected logs - return TopicEventResponse('retry') - return TopicEventResponse('success') - - -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_D', dead_letter_topic='TOPIC_D_DEAD') -def fail_and_send_to_dead_topic(event: v1.Event) -> TopicEventResponse: - return TopicEventResponse('retry') - - -@app.subscribe(pubsub_name='pubsub', topic='TOPIC_D_DEAD') -def mytopic_dead(event: v1.Event) -> TopicEventResponse: - data = json.loads(event.Data()) - print( - f'Dead-Letter Subscriber received: id={data["id"]}, message="{data["message"]}", ' - f'content_type="{event.content_type}"', - flush=True, - ) - print('Dead-Letter Subscriber. Received via deadletter topic: ' + event.Subject(), flush=True) - print( - 'Dead-Letter Subscriber. Originally intended topic: ' + event.Extensions()['topic'], - flush=True, - ) - return TopicEventResponse('success') - - -# == for testing with Redis only == -# workaround as redis pubsub does not support wildcards -# we manually register the distinct topics -for id in range(4, 7): - app._servicer._registered_topics.append( - appcallback_v1.TopicSubscription(pubsub_name='pubsub', topic=f'topic/{id}') - ) -# ================================= - - -# this allows subscribing to all events sent to this app - useful for wildcard topics -@app.subscribe(pubsub_name='pubsub', topic='topic/#', disable_topic_validation=True) -def mytopic_wildcard(event: v1.Event) -> TopicEventResponse: - data = json.loads(event.Data()) - print( - f'Wildcard-Subscriber received: id={data["id"]}, message="{data["message"]}", ' - f'content_type="{event.content_type}"', - flush=True, - ) - return TopicEventResponse('success') - - -# Example of an unhealthy status -# def unhealthy(): -# raise ValueError("Not healthy") -# app.register_health_check(unhealthy) - -app.register_health_check(lambda: print('Healthy')) - -app.run(50051) +# ------------------------------------------------------------ +# Copyright 2022 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +from time import sleep +from cloudevents.sdk.event import v1 +from dapr.ext.grpc import App +from dapr.clients.grpc._response import TopicEventResponse +from dapr.proto import appcallback_v1 + +import json + +app = App() +should_retry = True # To control whether dapr should retry sending a message + + +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_A') +def mytopic(event: v1.Event) -> TopicEventResponse: + global should_retry + data = json.loads(event.Data()) + print( + f'Subscriber received: id={data["id"]}, message="{data["message"]}", ' + f'content_type="{event.content_type}"', + flush=True, + ) + # event.Metadata() contains a dictionary of cloud event extensions and publish metadata + if should_retry: + should_retry = False # we only retry once in this example + sleep(0.5) # add some delay to help with ordering of expected logs + return TopicEventResponse('retry') + return TopicEventResponse('success') + + +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_D', dead_letter_topic='TOPIC_D_DEAD') +def fail_and_send_to_dead_topic(event: v1.Event) -> TopicEventResponse: + return TopicEventResponse('retry') + + +@app.subscribe(pubsub_name='pubsub', topic='TOPIC_D_DEAD') +def mytopic_dead(event: v1.Event) -> TopicEventResponse: + data = json.loads(event.Data()) + print( + f'Dead-Letter Subscriber received: id={data["id"]}, message="{data["message"]}", ' + f'content_type="{event.content_type}"', + flush=True, + ) + print('Dead-Letter Subscriber. Received via deadletter topic: ' + event.Subject(), flush=True) + print( + 'Dead-Letter Subscriber. Originally intended topic: ' + event.Extensions()['topic'], + flush=True, + ) + return TopicEventResponse('success') + + +# == for testing with Redis only == +# workaround as redis pubsub does not support wildcards +# we manually register the distinct topics +for id in range(4, 7): + app._servicer._registered_topics.append( + appcallback_v1.TopicSubscription(pubsub_name='pubsub', topic=f'topic/{id}') + ) +# ================================= + + +# this allows subscribing to all events sent to this app - useful for wildcard topics +@app.subscribe(pubsub_name='pubsub', topic='topic/#', disable_topic_validation=True) +def mytopic_wildcard(event: v1.Event) -> TopicEventResponse: + data = json.loads(event.Data()) + print( + f'Wildcard-Subscriber received: id={data["id"]}, message="{data["message"]}", ' + f'content_type="{event.content_type}"', + flush=True, + ) + return TopicEventResponse('success') + + +# Example of an unhealthy status +# def unhealthy(): +# raise ValueError("Not healthy") +# app.register_health_check(unhealthy) + +app.register_health_check(lambda: print('Healthy')) + +app.run(50051) diff --git a/examples/pubsub-streaming-async/README.md b/examples/pubsub-streaming-async/README.md index 60c1cdef0..6eb6b7e2c 100644 --- a/examples/pubsub-streaming-async/README.md +++ b/examples/pubsub-streaming-async/README.md @@ -1,122 +1,122 @@ -# Example - Publish and subscribe to messages - -This example utilizes a publisher and a subscriber to show the bidirectional pubsub pattern. -It creates a publisher and calls the `publish_event` method in the `DaprClient`. -In the s`subscriber.py` file it creates a subscriber object that can call the `next_message` method to get new messages from the stream. After processing the new message, it returns a status to the stream. - - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr -``` - -## Run async example where users control reading messages off the stream - -Run the following command in a terminal/command prompt: - - - -```bash -# 1. Start Subscriber -dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber.py --topic=TOPIC_B1 -``` - - - -In another terminal/command prompt run: - - - -```bash -# 2. Start Publisher -dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_B1 -``` - - - -## Run async example with a handler function - -Run the following command in a terminal/command prompt: - - - -```bash -# 1. Start Subscriber -dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber-handler.py --topic=TOPIC_B2 -``` - - - -In another terminal/command prompt run: - - - -```bash -# 2. Start Publisher -dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_B2 -``` - - - - -## Cleanup - - +# Example - Publish and subscribe to messages + +This example utilizes a publisher and a subscriber to show the bidirectional pubsub pattern. +It creates a publisher and calls the `publish_event` method in the `DaprClient`. +In the s`subscriber.py` file it creates a subscriber object that can call the `next_message` method to get new messages from the stream. After processing the new message, it returns a status to the stream. + + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr +``` + +## Run async example where users control reading messages off the stream + +Run the following command in a terminal/command prompt: + + + +```bash +# 1. Start Subscriber +dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber.py --topic=TOPIC_B1 +``` + + + +In another terminal/command prompt run: + + + +```bash +# 2. Start Publisher +dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_B1 +``` + + + +## Run async example with a handler function + +Run the following command in a terminal/command prompt: + + + +```bash +# 1. Start Subscriber +dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber-handler.py --topic=TOPIC_B2 +``` + + + +In another terminal/command prompt run: + + + +```bash +# 2. Start Publisher +dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_B2 +``` + + + + +## Cleanup + + diff --git a/examples/pubsub-streaming-async/publisher.py b/examples/pubsub-streaming-async/publisher.py index e4abf3593..347569f08 100644 --- a/examples/pubsub-streaming-async/publisher.py +++ b/examples/pubsub-streaming-async/publisher.py @@ -1,52 +1,52 @@ -# ------------------------------------------------------------ -# Copyright 2022 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ -import argparse -import asyncio -import json - -from dapr.aio.clients import DaprClient - -parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') -parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') -args = parser.parse_args() - -topic_name = args.topic - - -async def publish_events(): - """ - Publishes events to a pubsub topic asynchronously - """ - - async with DaprClient() as d: - id = 0 - while id < 5: - id += 1 - req_data = {'id': id, 'message': 'hello world'} - - # Create a typed message with content type and body - await d.publish_event( - pubsub_name='pubsub', - topic_name=topic_name, - data=json.dumps(req_data), - data_content_type='application/json', - publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, - ) - - # Print the request - print(req_data, flush=True) - - await asyncio.sleep(1) - - -asyncio.run(publish_events()) +# ------------------------------------------------------------ +# Copyright 2022 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ +import argparse +import asyncio +import json + +from dapr.aio.clients import DaprClient + +parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') +parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') +args = parser.parse_args() + +topic_name = args.topic + + +async def publish_events(): + """ + Publishes events to a pubsub topic asynchronously + """ + + async with DaprClient() as d: + id = 0 + while id < 5: + id += 1 + req_data = {'id': id, 'message': 'hello world'} + + # Create a typed message with content type and body + await d.publish_event( + pubsub_name='pubsub', + topic_name=topic_name, + data=json.dumps(req_data), + data_content_type='application/json', + publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, + ) + + # Print the request + print(req_data, flush=True) + + await asyncio.sleep(1) + + +asyncio.run(publish_events()) diff --git a/examples/pubsub-streaming-async/subscriber-handler.py b/examples/pubsub-streaming-async/subscriber-handler.py index 34129ee77..666585e9b 100644 --- a/examples/pubsub-streaming-async/subscriber-handler.py +++ b/examples/pubsub-streaming-async/subscriber-handler.py @@ -1,50 +1,50 @@ -import argparse -import asyncio -from dapr.aio.clients import DaprClient -from dapr.clients.grpc._response import TopicEventResponse - -parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') -parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') -args = parser.parse_args() - -topic_name = args.topic -dlq_topic_name = topic_name + '_DEAD' - -counter = 0 - - -async def process_message(message) -> TopicEventResponse: - """ - Asynchronously processes the message and returns a TopicEventResponse. - """ - - print(f'Processing message: {message.data()} from {message.topic()}...') - global counter - counter += 1 - return TopicEventResponse('success') - - -async def main(): - """ - Main function to subscribe to a pubsub topic and handle messages asynchronously. - """ - async with DaprClient() as client: - # Subscribe to the pubsub topic with the message handler - close_fn = await client.subscribe_with_handler( - pubsub_name='pubsub', - topic=topic_name, - handler_fn=process_message, - dead_letter_topic=dlq_topic_name, - ) - - # Wait until 5 messages are processed - global counter - while counter < 5: - await asyncio.sleep(1) - - print('Closing subscription...') - await close_fn() - - -if __name__ == '__main__': - asyncio.run(main()) +import argparse +import asyncio +from dapr.aio.clients import DaprClient +from dapr.clients.grpc._response import TopicEventResponse + +parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') +parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') +args = parser.parse_args() + +topic_name = args.topic +dlq_topic_name = topic_name + '_DEAD' + +counter = 0 + + +async def process_message(message) -> TopicEventResponse: + """ + Asynchronously processes the message and returns a TopicEventResponse. + """ + + print(f'Processing message: {message.data()} from {message.topic()}...') + global counter + counter += 1 + return TopicEventResponse('success') + + +async def main(): + """ + Main function to subscribe to a pubsub topic and handle messages asynchronously. + """ + async with DaprClient() as client: + # Subscribe to the pubsub topic with the message handler + close_fn = await client.subscribe_with_handler( + pubsub_name='pubsub', + topic=topic_name, + handler_fn=process_message, + dead_letter_topic=dlq_topic_name, + ) + + # Wait until 5 messages are processed + global counter + while counter < 5: + await asyncio.sleep(1) + + print('Closing subscription...') + await close_fn() + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/examples/pubsub-streaming-async/subscriber.py b/examples/pubsub-streaming-async/subscriber.py index 7907bb5fe..1b2bcd9e6 100644 --- a/examples/pubsub-streaming-async/subscriber.py +++ b/examples/pubsub-streaming-async/subscriber.py @@ -1,67 +1,67 @@ -import argparse -import asyncio - -from dapr.aio.clients import DaprClient -from dapr.clients.grpc.subscription import StreamInactiveError -from dapr.common.pubsub.subscription import StreamCancelledError - -parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') -parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') -args = parser.parse_args() - -topic_name = args.topic -dlq_topic_name = topic_name + '_DEAD' - -counter = 0 - - -def process_message(message): - global counter - counter += 1 - # Process the message here - print(f'Processing message: {message.data()} from {message.topic()}...') - return 'success' - - -async def main(): - async with DaprClient() as client: - global counter - subscription = await client.subscribe( - pubsub_name='pubsub', topic=topic_name, dead_letter_topic=dlq_topic_name - ) - - try: - while counter < 5: - try: - message = await subscription.next_message() - if message is None: - print( - 'No message received within timeout period. ' - 'The stream might have been cancelled.' - ) - continue - - except StreamInactiveError: - print('Stream is inactive. Retrying...') - await asyncio.sleep(1) - continue - except StreamCancelledError as e: - print('Stream was cancelled') - break - # Process the message - response_status = process_message(message) - - if response_status == 'success': - await subscription.respond_success(message) - elif response_status == 'retry': - await subscription.respond_retry(message) - elif response_status == 'drop': - await subscription.respond_drop(message) - - finally: - print('Closing subscription...') - await subscription.close() - - -if __name__ == '__main__': - asyncio.run(main()) +import argparse +import asyncio + +from dapr.aio.clients import DaprClient +from dapr.clients.grpc.subscription import StreamInactiveError +from dapr.common.pubsub.subscription import StreamCancelledError + +parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') +parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') +args = parser.parse_args() + +topic_name = args.topic +dlq_topic_name = topic_name + '_DEAD' + +counter = 0 + + +def process_message(message): + global counter + counter += 1 + # Process the message here + print(f'Processing message: {message.data()} from {message.topic()}...') + return 'success' + + +async def main(): + async with DaprClient() as client: + global counter + subscription = await client.subscribe( + pubsub_name='pubsub', topic=topic_name, dead_letter_topic=dlq_topic_name + ) + + try: + while counter < 5: + try: + message = await subscription.next_message() + if message is None: + print( + 'No message received within timeout period. ' + 'The stream might have been cancelled.' + ) + continue + + except StreamInactiveError: + print('Stream is inactive. Retrying...') + await asyncio.sleep(1) + continue + except StreamCancelledError as e: + print('Stream was cancelled') + break + # Process the message + response_status = process_message(message) + + if response_status == 'success': + await subscription.respond_success(message) + elif response_status == 'retry': + await subscription.respond_retry(message) + elif response_status == 'drop': + await subscription.respond_drop(message) + + finally: + print('Closing subscription...') + await subscription.close() + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/examples/pubsub-streaming/README.md b/examples/pubsub-streaming/README.md index 156645223..69813db09 100644 --- a/examples/pubsub-streaming/README.md +++ b/examples/pubsub-streaming/README.md @@ -1,121 +1,121 @@ -# Example - Publish and subscribe to messages - -This example utilizes a publisher and a subscriber to show the bidirectional pubsub pattern. -It creates a publisher and calls the `publish_event` method in the `DaprClient`. -In the s`subscriber.py` file it creates a subscriber object that can call the `next_message` method to get new messages from the stream. After processing the new message, it returns a status to the stream. - - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr -``` - -## Run example where users control reading messages off the stream - -Run the following command in a terminal/command prompt: - - - -```bash -# 1. Start Subscriber -dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber.py --topic=TOPIC_A1 -``` - - - -In another terminal/command prompt run: - - - -```bash -# 2. Start Publisher -dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_A1 -``` - - - -## Run example with a handler function - -Run the following command in a terminal/command prompt: - - - -```bash -# 1. Start Subscriber -dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber-handler.py --topic=TOPIC_A2 -``` - - - -In another terminal/command prompt run: - - - -```bash -# 2. Start Publisher -dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_A2 -``` - - - -## Cleanup - - +# Example - Publish and subscribe to messages + +This example utilizes a publisher and a subscriber to show the bidirectional pubsub pattern. +It creates a publisher and calls the `publish_event` method in the `DaprClient`. +In the s`subscriber.py` file it creates a subscriber object that can call the `next_message` method to get new messages from the stream. After processing the new message, it returns a status to the stream. + + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr +``` + +## Run example where users control reading messages off the stream + +Run the following command in a terminal/command prompt: + + + +```bash +# 1. Start Subscriber +dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber.py --topic=TOPIC_A1 +``` + + + +In another terminal/command prompt run: + + + +```bash +# 2. Start Publisher +dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_A1 +``` + + + +## Run example with a handler function + +Run the following command in a terminal/command prompt: + + + +```bash +# 1. Start Subscriber +dapr run --app-id python-subscriber --app-protocol grpc -- python3 subscriber-handler.py --topic=TOPIC_A2 +``` + + + +In another terminal/command prompt run: + + + +```bash +# 2. Start Publisher +dapr run --app-id python-publisher --app-protocol grpc --dapr-grpc-port=3500 --enable-app-health-check -- python3 publisher.py --topic=TOPIC_A2 +``` + + + +## Cleanup + + diff --git a/examples/pubsub-streaming/publisher.py b/examples/pubsub-streaming/publisher.py index 6ae68c22a..61988dcf0 100644 --- a/examples/pubsub-streaming/publisher.py +++ b/examples/pubsub-streaming/publisher.py @@ -1,43 +1,43 @@ -# ------------------------------------------------------------ -# Copyright 2022 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ -import argparse -import json -import time - -from dapr.clients import DaprClient - -parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') -parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') -args = parser.parse_args() - -topic_name = args.topic - -with DaprClient() as d: - id = 0 - while id < 5: - id += 1 - req_data = {'id': id, 'message': 'hello world'} - - # Create a typed message with content type and body - resp = d.publish_event( - pubsub_name='pubsub', - topic_name=topic_name, - data=json.dumps(req_data), - data_content_type='application/json', - publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, - ) - - # Print the request - print(req_data, flush=True) - - time.sleep(1) +# ------------------------------------------------------------ +# Copyright 2022 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ +import argparse +import json +import time + +from dapr.clients import DaprClient + +parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') +parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') +args = parser.parse_args() + +topic_name = args.topic + +with DaprClient() as d: + id = 0 + while id < 5: + id += 1 + req_data = {'id': id, 'message': 'hello world'} + + # Create a typed message with content type and body + resp = d.publish_event( + pubsub_name='pubsub', + topic_name=topic_name, + data=json.dumps(req_data), + data_content_type='application/json', + publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, + ) + + # Print the request + print(req_data, flush=True) + + time.sleep(1) diff --git a/examples/pubsub-streaming/subscriber-handler.py b/examples/pubsub-streaming/subscriber-handler.py index 3a963fd21..09453434b 100644 --- a/examples/pubsub-streaming/subscriber-handler.py +++ b/examples/pubsub-streaming/subscriber-handler.py @@ -1,44 +1,44 @@ -import argparse -import time - -from dapr.clients import DaprClient -from dapr.clients.grpc._response import TopicEventResponse - -counter = 0 - -parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') -parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') -args = parser.parse_args() - -topic_name = args.topic -dlq_topic_name = topic_name + '_DEAD' - - -def process_message(message): - # Process the message here - global counter - counter += 1 - print(f'Processing message: {message.data()} from {message.topic()}...') - return TopicEventResponse('success') - - -def main(): - with DaprClient() as client: - # This will start a new thread that will listen for messages - # and process them in the `process_message` function - close_fn = client.subscribe_with_handler( - pubsub_name='pubsub', - topic=topic_name, - handler_fn=process_message, - dead_letter_topic=dlq_topic_name, - ) - - while counter < 5: - time.sleep(1) - - print('Closing subscription...') - close_fn() - - -if __name__ == '__main__': - main() +import argparse +import time + +from dapr.clients import DaprClient +from dapr.clients.grpc._response import TopicEventResponse + +counter = 0 + +parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') +parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') +args = parser.parse_args() + +topic_name = args.topic +dlq_topic_name = topic_name + '_DEAD' + + +def process_message(message): + # Process the message here + global counter + counter += 1 + print(f'Processing message: {message.data()} from {message.topic()}...') + return TopicEventResponse('success') + + +def main(): + with DaprClient() as client: + # This will start a new thread that will listen for messages + # and process them in the `process_message` function + close_fn = client.subscribe_with_handler( + pubsub_name='pubsub', + topic=topic_name, + handler_fn=process_message, + dead_letter_topic=dlq_topic_name, + ) + + while counter < 5: + time.sleep(1) + + print('Closing subscription...') + close_fn() + + +if __name__ == '__main__': + main() diff --git a/examples/pubsub-streaming/subscriber.py b/examples/pubsub-streaming/subscriber.py index 88744c88c..ad2a14c0d 100644 --- a/examples/pubsub-streaming/subscriber.py +++ b/examples/pubsub-streaming/subscriber.py @@ -1,76 +1,76 @@ -import argparse -import time - -from dapr.clients import DaprClient -from dapr.clients.grpc.subscription import StreamInactiveError -from dapr.common.pubsub.subscription import StreamCancelledError - -counter = 0 - -parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') -parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') -args = parser.parse_args() - -topic_name = args.topic -dlq_topic_name = topic_name + '_DEAD' - - -def process_message(message): - global counter - counter += 1 - # Process the message here - print(f'Processing message: {message.data()} from {message.topic()}...') - return 'success' - - -def main(): - with DaprClient() as client: - global counter - - try: - subscription = client.subscribe( - pubsub_name='pubsub', topic=topic_name, dead_letter_topic=dlq_topic_name - ) - except Exception as e: - print(f'Error occurred: {e}') - return - - try: - while counter < 5: - try: - message = subscription.next_message() - if message is None: - print( - 'No message received within timeout period. ' - 'The stream might have been cancelled.' - ) - continue - - except StreamInactiveError as e: - print('Stream is inactive. Retrying...') - time.sleep(1) - continue - except StreamCancelledError as e: - print('Stream was cancelled') - break - except Exception as e: - print(f'Error occurred: {e}') - pass - - # Process the message - response_status = process_message(message) - - if response_status == 'success': - subscription.respond_success(message) - elif response_status == 'retry': - subscription.respond_retry(message) - elif response_status == 'drop': - subscription.respond_drop(message) - - finally: - print('Closing subscription...') - subscription.close() - - -if __name__ == '__main__': - main() +import argparse +import time + +from dapr.clients import DaprClient +from dapr.clients.grpc.subscription import StreamInactiveError +from dapr.common.pubsub.subscription import StreamCancelledError + +counter = 0 + +parser = argparse.ArgumentParser(description='Publish events to a Dapr pub/sub topic.') +parser.add_argument('--topic', type=str, required=True, help='The topic name to publish to.') +args = parser.parse_args() + +topic_name = args.topic +dlq_topic_name = topic_name + '_DEAD' + + +def process_message(message): + global counter + counter += 1 + # Process the message here + print(f'Processing message: {message.data()} from {message.topic()}...') + return 'success' + + +def main(): + with DaprClient() as client: + global counter + + try: + subscription = client.subscribe( + pubsub_name='pubsub', topic=topic_name, dead_letter_topic=dlq_topic_name + ) + except Exception as e: + print(f'Error occurred: {e}') + return + + try: + while counter < 5: + try: + message = subscription.next_message() + if message is None: + print( + 'No message received within timeout period. ' + 'The stream might have been cancelled.' + ) + continue + + except StreamInactiveError as e: + print('Stream is inactive. Retrying...') + time.sleep(1) + continue + except StreamCancelledError as e: + print('Stream was cancelled') + break + except Exception as e: + print(f'Error occurred: {e}') + pass + + # Process the message + response_status = process_message(message) + + if response_status == 'success': + subscription.respond_success(message) + elif response_status == 'retry': + subscription.respond_retry(message) + elif response_status == 'drop': + subscription.respond_drop(message) + + finally: + print('Closing subscription...') + subscription.close() + + +if __name__ == '__main__': + main() diff --git a/examples/secret_store/README.md b/examples/secret_store/README.md index cf5b86e05..aff1747f0 100644 --- a/examples/secret_store/README.md +++ b/examples/secret_store/README.md @@ -1,118 +1,118 @@ -# Example - Retrieve a secret from a secret store - -This example utilizes a local secret store to show how to retrieve secrets using dapr -It creates a dapr client and calls the `get_secret` method in the `DaprClient`. -This example also illustrates the use of access control for secrets. - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run the example - -Change directory to this folder: -```bash -cd examples/secret_store -``` - -To run this example, use the following command: - - - -```bash -dapr run --app-id=secretsapp --app-protocol grpc --resources-path components/ python3 example.py -``` - - - -You should be able to see the following output: -``` -== APP == Got! -== APP == {'secretKey': 'secretValue'} -== APP == Got! -== APP == [('random', {'random': 'randomValue'}), ('secretKey', {'secretKey': 'secretValue'})] -== APP == Got! -== APP == {'random': 'randomValue'} -``` - -In `config.yaml` you can see that the `localsecretstore` secret store has been defined with some restricted permissions. - -```yaml -apiVersion: dapr.io/v1alpha1 -kind: Configuration -metadata: - name: daprConfig -spec: - secrets: - scopes: - - storeName: "localsecretstore" - defaultAccess: "deny" - allowedSecrets: ["secretKey",] -``` - -The above configuration defines that the default access permission for the `localsecretstore` is `deny` and that only the -key `secretKey` is allowed to be accessed from the store. - -To see this run the same `example.py` app with the following command: - - - -```bash -dapr run --app-id=secretsapp --app-protocol grpc --config config.yaml --resources-path components/ python3 example.py -``` - - - -The above command overrides the default configuration file with the `--config` flag. - -The output should be as follows: -``` -== APP == Got! -== APP == {'secretKey': 'secretValue'} -== APP == Got! -== APP == [('secretKey', {'secretKey': 'secretValue'})] -== APP == Got expected error for accessing random key -``` - -It can be seen that when it tried to get the random key again, it fails as by default the access is denied for any key -unless defined in the `allowedSecrets` list. - -## Cleanup - -Either press CTRL + C to quit the app or run the following command in a new terminal to stop the app -```bash -dapr stop --app-id=secretsapp -``` - - -You can replace local secret store with any other secret stores that dapr supports like Kubernetes, Hashicorp Vault, Azure KeyVault etc. - +# Example - Retrieve a secret from a secret store + +This example utilizes a local secret store to show how to retrieve secrets using dapr +It creates a dapr client and calls the `get_secret` method in the `DaprClient`. +This example also illustrates the use of access control for secrets. + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run the example + +Change directory to this folder: +```bash +cd examples/secret_store +``` + +To run this example, use the following command: + + + +```bash +dapr run --app-id=secretsapp --app-protocol grpc --resources-path components/ python3 example.py +``` + + + +You should be able to see the following output: +``` +== APP == Got! +== APP == {'secretKey': 'secretValue'} +== APP == Got! +== APP == [('random', {'random': 'randomValue'}), ('secretKey', {'secretKey': 'secretValue'})] +== APP == Got! +== APP == {'random': 'randomValue'} +``` + +In `config.yaml` you can see that the `localsecretstore` secret store has been defined with some restricted permissions. + +```yaml +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: daprConfig +spec: + secrets: + scopes: + - storeName: "localsecretstore" + defaultAccess: "deny" + allowedSecrets: ["secretKey",] +``` + +The above configuration defines that the default access permission for the `localsecretstore` is `deny` and that only the +key `secretKey` is allowed to be accessed from the store. + +To see this run the same `example.py` app with the following command: + + + +```bash +dapr run --app-id=secretsapp --app-protocol grpc --config config.yaml --resources-path components/ python3 example.py +``` + + + +The above command overrides the default configuration file with the `--config` flag. + +The output should be as follows: +``` +== APP == Got! +== APP == {'secretKey': 'secretValue'} +== APP == Got! +== APP == [('secretKey', {'secretKey': 'secretValue'})] +== APP == Got expected error for accessing random key +``` + +It can be seen that when it tried to get the random key again, it fails as by default the access is denied for any key +unless defined in the `allowedSecrets` list. + +## Cleanup + +Either press CTRL + C to quit the app or run the following command in a new terminal to stop the app +```bash +dapr stop --app-id=secretsapp +``` + + +You can replace local secret store with any other secret stores that dapr supports like Kubernetes, Hashicorp Vault, Azure KeyVault etc. + diff --git a/examples/secret_store/components/localsecretstore.yaml b/examples/secret_store/components/localsecretstore.yaml index 6e844e8a9..a08489243 100644 --- a/examples/secret_store/components/localsecretstore.yaml +++ b/examples/secret_store/components/localsecretstore.yaml @@ -1,12 +1,12 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: localsecretstore - namespace: default -spec: - type: secretstores.local.file - metadata: - - name: secretsFile - value: secrets.json - - name: nestedSeparator +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: localsecretstore + namespace: default +spec: + type: secretstores.local.file + metadata: + - name: secretsFile + value: secrets.json + - name: nestedSeparator value: ":" \ No newline at end of file diff --git a/examples/secret_store/config.yaml b/examples/secret_store/config.yaml index 81a629641..b9293b1a7 100644 --- a/examples/secret_store/config.yaml +++ b/examples/secret_store/config.yaml @@ -1,10 +1,10 @@ -apiVersion: dapr.io/v1alpha1 -kind: Configuration -metadata: - name: daprConfig -spec: - secrets: - scopes: - - storeName: "localsecretstore" - defaultAccess: "deny" +apiVersion: dapr.io/v1alpha1 +kind: Configuration +metadata: + name: daprConfig +spec: + secrets: + scopes: + - storeName: "localsecretstore" + defaultAccess: "deny" allowedSecrets: ["secretKey",] \ No newline at end of file diff --git a/examples/secret_store/example.py b/examples/secret_store/example.py index 22d9a5058..9c1fb450a 100644 --- a/examples/secret_store/example.py +++ b/examples/secret_store/example.py @@ -1,33 +1,33 @@ -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -from dapr.clients import DaprClient - -with DaprClient() as d: - key = 'secretKey' - randomKey = 'random' - storeName = 'localsecretstore' - - resp = d.get_secret(store_name=storeName, key=key) - print('Got!') - print(resp.secret) - resp = d.get_bulk_secret(store_name=storeName) - print('Got!') - # Converts dict into sorted list of tuples for deterministic output. - print(sorted(resp.secrets.items())) - try: - resp = d.get_secret(store_name=storeName, key=randomKey) - print('Got!') - print(resp.secret) - except: - print('Got expected error for accessing random key') +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +from dapr.clients import DaprClient + +with DaprClient() as d: + key = 'secretKey' + randomKey = 'random' + storeName = 'localsecretstore' + + resp = d.get_secret(store_name=storeName, key=key) + print('Got!') + print(resp.secret) + resp = d.get_bulk_secret(store_name=storeName) + print('Got!') + # Converts dict into sorted list of tuples for deterministic output. + print(sorted(resp.secrets.items())) + try: + resp = d.get_secret(store_name=storeName, key=randomKey) + print('Got!') + print(resp.secret) + except: + print('Got expected error for accessing random key') diff --git a/examples/secret_store/secrets.json b/examples/secret_store/secrets.json index 40e5ad529..6c19ca05d 100644 --- a/examples/secret_store/secrets.json +++ b/examples/secret_store/secrets.json @@ -1,4 +1,4 @@ -{ - "secretKey": "secretValue", - "random": "randomValue" +{ + "secretKey": "secretValue", + "random": "randomValue" } \ No newline at end of file diff --git a/examples/state_store/README.md b/examples/state_store/README.md index 7c323873a..047199cb8 100644 --- a/examples/state_store/README.md +++ b/examples/state_store/README.md @@ -1,75 +1,75 @@ -# Example - Save and get state - -This example demonstrates the Statestore APIs in Dapr. -It demonstrates the following APIs: -- **save state**: Save single or mutiple states to statestore -- **get state**: Get a single state from statestore -- **bulk get**: Get multiple states(Bulk get) from statestore -- **transaction**: Execute a transaction on supported statestores -- **delete state**: Delete specified key from statestore -- **etags**: Use of etag and error handling for etag mismatches - -It creates a client using `DaprClient` and calls all the state API methods available as example. -It uses the default configuration from Dapr init in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Run the example - -To run this example, the following code can be utilized: - - - -```bash -dapr run -- python3 state_store.py -``` - - -The output should be as follows: - -``` -== APP == State store has successfully saved value_1 with key_1 as key - -== APP == Cannot save due to bad etag. ErrorCode=StatusCode.ABORTED - -== APP == State store has successfully saved value_2 with key_2 as key - -== APP == State store has successfully saved value_3 with key_3 as key - -== APP == Cannot save bulk due to bad etags. ErrorCode=StatusCode.ABORTED - -== APP == Got value=b'value_1' eTag=1 - -== APP == Got items with etags: [(b'value_1_updated', '2'), (b'value_2', '2')] - -== APP == Got value after delete: b'' -``` - -## Error Handling - -The Dapr python-sdk will pass through errors that it receives from the Dapr runtime. In the case of an etag mismatch, the Dapr runtime will return StatusCode.ABORTED +# Example - Save and get state + +This example demonstrates the Statestore APIs in Dapr. +It demonstrates the following APIs: +- **save state**: Save single or mutiple states to statestore +- **get state**: Get a single state from statestore +- **bulk get**: Get multiple states(Bulk get) from statestore +- **transaction**: Execute a transaction on supported statestores +- **delete state**: Delete specified key from statestore +- **etags**: Use of etag and error handling for etag mismatches + +It creates a client using `DaprClient` and calls all the state API methods available as example. +It uses the default configuration from Dapr init in [self-hosted mode](https://github.com/dapr/cli#install-dapr-on-your-local-machine-self-hosted). + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Run the example + +To run this example, the following code can be utilized: + + + +```bash +dapr run -- python3 state_store.py +``` + + +The output should be as follows: + +``` +== APP == State store has successfully saved value_1 with key_1 as key + +== APP == Cannot save due to bad etag. ErrorCode=StatusCode.ABORTED + +== APP == State store has successfully saved value_2 with key_2 as key + +== APP == State store has successfully saved value_3 with key_3 as key + +== APP == Cannot save bulk due to bad etags. ErrorCode=StatusCode.ABORTED + +== APP == Got value=b'value_1' eTag=1 + +== APP == Got items with etags: [(b'value_1_updated', '2'), (b'value_2', '2')] + +== APP == Got value after delete: b'' +``` + +## Error Handling + +The Dapr python-sdk will pass through errors that it receives from the Dapr runtime. In the case of an etag mismatch, the Dapr runtime will return StatusCode.ABORTED diff --git a/examples/state_store/state_store.py b/examples/state_store/state_store.py index f87167f58..6f36b7b9d 100644 --- a/examples/state_store/state_store.py +++ b/examples/state_store/state_store.py @@ -1,93 +1,93 @@ -""" -dapr run python3 state_store.py -""" - -import grpc - -from dapr.clients import DaprClient - -from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType -from dapr.clients.grpc._state import StateItem - - -with DaprClient() as d: - storeName = 'statestore' - - key = 'key_1' - value = 'value_1' - updated_value = 'value_1_updated' - - another_key = 'key_2' - another_value = 'value_2' - - yet_another_key = 'key_3' - yet_another_value = 'value_3' - - # Save single state. - d.save_state(store_name=storeName, key=key, value=value) - print(f'State store has successfully saved {value} with {key} as key') - - # Save with an etag that is different from the one stored in the database. - try: - d.save_state(store_name=storeName, key=key, value=another_value, etag='9999') - except grpc.RpcError as err: - # StatusCode should be StatusCode.ABORTED. - print(f'Cannot save due to bad etag. ErrorCode={err.code()}') - - # For detailed error messages from the dapr runtime: - # print(f"Details={err.details()}) - - # Save multiple states. - d.save_bulk_state( - store_name=storeName, - states=[ - StateItem(key=another_key, value=another_value), - StateItem(key=yet_another_key, value=yet_another_value), - ], - ) - print(f'State store has successfully saved {another_value} with {another_key} as key') - print(f'State store has successfully saved {yet_another_value} with {yet_another_key} as key') - - # Save bulk with etag that is different from the one stored in the database. - try: - d.save_bulk_state( - store_name=storeName, - states=[ - StateItem(key=another_key, value=another_value, etag='999'), - StateItem(key=yet_another_key, value=yet_another_value, etag='999'), - ], - ) - except grpc.RpcError as err: - # StatusCode should be StatusCode.ABORTED. - print(f'Cannot save bulk due to bad etags. ErrorCode={err.code()}') - - # For detailed error messages from the dapr runtime: # print(f"Details={err.details()}) - - # Get one state by key. - state = d.get_state(store_name=storeName, key=key, state_metadata={'metakey': 'metavalue'}) - print(f'Got value={state.data} eTag={state.etag}') - - # Transaction upsert - d.execute_state_transaction( - store_name=storeName, - operations=[ - TransactionalStateOperation( - operation_type=TransactionOperationType.upsert, - key=key, - data=updated_value, - etag=state.etag, - ), - TransactionalStateOperation(key=another_key, data=another_value), - ], - ) - - # Batch get - items = d.get_bulk_state( - store_name=storeName, keys=[key, another_key], states_metadata={'metakey': 'metavalue'} - ).items - print(f'Got items with etags: {[(i.data, i.etag) for i in items]}') - - # Delete one state by key. - d.delete_state(store_name=storeName, key=key, state_metadata={'metakey': 'metavalue'}) - data = d.get_state(store_name=storeName, key=key).data - print(f'Got value after delete: {data}') +""" +dapr run python3 state_store.py +""" + +import grpc + +from dapr.clients import DaprClient + +from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType +from dapr.clients.grpc._state import StateItem + + +with DaprClient() as d: + storeName = 'statestore' + + key = 'key_1' + value = 'value_1' + updated_value = 'value_1_updated' + + another_key = 'key_2' + another_value = 'value_2' + + yet_another_key = 'key_3' + yet_another_value = 'value_3' + + # Save single state. + d.save_state(store_name=storeName, key=key, value=value) + print(f'State store has successfully saved {value} with {key} as key') + + # Save with an etag that is different from the one stored in the database. + try: + d.save_state(store_name=storeName, key=key, value=another_value, etag='9999') + except grpc.RpcError as err: + # StatusCode should be StatusCode.ABORTED. + print(f'Cannot save due to bad etag. ErrorCode={err.code()}') + + # For detailed error messages from the dapr runtime: + # print(f"Details={err.details()}) + + # Save multiple states. + d.save_bulk_state( + store_name=storeName, + states=[ + StateItem(key=another_key, value=another_value), + StateItem(key=yet_another_key, value=yet_another_value), + ], + ) + print(f'State store has successfully saved {another_value} with {another_key} as key') + print(f'State store has successfully saved {yet_another_value} with {yet_another_key} as key') + + # Save bulk with etag that is different from the one stored in the database. + try: + d.save_bulk_state( + store_name=storeName, + states=[ + StateItem(key=another_key, value=another_value, etag='999'), + StateItem(key=yet_another_key, value=yet_another_value, etag='999'), + ], + ) + except grpc.RpcError as err: + # StatusCode should be StatusCode.ABORTED. + print(f'Cannot save bulk due to bad etags. ErrorCode={err.code()}') + + # For detailed error messages from the dapr runtime: # print(f"Details={err.details()}) + + # Get one state by key. + state = d.get_state(store_name=storeName, key=key, state_metadata={'metakey': 'metavalue'}) + print(f'Got value={state.data} eTag={state.etag}') + + # Transaction upsert + d.execute_state_transaction( + store_name=storeName, + operations=[ + TransactionalStateOperation( + operation_type=TransactionOperationType.upsert, + key=key, + data=updated_value, + etag=state.etag, + ), + TransactionalStateOperation(key=another_key, data=another_value), + ], + ) + + # Batch get + items = d.get_bulk_state( + store_name=storeName, keys=[key, another_key], states_metadata={'metakey': 'metavalue'} + ).items + print(f'Got items with etags: {[(i.data, i.etag) for i in items]}') + + # Delete one state by key. + d.delete_state(store_name=storeName, key=key, state_metadata={'metakey': 'metavalue'}) + data = d.get_state(store_name=storeName, key=key).data + print(f'Got value after delete: {data}') diff --git a/examples/state_store_query/README.md b/examples/state_store_query/README.md index a6e8fcebc..7926b0247 100644 --- a/examples/state_store_query/README.md +++ b/examples/state_store_query/README.md @@ -1,100 +1,100 @@ -# Example - Query State Store - -This example demonstrates the State Store Query Alpha API in Dapr. -It demonstrates the following APIs: -- **querystatealpha1**: Queries a compatible Dapr state store - -> **Note:** Make sure to use the latest proto bindings - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -## Install Dapr python-SDK - - -```bash -pip3 install dapr dapr-ext-grpc -``` - -## Store the configuration in configurationstore - - -```bash -docker run -d --rm -p 27017:27017 --name mongodb mongo:5 -``` - - - -## Run the example - -Change directory to this folder: -```bash -cd examples/state_store_query -``` - -To run this example, start by importing the test data - - - -```bash -# Import the example dataset -dapr run --app-id demo --dapr-http-port 3500 --resources-path components -- curl -X POST -H "Content-Type: application/json" -d @dataset.json http://localhost:3500/v1.0/state/statestore -``` - - - -Now run the app - - - -```bash -dapr run --app-id queryexample --resources-path components/ -- python3 state_store_query.py -``` - - -You should be able to see the following output: -``` -== APP == 1 {"city": "Seattle", "person": {"id": 1036.0, "org": "Dev Ops"}, "state": "WA"} -== APP == 4 {"city": "Spokane", "person": {"id": 1042.0, "org": "Dev Ops"}, "state": "WA"} -== APP == 10 {"city": "New York", "person": {"id": 1054.0, "org": "Dev Ops"}, "state": "NY"} -== APP == Token: 3 -== APP == 9 {"city": "San Diego", "person": {"id": 1002.0, "org": "Finance"}, "state": "CA"} -== APP == 7 {"city": "San Francisco", "person": {"id": 1015.0, "org": "Dev Ops"}, "state": "CA"} -== APP == 3 {"city": "Sacramento", "person": {"id": 1071.0, "org": "Finance"}, "state": "CA"} -== APP == Token: 6 -``` - -Cleanup - - - -```bash -docker kill mongodb -``` - +# Example - Query State Store + +This example demonstrates the State Store Query Alpha API in Dapr. +It demonstrates the following APIs: +- **querystatealpha1**: Queries a compatible Dapr state store + +> **Note:** Make sure to use the latest proto bindings + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +## Install Dapr python-SDK + + +```bash +pip3 install dapr dapr-ext-grpc +``` + +## Store the configuration in configurationstore + + +```bash +docker run -d --rm -p 27017:27017 --name mongodb mongo:5 +``` + + + +## Run the example + +Change directory to this folder: +```bash +cd examples/state_store_query +``` + +To run this example, start by importing the test data + + + +```bash +# Import the example dataset +dapr run --app-id demo --dapr-http-port 3500 --resources-path components -- curl -X POST -H "Content-Type: application/json" -d @dataset.json http://localhost:3500/v1.0/state/statestore +``` + + + +Now run the app + + + +```bash +dapr run --app-id queryexample --resources-path components/ -- python3 state_store_query.py +``` + + +You should be able to see the following output: +``` +== APP == 1 {"city": "Seattle", "person": {"id": 1036.0, "org": "Dev Ops"}, "state": "WA"} +== APP == 4 {"city": "Spokane", "person": {"id": 1042.0, "org": "Dev Ops"}, "state": "WA"} +== APP == 10 {"city": "New York", "person": {"id": 1054.0, "org": "Dev Ops"}, "state": "NY"} +== APP == Token: 3 +== APP == 9 {"city": "San Diego", "person": {"id": 1002.0, "org": "Finance"}, "state": "CA"} +== APP == 7 {"city": "San Francisco", "person": {"id": 1015.0, "org": "Dev Ops"}, "state": "CA"} +== APP == 3 {"city": "Sacramento", "person": {"id": 1071.0, "org": "Finance"}, "state": "CA"} +== APP == Token: 6 +``` + +Cleanup + + + +```bash +docker kill mongodb +``` + \ No newline at end of file diff --git a/examples/state_store_query/components/mongodb.yml b/examples/state_store_query/components/mongodb.yml index 82c96d187..e588e43f5 100644 --- a/examples/state_store_query/components/mongodb.yml +++ b/examples/state_store_query/components/mongodb.yml @@ -1,10 +1,10 @@ -apiVersion: dapr.io/v1alpha1 -kind: Component -metadata: - name: statestore -spec: - type: state.mongodb - version: v1 - metadata: - - name: host - value: localhost:27017 +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: statestore +spec: + type: state.mongodb + version: v1 + metadata: + - name: host + value: localhost:27017 diff --git a/examples/state_store_query/dataset.json b/examples/state_store_query/dataset.json index 69935f9bd..2c2fd0493 100644 --- a/examples/state_store_query/dataset.json +++ b/examples/state_store_query/dataset.json @@ -1,112 +1,112 @@ -[ - { - "key": "1", - "value": { - "person": { - "org": "Dev Ops", - "id": 1036 - }, - "city": "Seattle", - "state": "WA" - } - }, - { - "key": "2", - "value": { - "person": { - "org": "Hardware", - "id": 1028 - }, - "city": "Portland", - "state": "OR" - } - }, - { - "key": "3", - "value": { - "person": { - "org": "Finance", - "id": 1071 - }, - "city": "Sacramento", - "state": "CA" - } - }, - { - "key": "4", - "value": { - "person": { - "org": "Dev Ops", - "id": 1042 - }, - "city": "Spokane", - "state": "WA" - } - }, - { - "key": "5", - "value": { - "person": { - "org": "Hardware", - "id": 1007 - }, - "city": "Los Angeles", - "state": "CA" - } - }, - { - "key": "6", - "value": { - "person": { - "org": "Finance", - "id": 1094 - }, - "city": "Eugene", - "state": "OR" - } - }, - { - "key": "7", - "value": { - "person": { - "org": "Dev Ops", - "id": 1015 - }, - "city": "San Francisco", - "state": "CA" - } - }, - { - "key": "8", - "value": { - "person": { - "org": "Hardware", - "id": 1077 - }, - "city": "Redmond", - "state": "WA" - } - }, - { - "key": "9", - "value": { - "person": { - "org": "Finance", - "id": 1002 - }, - "city": "San Diego", - "state": "CA" - } - }, - { - "key": "10", - "value": { - "person": { - "org": "Dev Ops", - "id": 1054 - }, - "city": "New York", - "state": "NY" - } - } -] +[ + { + "key": "1", + "value": { + "person": { + "org": "Dev Ops", + "id": 1036 + }, + "city": "Seattle", + "state": "WA" + } + }, + { + "key": "2", + "value": { + "person": { + "org": "Hardware", + "id": 1028 + }, + "city": "Portland", + "state": "OR" + } + }, + { + "key": "3", + "value": { + "person": { + "org": "Finance", + "id": 1071 + }, + "city": "Sacramento", + "state": "CA" + } + }, + { + "key": "4", + "value": { + "person": { + "org": "Dev Ops", + "id": 1042 + }, + "city": "Spokane", + "state": "WA" + } + }, + { + "key": "5", + "value": { + "person": { + "org": "Hardware", + "id": 1007 + }, + "city": "Los Angeles", + "state": "CA" + } + }, + { + "key": "6", + "value": { + "person": { + "org": "Finance", + "id": 1094 + }, + "city": "Eugene", + "state": "OR" + } + }, + { + "key": "7", + "value": { + "person": { + "org": "Dev Ops", + "id": 1015 + }, + "city": "San Francisco", + "state": "CA" + } + }, + { + "key": "8", + "value": { + "person": { + "org": "Hardware", + "id": 1077 + }, + "city": "Redmond", + "state": "WA" + } + }, + { + "key": "9", + "value": { + "person": { + "org": "Finance", + "id": 1002 + }, + "city": "San Diego", + "state": "CA" + } + }, + { + "key": "10", + "value": { + "person": { + "org": "Dev Ops", + "id": 1054 + }, + "city": "New York", + "state": "NY" + } + } +] diff --git a/examples/state_store_query/query-token.json b/examples/state_store_query/query-token.json index 61c21c3de..3e4c6c3c3 100644 --- a/examples/state_store_query/query-token.json +++ b/examples/state_store_query/query-token.json @@ -1,32 +1,32 @@ -{ - "filter": { - "OR": [ - { - "EQ": { "person.org": "Dev Ops" } - }, - { - "AND": [ - { - "EQ": { "person.org": "Finance" } - }, - { - "IN": { "state": [ "CA", "WA" ] } - } - ] - } - ] - }, - "sort": [ - { - "key": "state", - "order": "DESC" - }, - { - "key": "person.id" - } - ], - "page": { - "limit": 3, - "token": "3" - } -} +{ + "filter": { + "OR": [ + { + "EQ": { "person.org": "Dev Ops" } + }, + { + "AND": [ + { + "EQ": { "person.org": "Finance" } + }, + { + "IN": { "state": [ "CA", "WA" ] } + } + ] + } + ] + }, + "sort": [ + { + "key": "state", + "order": "DESC" + }, + { + "key": "person.id" + } + ], + "page": { + "limit": 3, + "token": "3" + } +} diff --git a/examples/state_store_query/query.json b/examples/state_store_query/query.json index e93f5bfdd..c6a9d9d0f 100644 --- a/examples/state_store_query/query.json +++ b/examples/state_store_query/query.json @@ -1,31 +1,31 @@ -{ - "filter": { - "OR": [ - { - "EQ": { "person.org": "Dev Ops" } - }, - { - "AND": [ - { - "EQ": { "person.org": "Finance" } - }, - { - "IN": { "state": [ "CA", "WA" ] } - } - ] - } - ] - }, - "sort": [ - { - "key": "state", - "order": "DESC" - }, - { - "key": "person.id" - } - ], - "page": { - "limit": 3 - } -} +{ + "filter": { + "OR": [ + { + "EQ": { "person.org": "Dev Ops" } + }, + { + "AND": [ + { + "EQ": { "person.org": "Finance" } + }, + { + "IN": { "state": [ "CA", "WA" ] } + } + ] + } + ] + }, + "sort": [ + { + "key": "state", + "order": "DESC" + }, + { + "key": "person.id" + } + ], + "page": { + "limit": 3 + } +} diff --git a/examples/state_store_query/state_store_query.py b/examples/state_store_query/state_store_query.py index f532f0eb0..a19f29ee5 100644 --- a/examples/state_store_query/state_store_query.py +++ b/examples/state_store_query/state_store_query.py @@ -1,27 +1,27 @@ -""" -dapr run python3 state_store_query.py -""" - -from dapr.clients import DaprClient - -import json - - -with DaprClient() as d: - store_name = 'statestore' - - # Query the state store - - query = open('query.json', 'r').read() - res = d.query_state(store_name=store_name, query=query) - for r in res.results: - print(r.key, json.dumps(json.loads(str(r.value, 'UTF-8')), sort_keys=True)) - print('Token:', res.token) - - # Get more results using a pagination token - - query = open('query-token.json', 'r').read() - res = d.query_state(store_name=store_name, query=query) - for r in res.results: - print(r.key, json.dumps(json.loads(str(r.value, 'UTF-8')), sort_keys=True)) - print('Token:', res.token) +""" +dapr run python3 state_store_query.py +""" + +from dapr.clients import DaprClient + +import json + + +with DaprClient() as d: + store_name = 'statestore' + + # Query the state store + + query = open('query.json', 'r').read() + res = d.query_state(store_name=store_name, query=query) + for r in res.results: + print(r.key, json.dumps(json.loads(str(r.value, 'UTF-8')), sort_keys=True)) + print('Token:', res.token) + + # Get more results using a pagination token + + query = open('query-token.json', 'r').read() + res = d.query_state(store_name=store_name, query=query) + for r in res.results: + print(r.key, json.dumps(json.loads(str(r.value, 'UTF-8')), sort_keys=True)) + print('Token:', res.token) diff --git a/examples/validate.sh b/examples/validate.sh index 202fcaedd..5a2bc10ea 100755 --- a/examples/validate.sh +++ b/examples/validate.sh @@ -1,4 +1,4 @@ -#!/bin/sh -echo "Home: $HOME" - -cd $1 && mm.py -l README.md +#!/bin/sh +echo "Home: $HOME" + +cd $1 && mm.py -l README.md diff --git a/examples/w3c-tracing/Dockerfile b/examples/w3c-tracing/Dockerfile index 892d3f624..0e58edeb3 100644 --- a/examples/w3c-tracing/Dockerfile +++ b/examples/w3c-tracing/Dockerfile @@ -1,10 +1,10 @@ -FROM python:3.9-slim - -WORKDIR /app - -ADD requirements.txt . -RUN pip install -r requirements.txt - -COPY *.py /app/ - -CMD [ "python", "invoke-receiver.py" ] +FROM python:3.9-slim + +WORKDIR /app + +ADD requirements.txt . +RUN pip install -r requirements.txt + +COPY *.py /app/ + +CMD [ "python", "invoke-receiver.py" ] diff --git a/examples/w3c-tracing/README.md b/examples/w3c-tracing/README.md index 8f694e8b2..d9caac95a 100644 --- a/examples/w3c-tracing/README.md +++ b/examples/w3c-tracing/README.md @@ -1,337 +1,337 @@ -# Example - Distributed tracing - -In this sample, we'll run two Python applications: a service application, which exposes two methods, and a client application which will invoke the methods from the service using Dapr. The code is instrumented with [OpenTelemetry SDK for Python](https://opentelemetry.io/docs/languages/python/). -This sample includes: - -- invoke-receiver: Exposes the methods to be remotely accessed -- invoke-caller: Invokes the exposed methods - -Also consider [getting started with observability in Dapr](https://github.com/dapr/quickstarts/tree/master/tutorials/observability). - -## Example overview - -This sample uses the Client provided in Dapr's Python SDK invoking a remote method and Zipkin to collect and display tracing data. - -## Pre-requisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -### Install dependencies - -Clone this repository: - -```sh -git clone https://github.com/dapr/python-sdk.git -cd python-sdk -``` - -Then get into the examples directory: - -```sh -cd examples/w3c-tracing -``` - -Install dependencies: - - - -```sh -pip3 install -r requirements.txt -``` - - - -### Verify Zipkin is running - -Run `docker ps` to see if the container `dapr_zipkin` is running locally: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -24d043379da2 daprio/dapr "./placement" 2 days ago Up 32 hours 0.0.0.0:6050->50005/tcp dapr_placement -5779a0268159 openzipkin/zipkin "/busybox/sh run.sh" 2 days ago Up 32 hours 9410/tcp, 0.0.0.0:9411->9411/tcp dapr_zipkin -317fef6a8297 redis "docker-entrypoint.s…" 2 days ago Up 32 hours 0.0.0.0:6379->6379/tcp dapr_redis -``` - -If Zipkin is not working, [install the newest version of Dapr Cli and initialize it](https://docs.dapr.io/getting-started/install-dapr-cli/). - -### Run the Demo service sample - -The Demo service application exposes three methods that can be remotely invoked. In this example, the service code has two parts: - -In the `invoke-receiver.py` file, you will find the Opentelemetry tracing and exporter initialization in addition to two methods: `say`, `sleep` and `forward`. The instrumentation for the service happens automatically via the `GrpcInstrumentorServer` class. -```python -grpc_server_instrumentor = GrpcInstrumentorServer() -grpc_server_instrumentor.instrument() -``` - - -The `saytrace` method prints the incoming payload and metadata in console. We also return the current trace ID so we can verify whether the trace ID propagated correctly. -See the code snippet below: - -```python -@app.method(name='saytrace') -def saytrace(request: InvokeMethodRequest) -> InvokeMethodResponse: - with tracer.start_as_current_span(name='say') as span: - data = request.text() - span.add_event(name='log', attributes={'Request length': len(data)}) - print(request.metadata, flush=True) - print(request.text(), flush=True) - - resp = { - 'receivedtraceid': span.get_span_context().trace_id, - 'method': 'SAY' - } - - return InvokeMethodResponse(json.dumps(resp), 'application/json; charset=UTF-8') -``` - -The `sleep` methods simply waits for two seconds to simulate a slow operation. -```python -@app.method(name='sleep') -def sleep(request: InvokeMethodRequest) -> InvokeMethodResponse: - with tracer.start_as_current_span(name='sleep'): - time.sleep(2) - print(request.metadata, flush=True) - print(request.text(), flush=True) - - return InvokeMethodResponse(b'SLEEP', 'text/plain; charset=UTF-8') -``` - -The `forward` method makes a request to the `saytrace` method while attaching the current trace context. It simply returns the response of the `saytrace` method. -This allows us to verify whether the `traceid` is still correct after this nested callchain. - -```python -@app.method(name='forward') -def forward(request: InvokeMethodRequest) -> InvokeMethodResponse: - with tracer.start_as_current_span(name='forward'): - print(request.metadata, flush=True) - print(request.text(), flush=True) - - # this helper method can be used to inject the tracing headers into the request - def trace_injector() -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier=headers) - return headers - - # service invocation uses HTTP, so we need to inject the tracing headers into the request - with DaprClient(headers_callback=trace_injector) as d: - resp = d.invoke_method( - 'invoke-receiver', - 'saytrace', - data=request.text().encode("utf-8"), - ) - - return InvokeMethodResponse(json.dumps(resp.json()), 'application/json; charset=UTF-8') -``` - -Use the following command to execute the service: - - - -```sh -dapr run --app-id invoke-receiver --app-protocol grpc --app-port 3001 python3 invoke-receiver.py -``` - - - -Once running, the service is now ready to be invoked by Dapr. - - -### Run the InvokeClient sample - -This sample code uses the Dapr SDK for invoking three remote methods (`say`, `sleep` and `forward`). Again, it is instrumented with OpenTelemetry with the Zipkin exporter. See the code snippet below: - -```python -import json -import typing - -from opentelemetry import trace -from opentelemetry.exporter.zipkin.json import ZipkinExporter -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry.sdk.trace.sampling import ALWAYS_ON -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator - -from dapr.clients import DaprClient - -# Create a tracer provider -tracer_provider = TracerProvider(sampler=ALWAYS_ON) - -# Create a span processor -span_processor = BatchSpanProcessor(ZipkinExporter(endpoint="http://localhost:9411/api/v2/spans")) - -# Add the span processor to the tracer provider -tracer_provider.add_span_processor(span_processor) - -# Set the tracer provider -trace.set_tracer_provider(tracer_provider) - -# Get the tracer -tracer = trace.get_tracer(__name__) - - -# this helper method can be used to inject the tracing headers into the request -def trace_injector() -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier=headers) - return headers - - -with tracer.start_as_current_span(name='main') as span: - with DaprClient( - # service invocation uses HTTP, so we need to inject the tracing headers into the request - headers_callback=lambda: trace_injector() - ) as d: - num_messages = 2 - - for i in range(num_messages): - # Create a typed message with content type and body - resp = d.invoke_method( - 'invoke-receiver', - 'saytrace', - data=json.dumps({'id': i, 'message': 'hello world'}), - ) - # Print the response - print(resp.content_type, flush=True) - print(resp.json()['method'], flush=True) - traceid = resp.json()['receivedtraceid'] - - resp = d.invoke_method('invoke-receiver', 'sleep', data='') - # Print the response - print(resp.content_type, flush=True) - print(resp.text(), flush=True) - - forwarded_resp = d.invoke_method('invoke-receiver', 'forward', data='') - match_string = 'matches' if ( - forwarded_resp.json()["receivedtraceid"] == traceid) else 'does not match' - print(f"Trace ID {match_string} after forwarding", flush=True) -``` - -The class knows the `app-id` for the remote application. It uses `invoke_method` to invoke API calls on the service endpoint. Instrumentation happens automatically in `Dapr` client via the `tracer` argument. - -Execute the following command in order to run the caller example, it will call each method twice: - - - -```bash -dapr run --app-id invoke-caller --app-protocol grpc python3 invoke-caller.py -``` - - - - - - - -Once running, the output should display the messages sent from invoker in the service output as follows: - -![exposeroutput](./img/service.png) - -Methods have been remotely invoked and display the remote messages. - -Now, open Zipkin on [http://localhost:9411/zipkin](http://localhost:9411/zipkin). You should see a screen like the one below: - -![zipking-landing](./img/zipkin-landing.png) - -Click on the search icon to see the latest query results. You should see a tracing diagram similar to the one below: - -![zipking-landing](./img/zipkin-result.png) - -Once you click on the tracing event, you will see the details of the call stack starting in the client and then showing the service API calls right below. - -![zipking-details](./img/zipkin-details.png) - -### Zipkin API - -Zipkin also has an API available. See [Zipkin API](https://zipkin.io/zipkin-api/) for more details. - -To see traces collected through the API: - - - -```bash -curl -s "http://localhost:9411/api/v2/traces?serviceName=invoke-receiver&spanName=calllocal%2Finvoke-receiver%2Fsaytrace&limit=10" -H "accept: application/json" | jq ".[][] | .name, .duration" -``` - - - -The `jq` command line utility is used in the above to give you a nice human readable printout of method calls and their duration: - -``` -"calllocal/invoke-receiver/saytrace" -7511 -"calllocal/invoke-receiver/sleep" -2006537 -"calllocal/invoke-receiver/sleep" -2006268 -"calllocal/invoke-receiver/forward" -10965 -"calllocal/invoke-receiver/forward" -10490 -"calllocal/invoke-receiver/saytrace" -1948 -"calllocal/invoke-receiver/saytrace" -1545 -"main" -4053102 -``` - -## Cleanup - -Shutdown running dapr apps with Ctl-C or simply run the following: - - - -```bash -dapr stop --app-id invoke-receiver -``` - - - - +# Example - Distributed tracing + +In this sample, we'll run two Python applications: a service application, which exposes two methods, and a client application which will invoke the methods from the service using Dapr. The code is instrumented with [OpenTelemetry SDK for Python](https://opentelemetry.io/docs/languages/python/). +This sample includes: + +- invoke-receiver: Exposes the methods to be remotely accessed +- invoke-caller: Invokes the exposed methods + +Also consider [getting started with observability in Dapr](https://github.com/dapr/quickstarts/tree/master/tutorials/observability). + +## Example overview + +This sample uses the Client provided in Dapr's Python SDK invoking a remote method and Zipkin to collect and display tracing data. + +## Pre-requisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +### Install dependencies + +Clone this repository: + +```sh +git clone https://github.com/dapr/python-sdk.git +cd python-sdk +``` + +Then get into the examples directory: + +```sh +cd examples/w3c-tracing +``` + +Install dependencies: + + + +```sh +pip3 install -r requirements.txt +``` + + + +### Verify Zipkin is running + +Run `docker ps` to see if the container `dapr_zipkin` is running locally: + +```bash +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +24d043379da2 daprio/dapr "./placement" 2 days ago Up 32 hours 0.0.0.0:6050->50005/tcp dapr_placement +5779a0268159 openzipkin/zipkin "/busybox/sh run.sh" 2 days ago Up 32 hours 9410/tcp, 0.0.0.0:9411->9411/tcp dapr_zipkin +317fef6a8297 redis "docker-entrypoint.s…" 2 days ago Up 32 hours 0.0.0.0:6379->6379/tcp dapr_redis +``` + +If Zipkin is not working, [install the newest version of Dapr Cli and initialize it](https://docs.dapr.io/getting-started/install-dapr-cli/). + +### Run the Demo service sample + +The Demo service application exposes three methods that can be remotely invoked. In this example, the service code has two parts: + +In the `invoke-receiver.py` file, you will find the Opentelemetry tracing and exporter initialization in addition to two methods: `say`, `sleep` and `forward`. The instrumentation for the service happens automatically via the `GrpcInstrumentorServer` class. +```python +grpc_server_instrumentor = GrpcInstrumentorServer() +grpc_server_instrumentor.instrument() +``` + + +The `saytrace` method prints the incoming payload and metadata in console. We also return the current trace ID so we can verify whether the trace ID propagated correctly. +See the code snippet below: + +```python +@app.method(name='saytrace') +def saytrace(request: InvokeMethodRequest) -> InvokeMethodResponse: + with tracer.start_as_current_span(name='say') as span: + data = request.text() + span.add_event(name='log', attributes={'Request length': len(data)}) + print(request.metadata, flush=True) + print(request.text(), flush=True) + + resp = { + 'receivedtraceid': span.get_span_context().trace_id, + 'method': 'SAY' + } + + return InvokeMethodResponse(json.dumps(resp), 'application/json; charset=UTF-8') +``` + +The `sleep` methods simply waits for two seconds to simulate a slow operation. +```python +@app.method(name='sleep') +def sleep(request: InvokeMethodRequest) -> InvokeMethodResponse: + with tracer.start_as_current_span(name='sleep'): + time.sleep(2) + print(request.metadata, flush=True) + print(request.text(), flush=True) + + return InvokeMethodResponse(b'SLEEP', 'text/plain; charset=UTF-8') +``` + +The `forward` method makes a request to the `saytrace` method while attaching the current trace context. It simply returns the response of the `saytrace` method. +This allows us to verify whether the `traceid` is still correct after this nested callchain. + +```python +@app.method(name='forward') +def forward(request: InvokeMethodRequest) -> InvokeMethodResponse: + with tracer.start_as_current_span(name='forward'): + print(request.metadata, flush=True) + print(request.text(), flush=True) + + # this helper method can be used to inject the tracing headers into the request + def trace_injector() -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier=headers) + return headers + + # service invocation uses HTTP, so we need to inject the tracing headers into the request + with DaprClient(headers_callback=trace_injector) as d: + resp = d.invoke_method( + 'invoke-receiver', + 'saytrace', + data=request.text().encode("utf-8"), + ) + + return InvokeMethodResponse(json.dumps(resp.json()), 'application/json; charset=UTF-8') +``` + +Use the following command to execute the service: + + + +```sh +dapr run --app-id invoke-receiver --app-protocol grpc --app-port 3001 python3 invoke-receiver.py +``` + + + +Once running, the service is now ready to be invoked by Dapr. + + +### Run the InvokeClient sample + +This sample code uses the Dapr SDK for invoking three remote methods (`say`, `sleep` and `forward`). Again, it is instrumented with OpenTelemetry with the Zipkin exporter. See the code snippet below: + +```python +import json +import typing + +from opentelemetry import trace +from opentelemetry.exporter.zipkin.json import ZipkinExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.trace.sampling import ALWAYS_ON +from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + +from dapr.clients import DaprClient + +# Create a tracer provider +tracer_provider = TracerProvider(sampler=ALWAYS_ON) + +# Create a span processor +span_processor = BatchSpanProcessor(ZipkinExporter(endpoint="http://localhost:9411/api/v2/spans")) + +# Add the span processor to the tracer provider +tracer_provider.add_span_processor(span_processor) + +# Set the tracer provider +trace.set_tracer_provider(tracer_provider) + +# Get the tracer +tracer = trace.get_tracer(__name__) + + +# this helper method can be used to inject the tracing headers into the request +def trace_injector() -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier=headers) + return headers + + +with tracer.start_as_current_span(name='main') as span: + with DaprClient( + # service invocation uses HTTP, so we need to inject the tracing headers into the request + headers_callback=lambda: trace_injector() + ) as d: + num_messages = 2 + + for i in range(num_messages): + # Create a typed message with content type and body + resp = d.invoke_method( + 'invoke-receiver', + 'saytrace', + data=json.dumps({'id': i, 'message': 'hello world'}), + ) + # Print the response + print(resp.content_type, flush=True) + print(resp.json()['method'], flush=True) + traceid = resp.json()['receivedtraceid'] + + resp = d.invoke_method('invoke-receiver', 'sleep', data='') + # Print the response + print(resp.content_type, flush=True) + print(resp.text(), flush=True) + + forwarded_resp = d.invoke_method('invoke-receiver', 'forward', data='') + match_string = 'matches' if ( + forwarded_resp.json()["receivedtraceid"] == traceid) else 'does not match' + print(f"Trace ID {match_string} after forwarding", flush=True) +``` + +The class knows the `app-id` for the remote application. It uses `invoke_method` to invoke API calls on the service endpoint. Instrumentation happens automatically in `Dapr` client via the `tracer` argument. + +Execute the following command in order to run the caller example, it will call each method twice: + + + +```bash +dapr run --app-id invoke-caller --app-protocol grpc python3 invoke-caller.py +``` + + + + + + + +Once running, the output should display the messages sent from invoker in the service output as follows: + +![exposeroutput](./img/service.png) + +Methods have been remotely invoked and display the remote messages. + +Now, open Zipkin on [http://localhost:9411/zipkin](http://localhost:9411/zipkin). You should see a screen like the one below: + +![zipking-landing](./img/zipkin-landing.png) + +Click on the search icon to see the latest query results. You should see a tracing diagram similar to the one below: + +![zipking-landing](./img/zipkin-result.png) + +Once you click on the tracing event, you will see the details of the call stack starting in the client and then showing the service API calls right below. + +![zipking-details](./img/zipkin-details.png) + +### Zipkin API + +Zipkin also has an API available. See [Zipkin API](https://zipkin.io/zipkin-api/) for more details. + +To see traces collected through the API: + + + +```bash +curl -s "http://localhost:9411/api/v2/traces?serviceName=invoke-receiver&spanName=calllocal%2Finvoke-receiver%2Fsaytrace&limit=10" -H "accept: application/json" | jq ".[][] | .name, .duration" +``` + + + +The `jq` command line utility is used in the above to give you a nice human readable printout of method calls and their duration: + +``` +"calllocal/invoke-receiver/saytrace" +7511 +"calllocal/invoke-receiver/sleep" +2006537 +"calllocal/invoke-receiver/sleep" +2006268 +"calllocal/invoke-receiver/forward" +10965 +"calllocal/invoke-receiver/forward" +10490 +"calllocal/invoke-receiver/saytrace" +1948 +"calllocal/invoke-receiver/saytrace" +1545 +"main" +4053102 +``` + +## Cleanup + +Shutdown running dapr apps with Ctl-C or simply run the following: + + + +```bash +dapr stop --app-id invoke-receiver +``` + + + + diff --git a/examples/w3c-tracing/invoke-caller.py b/examples/w3c-tracing/invoke-caller.py index 3ade61e4c..479093b26 100644 --- a/examples/w3c-tracing/invoke-caller.py +++ b/examples/w3c-tracing/invoke-caller.py @@ -1,66 +1,66 @@ -import json -import typing - -from opentelemetry import trace -from opentelemetry.exporter.zipkin.json import ZipkinExporter -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry.sdk.trace.sampling import ALWAYS_ON -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator - -from dapr.clients import DaprClient - -# Create a tracer provider -tracer_provider = TracerProvider(sampler=ALWAYS_ON) - -# Create a span processor -span_processor = BatchSpanProcessor(ZipkinExporter(endpoint='http://localhost:9411/api/v2/spans')) - -# Add the span processor to the tracer provider -tracer_provider.add_span_processor(span_processor) - -# Set the tracer provider -trace.set_tracer_provider(tracer_provider) - -# Get the tracer -tracer = trace.get_tracer(__name__) - - -# this helper method can be used to inject the tracing headers into the request -def trace_injector() -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier=headers) - return headers - - -with tracer.start_as_current_span(name='main') as span: - with DaprClient( - # service invocation uses HTTP, so we need to inject the tracing headers into the request - headers_callback=lambda: trace_injector() - ) as d: - num_messages = 2 - - for i in range(num_messages): - # Create a typed message with content type and body - resp = d.invoke_method( - 'invoke-receiver', - 'saytrace', - data=json.dumps({'id': i, 'message': 'hello world'}), - ) - # Print the response - print(resp.content_type, flush=True) - print(resp.json()['method'], flush=True) - traceid = resp.json()['receivedtraceid'] - - resp = d.invoke_method('invoke-receiver', 'sleep', data='') - # Print the response - print(resp.content_type, flush=True) - print(resp.text(), flush=True) - - forwarded_resp = d.invoke_method('invoke-receiver', 'forward', data='') - match_string = ( - 'matches' - if (forwarded_resp.json()['receivedtraceid'] == traceid) - else 'does not match' - ) - print(f'Trace ID {match_string} after forwarding', flush=True) +import json +import typing + +from opentelemetry import trace +from opentelemetry.exporter.zipkin.json import ZipkinExporter +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.trace.sampling import ALWAYS_ON +from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + +from dapr.clients import DaprClient + +# Create a tracer provider +tracer_provider = TracerProvider(sampler=ALWAYS_ON) + +# Create a span processor +span_processor = BatchSpanProcessor(ZipkinExporter(endpoint='http://localhost:9411/api/v2/spans')) + +# Add the span processor to the tracer provider +tracer_provider.add_span_processor(span_processor) + +# Set the tracer provider +trace.set_tracer_provider(tracer_provider) + +# Get the tracer +tracer = trace.get_tracer(__name__) + + +# this helper method can be used to inject the tracing headers into the request +def trace_injector() -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier=headers) + return headers + + +with tracer.start_as_current_span(name='main') as span: + with DaprClient( + # service invocation uses HTTP, so we need to inject the tracing headers into the request + headers_callback=lambda: trace_injector() + ) as d: + num_messages = 2 + + for i in range(num_messages): + # Create a typed message with content type and body + resp = d.invoke_method( + 'invoke-receiver', + 'saytrace', + data=json.dumps({'id': i, 'message': 'hello world'}), + ) + # Print the response + print(resp.content_type, flush=True) + print(resp.json()['method'], flush=True) + traceid = resp.json()['receivedtraceid'] + + resp = d.invoke_method('invoke-receiver', 'sleep', data='') + # Print the response + print(resp.content_type, flush=True) + print(resp.text(), flush=True) + + forwarded_resp = d.invoke_method('invoke-receiver', 'forward', data='') + match_string = ( + 'matches' + if (forwarded_resp.json()['receivedtraceid'] == traceid) + else 'does not match' + ) + print(f'Trace ID {match_string} after forwarding', flush=True) diff --git a/examples/w3c-tracing/invoke-receiver.py b/examples/w3c-tracing/invoke-receiver.py index 92300aebe..88e62552a 100644 --- a/examples/w3c-tracing/invoke-receiver.py +++ b/examples/w3c-tracing/invoke-receiver.py @@ -1,88 +1,88 @@ -import json -import time -import typing -from concurrent import futures - -from opentelemetry import trace -from opentelemetry.exporter.zipkin.json import ZipkinExporter -from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer, filters -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry.sdk.trace.sampling import ALWAYS_ON -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator - -from dapr.clients import DaprClient -from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse - -# Create a tracer provider -tracer_provider = TracerProvider(sampler=ALWAYS_ON) - -# Create a span processor -span_processor = BatchSpanProcessor(ZipkinExporter(endpoint='http://localhost:9411/api/v2/spans')) - -# Add the span processor to the tracer provider -tracer_provider.add_span_processor(span_processor) - -# Set the tracer provider -trace.set_tracer_provider(tracer_provider) - -# Get the tracer -tracer = trace.get_tracer(__name__) - -# automatically intercept incoming tracing headers and propagate them to the current context -grpc_server_instrumentor = GrpcInstrumentorServer() -grpc_server_instrumentor.instrument() - - -app = App(thread_pool=futures.ThreadPoolExecutor(max_workers=10)) - - -@app.method(name='saytrace') -def saytrace(request: InvokeMethodRequest) -> InvokeMethodResponse: - with tracer.start_as_current_span(name='say') as span: - data = request.text() - span.add_event(name='log', attributes={'Request length': len(data)}) - print(request.metadata, flush=True) - print(request.text(), flush=True) - - resp = {'receivedtraceid': span.get_span_context().trace_id, 'method': 'SAY'} - - return InvokeMethodResponse(json.dumps(resp), 'application/json; charset=UTF-8') - - -@app.method(name='sleep') -def sleep(request: InvokeMethodRequest) -> InvokeMethodResponse: - with tracer.start_as_current_span(name='sleep'): - time.sleep(2) - print(request.metadata, flush=True) - print(request.text(), flush=True) - - return InvokeMethodResponse(b'SLEEP', 'text/plain; charset=UTF-8') - - -# This method is used to forward the request to another service -# this is used to test the tracing propagation -@app.method(name='forward') -def forward(request: InvokeMethodRequest) -> InvokeMethodResponse: - with tracer.start_as_current_span(name='forward'): - print(request.metadata, flush=True) - print(request.text(), flush=True) - - # this helper method can be used to inject the tracing headers into the request - def trace_injector() -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier=headers) - return headers - - # service invocation uses HTTP, so we need to inject the tracing headers into the request - with DaprClient(headers_callback=trace_injector) as d: - resp = d.invoke_method( - 'invoke-receiver', - 'saytrace', - data=request.text().encode('utf-8'), - ) - - return InvokeMethodResponse(json.dumps(resp.json()), 'application/json; charset=UTF-8') - - -app.run(3001) +import json +import time +import typing +from concurrent import futures + +from opentelemetry import trace +from opentelemetry.exporter.zipkin.json import ZipkinExporter +from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer, filters +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor +from opentelemetry.sdk.trace.sampling import ALWAYS_ON +from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + +from dapr.clients import DaprClient +from dapr.ext.grpc import App, InvokeMethodRequest, InvokeMethodResponse + +# Create a tracer provider +tracer_provider = TracerProvider(sampler=ALWAYS_ON) + +# Create a span processor +span_processor = BatchSpanProcessor(ZipkinExporter(endpoint='http://localhost:9411/api/v2/spans')) + +# Add the span processor to the tracer provider +tracer_provider.add_span_processor(span_processor) + +# Set the tracer provider +trace.set_tracer_provider(tracer_provider) + +# Get the tracer +tracer = trace.get_tracer(__name__) + +# automatically intercept incoming tracing headers and propagate them to the current context +grpc_server_instrumentor = GrpcInstrumentorServer() +grpc_server_instrumentor.instrument() + + +app = App(thread_pool=futures.ThreadPoolExecutor(max_workers=10)) + + +@app.method(name='saytrace') +def saytrace(request: InvokeMethodRequest) -> InvokeMethodResponse: + with tracer.start_as_current_span(name='say') as span: + data = request.text() + span.add_event(name='log', attributes={'Request length': len(data)}) + print(request.metadata, flush=True) + print(request.text(), flush=True) + + resp = {'receivedtraceid': span.get_span_context().trace_id, 'method': 'SAY'} + + return InvokeMethodResponse(json.dumps(resp), 'application/json; charset=UTF-8') + + +@app.method(name='sleep') +def sleep(request: InvokeMethodRequest) -> InvokeMethodResponse: + with tracer.start_as_current_span(name='sleep'): + time.sleep(2) + print(request.metadata, flush=True) + print(request.text(), flush=True) + + return InvokeMethodResponse(b'SLEEP', 'text/plain; charset=UTF-8') + + +# This method is used to forward the request to another service +# this is used to test the tracing propagation +@app.method(name='forward') +def forward(request: InvokeMethodRequest) -> InvokeMethodResponse: + with tracer.start_as_current_span(name='forward'): + print(request.metadata, flush=True) + print(request.text(), flush=True) + + # this helper method can be used to inject the tracing headers into the request + def trace_injector() -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier=headers) + return headers + + # service invocation uses HTTP, so we need to inject the tracing headers into the request + with DaprClient(headers_callback=trace_injector) as d: + resp = d.invoke_method( + 'invoke-receiver', + 'saytrace', + data=request.text().encode('utf-8'), + ) + + return InvokeMethodResponse(json.dumps(resp.json()), 'application/json; charset=UTF-8') + + +app.run(3001) diff --git a/examples/w3c-tracing/requirements.txt b/examples/w3c-tracing/requirements.txt index 68e1bff34..70f4bb57d 100644 --- a/examples/w3c-tracing/requirements.txt +++ b/examples/w3c-tracing/requirements.txt @@ -1,5 +1,5 @@ -dapr-ext-grpc-dev >= 1.13.0rc1.dev -dapr-dev >= 1.13.0rc1.dev -opentelemetry-sdk -opentelemetry-instrumentation-grpc -opentelemetry-exporter-zipkin +dapr-ext-grpc-dev >= 1.13.0rc1.dev +dapr-dev >= 1.13.0rc1.dev +opentelemetry-sdk +opentelemetry-instrumentation-grpc +opentelemetry-exporter-zipkin diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 63208c8e3..d2021a291 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -1,206 +1,206 @@ -# Workflow Examples - -This directory contains examples of using the [Dapr Workflow](https://docs.dapr.io/developing-applications/building-blocks/workflow/) extension. You can find additional information about these examples in the [Dapr Workflow Application Patterns docs](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-patterns#tabs-0-python). - -## Prerequisites - -- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) -- [Install Python 3.8+](https://www.python.org/downloads/) - -### Install requirements - -You can install dapr SDK package using pip command: - -```sh -pip3 install -r requirements.txt -``` - -## Running the samples - -Each of the examples in this directory can be run directly from the command line. - -### Task Chaining - -This example demonstrates how to chain "activity" tasks together in a workflow. You can run this sample using the following command: - - -```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -- python3 task_chaining.py -``` - - -The output of this example should look like this: - -``` -== APP == Workflow started. Instance ID: b716208586c24829806b44b62816b598 -== APP == Step 1: Received input: 42. -== APP == Step 2: Received input: 43. -== APP == Step 3: Received input: 86. -== APP == Workflow completed! Status: WorkflowStatus.COMPLETED -``` - -### Fan-out/Fan-in - -This example demonstrates how to fan-out a workflow into multiple parallel tasks, and then fan-in the results of those tasks. You can run this sample using the following command: - - - -```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -- python3 fan_out_fan_in.py -``` - - -The output of this sample should look like this: - -``` -== APP == Workflow started. Instance ID: 2e656befbb304e758776e30642b75944 -== APP == Processing work item: 1. -== APP == Processing work item: 2. -== APP == Processing work item: 3. -== APP == Processing work item: 4. -== APP == Processing work item: 5. -== APP == Processing work item: 6. -== APP == Processing work item: 7. -== APP == Processing work item: 8. -== APP == Processing work item: 9. -== APP == Processing work item: 10. -== APP == Work item 1 processed. Result: 2. -== APP == Work item 2 processed. Result: 4. -== APP == Work item 3 processed. Result: 6. -== APP == Work item 4 processed. Result: 8. -== APP == Work item 5 processed. Result: 10. -== APP == Work item 6 processed. Result: 12. -== APP == Work item 7 processed. Result: 14. -== APP == Work item 8 processed. Result: 16. -== APP == Work item 9 processed. Result: 18. -== APP == Work item 10 processed. Result: 20. -== APP == Final result: 110. -``` - -Note that the ordering of the work-items is non-deterministic since they are all running in parallel. - -### Human Interaction - -This example demonstrates how to use a workflow to interact with a human user. This example requires input from the user, so you'll need to have a separate command for the Dapr CLI and the Python app. - -The Dapr CLI can be started using the following command: - -```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -``` - -In a separate terminal window, run the following command to start the Python workflow app: - -```sh - python3 human_approval.py - ``` - -When you run the example, you will see output as well as a prompt like this: - -``` -*** Requesting approval from user for order: namespace(cost=2000, product='MyProduct', quantity=1) -Press [ENTER] to approve the order... -``` - -Press the `ENTER` key to continue the workflow. If `ENTER` is pressed before the hardcoded timeout expires, then the following output will be displayed: - -``` -*** Placing order: namespace(cost=2000, product='MyProduct', quantity=1) -Workflow completed! Result: "Approved by 'Me'" -``` - -However, if the timeout expires before `ENTER` is pressed, then the following output will be displayed: - -``` -*** Workflow timed out! -``` - -### Monitor - -This example demonstrates how to eternally running workflow that polls an endpoint to detect service health events. This example requires input from the user, so you'll need to have a separate command for the Dapr CLI and the Python app. - -The Dapr CLI can be started using the following command: - -```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -``` - -In a separate terminal window, run the following command to start the Python workflow app: - -```sh -python3 monitor.py -``` - -The workflow runs forever, or until the app is stopped. While it is running, it will periodically report information about whether a "job" is healthy or unhealthy. After several minutes, the output of this workflow will look something like this (note that the healthy and unhealthy message ordering is completely random): - -``` -Press Enter to stop... -Job 'job1' is healthy. -Job 'job1' is healthy. -Job 'job1' is unhealthy. -*** Alert: Job 'job1' is unhealthy! -Job 'job1' is healthy. -Job 'job1' is healthy. -Job 'job1' is healthy. -Job 'job1' is unhealthy. -*** Alert: Job 'job1' is unhealthy! -Job 'job1' is unhealthy. -``` - -This workflow runs forever or until you press `ENTER` to stop it. Starting the app again after stopping it will cause the same workflow instance to resume where it left off. - -### Child Workflow - -This example demonstrates how to call a child workflow. The Dapr CLI can be started using the following command: - -```sh -dapr run --app-id wfexample --dapr-grpc-port 50001 -``` - -In a separate terminal window, run the following command to start the Python workflow app: - -```sh -python3 child_workflow.py -``` - -When you run the example, you will see output like this: -``` -... -*** Calling child workflow 29a7592a1e874b07aad2bb58de309a51-child -*** Child workflow 6feadc5370184b4998e50875b20084f6 called -... +# Workflow Examples + +This directory contains examples of using the [Dapr Workflow](https://docs.dapr.io/developing-applications/building-blocks/workflow/) extension. You can find additional information about these examples in the [Dapr Workflow Application Patterns docs](https://docs.dapr.io/developing-applications/building-blocks/workflow/workflow-patterns#tabs-0-python). + +## Prerequisites + +- [Dapr CLI and initialized environment](https://docs.dapr.io/getting-started) +- [Install Python 3.8+](https://www.python.org/downloads/) + +### Install requirements + +You can install dapr SDK package using pip command: + +```sh +pip3 install -r requirements.txt +``` + +## Running the samples + +Each of the examples in this directory can be run directly from the command line. + +### Task Chaining + +This example demonstrates how to chain "activity" tasks together in a workflow. You can run this sample using the following command: + + +```sh +dapr run --app-id wfexample --dapr-grpc-port 50001 -- python3 task_chaining.py +``` + + +The output of this example should look like this: + +``` +== APP == Workflow started. Instance ID: b716208586c24829806b44b62816b598 +== APP == Step 1: Received input: 42. +== APP == Step 2: Received input: 43. +== APP == Step 3: Received input: 86. +== APP == Workflow completed! Status: WorkflowStatus.COMPLETED +``` + +### Fan-out/Fan-in + +This example demonstrates how to fan-out a workflow into multiple parallel tasks, and then fan-in the results of those tasks. You can run this sample using the following command: + + + +```sh +dapr run --app-id wfexample --dapr-grpc-port 50001 -- python3 fan_out_fan_in.py +``` + + +The output of this sample should look like this: + +``` +== APP == Workflow started. Instance ID: 2e656befbb304e758776e30642b75944 +== APP == Processing work item: 1. +== APP == Processing work item: 2. +== APP == Processing work item: 3. +== APP == Processing work item: 4. +== APP == Processing work item: 5. +== APP == Processing work item: 6. +== APP == Processing work item: 7. +== APP == Processing work item: 8. +== APP == Processing work item: 9. +== APP == Processing work item: 10. +== APP == Work item 1 processed. Result: 2. +== APP == Work item 2 processed. Result: 4. +== APP == Work item 3 processed. Result: 6. +== APP == Work item 4 processed. Result: 8. +== APP == Work item 5 processed. Result: 10. +== APP == Work item 6 processed. Result: 12. +== APP == Work item 7 processed. Result: 14. +== APP == Work item 8 processed. Result: 16. +== APP == Work item 9 processed. Result: 18. +== APP == Work item 10 processed. Result: 20. +== APP == Final result: 110. +``` + +Note that the ordering of the work-items is non-deterministic since they are all running in parallel. + +### Human Interaction + +This example demonstrates how to use a workflow to interact with a human user. This example requires input from the user, so you'll need to have a separate command for the Dapr CLI and the Python app. + +The Dapr CLI can be started using the following command: + +```sh +dapr run --app-id wfexample --dapr-grpc-port 50001 +``` + +In a separate terminal window, run the following command to start the Python workflow app: + +```sh + python3 human_approval.py + ``` + +When you run the example, you will see output as well as a prompt like this: + +``` +*** Requesting approval from user for order: namespace(cost=2000, product='MyProduct', quantity=1) +Press [ENTER] to approve the order... +``` + +Press the `ENTER` key to continue the workflow. If `ENTER` is pressed before the hardcoded timeout expires, then the following output will be displayed: + +``` +*** Placing order: namespace(cost=2000, product='MyProduct', quantity=1) +Workflow completed! Result: "Approved by 'Me'" +``` + +However, if the timeout expires before `ENTER` is pressed, then the following output will be displayed: + +``` +*** Workflow timed out! +``` + +### Monitor + +This example demonstrates how to eternally running workflow that polls an endpoint to detect service health events. This example requires input from the user, so you'll need to have a separate command for the Dapr CLI and the Python app. + +The Dapr CLI can be started using the following command: + +```sh +dapr run --app-id wfexample --dapr-grpc-port 50001 +``` + +In a separate terminal window, run the following command to start the Python workflow app: + +```sh +python3 monitor.py +``` + +The workflow runs forever, or until the app is stopped. While it is running, it will periodically report information about whether a "job" is healthy or unhealthy. After several minutes, the output of this workflow will look something like this (note that the healthy and unhealthy message ordering is completely random): + +``` +Press Enter to stop... +Job 'job1' is healthy. +Job 'job1' is healthy. +Job 'job1' is unhealthy. +*** Alert: Job 'job1' is unhealthy! +Job 'job1' is healthy. +Job 'job1' is healthy. +Job 'job1' is healthy. +Job 'job1' is unhealthy. +*** Alert: Job 'job1' is unhealthy! +Job 'job1' is unhealthy. +``` + +This workflow runs forever or until you press `ENTER` to stop it. Starting the app again after stopping it will cause the same workflow instance to resume where it left off. + +### Child Workflow + +This example demonstrates how to call a child workflow. The Dapr CLI can be started using the following command: + +```sh +dapr run --app-id wfexample --dapr-grpc-port 50001 +``` + +In a separate terminal window, run the following command to start the Python workflow app: + +```sh +python3 child_workflow.py +``` + +When you run the example, you will see output like this: +``` +... +*** Calling child workflow 29a7592a1e874b07aad2bb58de309a51-child +*** Child workflow 6feadc5370184b4998e50875b20084f6 called +... ``` \ No newline at end of file diff --git a/examples/workflow/child_workflow.py b/examples/workflow/child_workflow.py index dccaa631b..4c7212b57 100644 --- a/examples/workflow/child_workflow.py +++ b/examples/workflow/child_workflow.py @@ -1,50 +1,50 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import dapr.ext.workflow as wf -import time - -wfr = wf.WorkflowRuntime() - - -@wfr.workflow -def main_workflow(ctx: wf.DaprWorkflowContext): - try: - instance_id = ctx.instance_id - child_instance_id = instance_id + '-child' - print(f'*** Calling child workflow {child_instance_id}', flush=True) - yield ctx.call_child_workflow( - workflow=child_workflow, input=None, instance_id=child_instance_id - ) - except Exception as e: - print(f'*** Exception: {e}') - - return - - -@wfr.workflow -def child_workflow(ctx: wf.DaprWorkflowContext): - instance_id = ctx.instance_id - print(f'*** Child workflow {instance_id} called', flush=True) - - -if __name__ == '__main__': - wfr.start() - time.sleep(10) # wait for workflow runtime to start - - wf_client = wf.DaprWorkflowClient() - instance_id = wf_client.schedule_new_workflow(workflow=main_workflow) - - # Wait for the workflow to complete - time.sleep(5) - - wfr.shutdown() +# -*- coding: utf-8 -*- +# Copyright 2023 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dapr.ext.workflow as wf +import time + +wfr = wf.WorkflowRuntime() + + +@wfr.workflow +def main_workflow(ctx: wf.DaprWorkflowContext): + try: + instance_id = ctx.instance_id + child_instance_id = instance_id + '-child' + print(f'*** Calling child workflow {child_instance_id}', flush=True) + yield ctx.call_child_workflow( + workflow=child_workflow, input=None, instance_id=child_instance_id + ) + except Exception as e: + print(f'*** Exception: {e}') + + return + + +@wfr.workflow +def child_workflow(ctx: wf.DaprWorkflowContext): + instance_id = ctx.instance_id + print(f'*** Child workflow {instance_id} called', flush=True) + + +if __name__ == '__main__': + wfr.start() + time.sleep(10) # wait for workflow runtime to start + + wf_client = wf.DaprWorkflowClient() + instance_id = wf_client.schedule_new_workflow(workflow=main_workflow) + + # Wait for the workflow to complete + time.sleep(5) + + wfr.shutdown() diff --git a/examples/workflow/fan_out_fan_in.py b/examples/workflow/fan_out_fan_in.py index e5799862f..3254cba35 100644 --- a/examples/workflow/fan_out_fan_in.py +++ b/examples/workflow/fan_out_fan_in.py @@ -1,64 +1,64 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import time -from typing import List -import dapr.ext.workflow as wf - -wfr = wf.WorkflowRuntime() - - -@wfr.workflow(name='batch_processing') -def batch_processing_workflow(ctx: wf.DaprWorkflowContext, wf_input: int): - # get a batch of N work items to process in parallel - work_batch = yield ctx.call_activity(get_work_batch, input=wf_input) - - # schedule N parallel tasks to process the work items and wait for all to complete - parallel_tasks = [ - ctx.call_activity(process_work_item, input=work_item) for work_item in work_batch - ] - outputs = yield wf.when_all(parallel_tasks) - - # aggregate the results and send them to another activity - total = sum(outputs) - yield ctx.call_activity(process_results, input=total) - - -@wfr.activity(name='get_batch') -def get_work_batch(ctx, batch_size: int) -> List[int]: - return [i + 1 for i in range(batch_size)] - - -@wfr.activity -def process_work_item(ctx, work_item: int) -> int: - print(f'Processing work item: {work_item}.') - time.sleep(5) - result = work_item * 2 - print(f'Work item {work_item} processed. Result: {result}.') - return result - - -@wfr.activity(name='final_process') -def process_results(ctx, final_result: int): - print(f'Final result: {final_result}.') - - -if __name__ == '__main__': - wfr.start() - time.sleep(10) # wait for workflow runtime to start - - wf_client = wf.DaprWorkflowClient() - instance_id = wf_client.schedule_new_workflow(workflow=batch_processing_workflow, input=10) - print(f'Workflow started. Instance ID: {instance_id}') - state = wf_client.wait_for_workflow_completion(instance_id) - - wfr.shutdown() +# -*- coding: utf-8 -*- +# Copyright 2023 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +from typing import List +import dapr.ext.workflow as wf + +wfr = wf.WorkflowRuntime() + + +@wfr.workflow(name='batch_processing') +def batch_processing_workflow(ctx: wf.DaprWorkflowContext, wf_input: int): + # get a batch of N work items to process in parallel + work_batch = yield ctx.call_activity(get_work_batch, input=wf_input) + + # schedule N parallel tasks to process the work items and wait for all to complete + parallel_tasks = [ + ctx.call_activity(process_work_item, input=work_item) for work_item in work_batch + ] + outputs = yield wf.when_all(parallel_tasks) + + # aggregate the results and send them to another activity + total = sum(outputs) + yield ctx.call_activity(process_results, input=total) + + +@wfr.activity(name='get_batch') +def get_work_batch(ctx, batch_size: int) -> List[int]: + return [i + 1 for i in range(batch_size)] + + +@wfr.activity +def process_work_item(ctx, work_item: int) -> int: + print(f'Processing work item: {work_item}.') + time.sleep(5) + result = work_item * 2 + print(f'Work item {work_item} processed. Result: {result}.') + return result + + +@wfr.activity(name='final_process') +def process_results(ctx, final_result: int): + print(f'Final result: {final_result}.') + + +if __name__ == '__main__': + wfr.start() + time.sleep(10) # wait for workflow runtime to start + + wf_client = wf.DaprWorkflowClient() + instance_id = wf_client.schedule_new_workflow(workflow=batch_processing_workflow, input=10) + print(f'Workflow started. Instance ID: {instance_id}') + state = wf_client.wait_for_workflow_completion(instance_id) + + wfr.shutdown() diff --git a/examples/workflow/human_approval.py b/examples/workflow/human_approval.py index 6a8a725d7..f61d9383b 100644 --- a/examples/workflow/human_approval.py +++ b/examples/workflow/human_approval.py @@ -1,122 +1,122 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import threading -from dataclasses import asdict, dataclass -from datetime import timedelta -import time - -from dapr.clients import DaprClient -import dapr.ext.workflow as wf - -wfr = wf.WorkflowRuntime() - - -@dataclass -class Order: - cost: float - product: str - quantity: int - - def __str__(self): - return f'{self.product} ({self.quantity})' - - -@dataclass -class Approval: - approver: str - - @staticmethod - def from_dict(dict): - return Approval(**dict) - - -@wfr.workflow(name='purchase_order_wf') -def purchase_order_workflow(ctx: wf.DaprWorkflowContext, order: Order): - # Orders under $1000 are auto-approved - if order.cost < 1000: - return 'Auto-approved' - - # Orders of $1000 or more require manager approval - yield ctx.call_activity(send_approval_request, input=order) - - # Approvals must be received within 24 hours or they will be canceled. - approval_event = ctx.wait_for_external_event('approval_received') - timeout_event = ctx.create_timer(timedelta(hours=24)) - winner = yield wf.when_any([approval_event, timeout_event]) - if winner == timeout_event: - return 'Cancelled' - - # The order was approved - yield ctx.call_activity(place_order, input=order) - approval_details = Approval.from_dict(approval_event.get_result()) - return f"Approved by '{approval_details.approver}'" - - -@wfr.activity(name='send_approval') -def send_approval_request(_, order: Order) -> None: - print(f'*** Requesting approval from user for order: {order}') - - -@wfr.activity -def place_order(_, order: Order) -> None: - print(f'*** Placing order: {order}') - - -if __name__ == '__main__': - import argparse - - parser = argparse.ArgumentParser(description='Order purchasing workflow demo.') - parser.add_argument('--cost', type=int, default=2000, help='Cost of the order') - parser.add_argument('--approver', type=str, default='Me', help='Approver name') - parser.add_argument('--timeout', type=int, default=60, help='Timeout in seconds') - args = parser.parse_args() - - # start the workflow runtime - wfr.start() - - # Start a purchase order workflow using the user input - order = Order(args.cost, 'MyProduct', 1) - - wf_client = wf.DaprWorkflowClient() - instance_id = wf_client.schedule_new_workflow(workflow=purchase_order_workflow, input=order) - - def prompt_for_approval(): - # Give the workflow time to start up and notify the user - time.sleep(2) - input('Press [ENTER] to approve the order...\n') - with DaprClient() as d: - d.raise_workflow_event( - instance_id=instance_id, - workflow_component='dapr', - event_name='approval_received', - event_data=asdict(Approval(args.approver)), - ) - - # Prompt the user for approval on a background thread - threading.Thread(target=prompt_for_approval, daemon=True).start() - - # Wait for the orchestration to complete - try: - state = wf_client.wait_for_workflow_completion( - instance_id, timeout_in_seconds=args.timeout + 2 - ) - if not state: - print('Workflow not found!') # not expected - elif state.runtime_status.name == 'COMPLETED': - print(f'Workflow completed! Result: {state.serialized_output}') - else: - print(f'Workflow failed! Status: {state.runtime_status.name}') # not expected - except TimeoutError: - print('*** Workflow timed out!') - - wfr.shutdown() +# -*- coding: utf-8 -*- +# Copyright 2023 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import threading +from dataclasses import asdict, dataclass +from datetime import timedelta +import time + +from dapr.clients import DaprClient +import dapr.ext.workflow as wf + +wfr = wf.WorkflowRuntime() + + +@dataclass +class Order: + cost: float + product: str + quantity: int + + def __str__(self): + return f'{self.product} ({self.quantity})' + + +@dataclass +class Approval: + approver: str + + @staticmethod + def from_dict(dict): + return Approval(**dict) + + +@wfr.workflow(name='purchase_order_wf') +def purchase_order_workflow(ctx: wf.DaprWorkflowContext, order: Order): + # Orders under $1000 are auto-approved + if order.cost < 1000: + return 'Auto-approved' + + # Orders of $1000 or more require manager approval + yield ctx.call_activity(send_approval_request, input=order) + + # Approvals must be received within 24 hours or they will be canceled. + approval_event = ctx.wait_for_external_event('approval_received') + timeout_event = ctx.create_timer(timedelta(hours=24)) + winner = yield wf.when_any([approval_event, timeout_event]) + if winner == timeout_event: + return 'Cancelled' + + # The order was approved + yield ctx.call_activity(place_order, input=order) + approval_details = Approval.from_dict(approval_event.get_result()) + return f"Approved by '{approval_details.approver}'" + + +@wfr.activity(name='send_approval') +def send_approval_request(_, order: Order) -> None: + print(f'*** Requesting approval from user for order: {order}') + + +@wfr.activity +def place_order(_, order: Order) -> None: + print(f'*** Placing order: {order}') + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser(description='Order purchasing workflow demo.') + parser.add_argument('--cost', type=int, default=2000, help='Cost of the order') + parser.add_argument('--approver', type=str, default='Me', help='Approver name') + parser.add_argument('--timeout', type=int, default=60, help='Timeout in seconds') + args = parser.parse_args() + + # start the workflow runtime + wfr.start() + + # Start a purchase order workflow using the user input + order = Order(args.cost, 'MyProduct', 1) + + wf_client = wf.DaprWorkflowClient() + instance_id = wf_client.schedule_new_workflow(workflow=purchase_order_workflow, input=order) + + def prompt_for_approval(): + # Give the workflow time to start up and notify the user + time.sleep(2) + input('Press [ENTER] to approve the order...\n') + with DaprClient() as d: + d.raise_workflow_event( + instance_id=instance_id, + workflow_component='dapr', + event_name='approval_received', + event_data=asdict(Approval(args.approver)), + ) + + # Prompt the user for approval on a background thread + threading.Thread(target=prompt_for_approval, daemon=True).start() + + # Wait for the orchestration to complete + try: + state = wf_client.wait_for_workflow_completion( + instance_id, timeout_in_seconds=args.timeout + 2 + ) + if not state: + print('Workflow not found!') # not expected + elif state.runtime_status.name == 'COMPLETED': + print(f'Workflow completed! Result: {state.serialized_output}') + else: + print(f'Workflow failed! Status: {state.runtime_status.name}') # not expected + except TimeoutError: + print('*** Workflow timed out!') + + wfr.shutdown() diff --git a/examples/workflow/monitor.py b/examples/workflow/monitor.py index a6da1c7db..f04184f4f 100644 --- a/examples/workflow/monitor.py +++ b/examples/workflow/monitor.py @@ -1,82 +1,82 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from dataclasses import dataclass -from datetime import timedelta -import random -from time import sleep -import dapr.ext.workflow as wf - -wfr = wf.WorkflowRuntime() - - -@dataclass -class JobStatus: - job_id: str - is_healthy: bool - - -@wfr.workflow(name='status_monitor') -def status_monitor_workflow(ctx: wf.DaprWorkflowContext, job: JobStatus): - # poll a status endpoint associated with this job - status = yield ctx.call_activity(check_status, input=job) - if not ctx.is_replaying: - print(f"Job '{job.job_id}' is {status}.") - - if status == 'healthy': - job.is_healthy = True - next_sleep_interval = 60 # check less frequently when healthy - else: - if job.is_healthy: - job.is_healthy = False - ctx.call_activity(send_alert, input=f"Job '{job.job_id}' is unhealthy!") - next_sleep_interval = 5 # check more frequently when unhealthy - - yield ctx.create_timer(fire_at=timedelta(seconds=next_sleep_interval)) - - # restart from the beginning with a new JobStatus input - ctx.continue_as_new(job) - - -@wfr.activity -def check_status(ctx, _) -> str: - return random.choice(['healthy', 'unhealthy']) - - -@wfr.activity -def send_alert(ctx, message: str): - print(f'*** Alert: {message}') - - -if __name__ == '__main__': - wfr.start() - sleep(10) # wait for workflow runtime to start - - wf_client = wf.DaprWorkflowClient() - job_id = 'job1' - status = None - try: - status = wf_client.get_workflow_state(job_id) - except Exception: - pass - if not status or status.runtime_status.name != 'RUNNING': - instance_id = wf_client.schedule_new_workflow( - workflow=status_monitor_workflow, - input=JobStatus(job_id=job_id, is_healthy=True), - instance_id=job_id, - ) - print(f'Workflow started. Instance ID: {instance_id}') - else: - print(f'Workflow already running. Instance ID: {job_id}') - - input('Press Enter to stop...\n') - wfr.shutdown() +# -*- coding: utf-8 -*- +# Copyright 2023 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass +from datetime import timedelta +import random +from time import sleep +import dapr.ext.workflow as wf + +wfr = wf.WorkflowRuntime() + + +@dataclass +class JobStatus: + job_id: str + is_healthy: bool + + +@wfr.workflow(name='status_monitor') +def status_monitor_workflow(ctx: wf.DaprWorkflowContext, job: JobStatus): + # poll a status endpoint associated with this job + status = yield ctx.call_activity(check_status, input=job) + if not ctx.is_replaying: + print(f"Job '{job.job_id}' is {status}.") + + if status == 'healthy': + job.is_healthy = True + next_sleep_interval = 60 # check less frequently when healthy + else: + if job.is_healthy: + job.is_healthy = False + ctx.call_activity(send_alert, input=f"Job '{job.job_id}' is unhealthy!") + next_sleep_interval = 5 # check more frequently when unhealthy + + yield ctx.create_timer(fire_at=timedelta(seconds=next_sleep_interval)) + + # restart from the beginning with a new JobStatus input + ctx.continue_as_new(job) + + +@wfr.activity +def check_status(ctx, _) -> str: + return random.choice(['healthy', 'unhealthy']) + + +@wfr.activity +def send_alert(ctx, message: str): + print(f'*** Alert: {message}') + + +if __name__ == '__main__': + wfr.start() + sleep(10) # wait for workflow runtime to start + + wf_client = wf.DaprWorkflowClient() + job_id = 'job1' + status = None + try: + status = wf_client.get_workflow_state(job_id) + except Exception: + pass + if not status or status.runtime_status.name != 'RUNNING': + instance_id = wf_client.schedule_new_workflow( + workflow=status_monitor_workflow, + input=JobStatus(job_id=job_id, is_healthy=True), + instance_id=job_id, + ) + print(f'Workflow started. Instance ID: {instance_id}') + else: + print(f'Workflow already running. Instance ID: {job_id}') + + input('Press Enter to stop...\n') + wfr.shutdown() diff --git a/examples/workflow/requirements.txt b/examples/workflow/requirements.txt index 6a748d0ec..abfd5ef62 100644 --- a/examples/workflow/requirements.txt +++ b/examples/workflow/requirements.txt @@ -1,2 +1,2 @@ -dapr-ext-workflow-dev>=0.4.1rc1.dev -dapr-dev>=1.13.0rc1.dev +dapr-ext-workflow-dev>=0.4.1rc1.dev +dapr-dev>=1.13.0rc1.dev diff --git a/examples/workflow/task_chaining.py b/examples/workflow/task_chaining.py index c24e340cf..cea92fe2c 100644 --- a/examples/workflow/task_chaining.py +++ b/examples/workflow/task_chaining.py @@ -1,70 +1,70 @@ -# -*- coding: utf-8 -*- -# Copyright 2023 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from time import sleep - -import dapr.ext.workflow as wf - - -wfr = wf.WorkflowRuntime() - - -@wfr.workflow(name='random_workflow') -def task_chain_workflow(ctx: wf.DaprWorkflowContext, wf_input: int): - try: - result1 = yield ctx.call_activity(step1, input=wf_input) - result2 = yield ctx.call_activity(step2, input=result1) - result3 = yield ctx.call_activity(step3, input=result2) - except Exception as e: - yield ctx.call_activity(error_handler, input=str(e)) - raise - return [result1, result2, result3] - - -@wfr.activity(name='step10') -def step1(ctx, activity_input): - print(f'Step 1: Received input: {activity_input}.') - # Do some work - return activity_input + 1 - - -@wfr.activity -def step2(ctx, activity_input): - print(f'Step 2: Received input: {activity_input}.') - # Do some work - return activity_input * 2 - - -@wfr.activity -def step3(ctx, activity_input): - print(f'Step 3: Received input: {activity_input}.') - # Do some work - return activity_input ^ 2 - - -@wfr.activity -def error_handler(ctx, error): - print(f'Executing error handler: {error}.') - # Do some compensating work - - -if __name__ == '__main__': - wfr.start() - sleep(10) # wait for workflow runtime to start - - wf_client = wf.DaprWorkflowClient() - instance_id = wf_client.schedule_new_workflow(workflow=task_chain_workflow, input=42) - print(f'Workflow started. Instance ID: {instance_id}') - state = wf_client.wait_for_workflow_completion(instance_id) - print(f'Workflow completed! Status: {state.runtime_status}') - - wfr.shutdown() +# -*- coding: utf-8 -*- +# Copyright 2023 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from time import sleep + +import dapr.ext.workflow as wf + + +wfr = wf.WorkflowRuntime() + + +@wfr.workflow(name='random_workflow') +def task_chain_workflow(ctx: wf.DaprWorkflowContext, wf_input: int): + try: + result1 = yield ctx.call_activity(step1, input=wf_input) + result2 = yield ctx.call_activity(step2, input=result1) + result3 = yield ctx.call_activity(step3, input=result2) + except Exception as e: + yield ctx.call_activity(error_handler, input=str(e)) + raise + return [result1, result2, result3] + + +@wfr.activity(name='step10') +def step1(ctx, activity_input): + print(f'Step 1: Received input: {activity_input}.') + # Do some work + return activity_input + 1 + + +@wfr.activity +def step2(ctx, activity_input): + print(f'Step 2: Received input: {activity_input}.') + # Do some work + return activity_input * 2 + + +@wfr.activity +def step3(ctx, activity_input): + print(f'Step 3: Received input: {activity_input}.') + # Do some work + return activity_input ^ 2 + + +@wfr.activity +def error_handler(ctx, error): + print(f'Executing error handler: {error}.') + # Do some compensating work + + +if __name__ == '__main__': + wfr.start() + sleep(10) # wait for workflow runtime to start + + wf_client = wf.DaprWorkflowClient() + instance_id = wf_client.schedule_new_workflow(workflow=task_chain_workflow, input=42) + print(f'Workflow started. Instance ID: {instance_id}') + state = wf_client.wait_for_workflow_completion(instance_id) + print(f'Workflow completed! Status: {state.runtime_status}') + + wfr.shutdown() diff --git a/ext/dapr-ext-fastapi/LICENSE b/ext/dapr-ext-fastapi/LICENSE index be033a7fd..da1ba6b93 100644 --- a/ext/dapr-ext-fastapi/LICENSE +++ b/ext/dapr-ext-fastapi/LICENSE @@ -1,203 +1,203 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 The Dapr Authors. - - and others that have contributed code to the public domain. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Dapr Authors. + + and others that have contributed code to the public domain. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/ext/dapr-ext-fastapi/README.rst b/ext/dapr-ext-fastapi/README.rst index 9d44f633e..f55d60e00 100644 --- a/ext/dapr-ext-fastapi/README.rst +++ b/ext/dapr-ext-fastapi/README.rst @@ -1,22 +1,22 @@ -dapr-ext-fastapi extension -========================== - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/dapr-ext-fastapi.svg - :target: https://pypi.org/project/dapr-ext-fastapi/ - -This FastAPI extension is used for FastAPI http server. - -Installation ------------- - -:: - - pip install dapr-ext-fastapi - -References ----------- - -* `Dapr `_ -* `Dapr Python-SDK `_ +dapr-ext-fastapi extension +========================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/dapr-ext-fastapi.svg + :target: https://pypi.org/project/dapr-ext-fastapi/ + +This FastAPI extension is used for FastAPI http server. + +Installation +------------ + +:: + + pip install dapr-ext-fastapi + +References +---------- + +* `Dapr `_ +* `Dapr Python-SDK `_ diff --git a/ext/dapr-ext-fastapi/dapr/ext/fastapi/__init__.py b/ext/dapr-ext-fastapi/dapr/ext/fastapi/__init__.py index 942603078..3f52983f7 100644 --- a/ext/dapr-ext-fastapi/dapr/ext/fastapi/__init__.py +++ b/ext/dapr-ext-fastapi/dapr/ext/fastapi/__init__.py @@ -1,20 +1,20 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from .actor import DaprActor -from .app import DaprApp - - -__all__ = ['DaprActor', 'DaprApp'] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from .actor import DaprActor +from .app import DaprApp + + +__all__ = ['DaprActor', 'DaprApp'] diff --git a/ext/dapr-ext-fastapi/dapr/ext/fastapi/actor.py b/ext/dapr-ext-fastapi/dapr/ext/fastapi/actor.py index 793704f74..2f0dbe439 100644 --- a/ext/dapr-ext-fastapi/dapr/ext/fastapi/actor.py +++ b/ext/dapr-ext-fastapi/dapr/ext/fastapi/actor.py @@ -1,154 +1,154 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Any, Optional, Type, List - -from fastapi import FastAPI, APIRouter, Request, Response, status # type: ignore -from fastapi.logger import logger -from fastapi.responses import JSONResponse - -from dapr.actor import Actor, ActorRuntime -from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN -from dapr.serializers import DefaultJSONSerializer - -DEFAULT_CONTENT_TYPE = 'application/json; utf-8' -DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id' - - -def _wrap_response( - status_code: int, - msg: Any, - error_code: Optional[str] = None, - content_type: Optional[str] = DEFAULT_CONTENT_TYPE, -): - resp = None - if isinstance(msg, str): - response_obj = { - 'message': msg, - } - if not (status_code >= 200 and status_code < 300) and error_code: - response_obj['errorCode'] = error_code - resp = JSONResponse(content=response_obj, status_code=status_code) - elif isinstance(msg, bytes): - resp = Response(content=msg, media_type=content_type) - else: - resp = JSONResponse(content=msg, status_code=status_code) - return resp - - -class DaprActor(object): - def __init__(self, app: FastAPI, router_tags: Optional[List[str]] = ['Actor']): - # router_tags should be added to all magic Dapr Actor methods implemented here - self._router_tags = router_tags - self._router = APIRouter() - self._dapr_serializer = DefaultJSONSerializer() - self.init_routes(self._router) - app.include_router(self._router) - - def init_routes(self, router: APIRouter): - @router.get('/healthz', tags=self._router_tags) - async def healthz(): - return {'status': 'ok'} - - @router.get('/dapr/config', tags=self._router_tags) - async def dapr_config(): - serialized = self._dapr_serializer.serialize(ActorRuntime.get_actor_config()) - return _wrap_response(status.HTTP_200_OK, serialized) - - @router.delete('/actors/{actor_type_name}/{actor_id}', tags=self._router_tags) - async def actor_deactivation(actor_type_name: str, actor_id: str): - try: - await ActorRuntime.deactivate(actor_type_name, actor_id) - except DaprInternalError as ex: - return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) - except Exception as ex: - return _wrap_response( - status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN - ) - - msg = f'deactivated actor: {actor_type_name}.{actor_id}' - logger.debug(msg) - return _wrap_response(status.HTTP_200_OK, msg) - - @router.put( - '/actors/{actor_type_name}/{actor_id}/method/{method_name}', tags=self._router_tags - ) - async def actor_method( - actor_type_name: str, actor_id: str, method_name: str, request: Request - ): - try: - # Read raw bytes from request stream - req_body = await request.body() - reentrancy_id = request.headers.get(DAPR_REENTRANCY_ID_HEADER) - result = await ActorRuntime.dispatch( - actor_type_name, actor_id, method_name, req_body, reentrancy_id - ) - except DaprInternalError as ex: - return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) - except Exception as ex: - return _wrap_response( - status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN - ) - - msg = f'called method. actor: {actor_type_name}.{actor_id}, method: {method_name}' - logger.debug(msg) - return _wrap_response(status.HTTP_200_OK, result) - - @router.put( - '/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}', tags=self._router_tags - ) - async def actor_timer( - actor_type_name: str, actor_id: str, timer_name: str, request: Request - ): - try: - # Read raw bytes from request stream - req_body = await request.body() - await ActorRuntime.fire_timer(actor_type_name, actor_id, timer_name, req_body) - except DaprInternalError as ex: - return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) - except Exception as ex: - return _wrap_response( - status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN - ) - - msg = f'called timer. actor: {actor_type_name}.{actor_id}, timer: {timer_name}' - logger.debug(msg) - return _wrap_response(status.HTTP_200_OK, msg) - - @router.put( - '/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}', - tags=self._router_tags, - ) - async def actor_reminder( - actor_type_name: str, actor_id: str, reminder_name: str, request: Request - ): - try: - # Read raw bytes from request stream - req_body = await request.body() - await ActorRuntime.fire_reminder(actor_type_name, actor_id, reminder_name, req_body) - except DaprInternalError as ex: - return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) - except Exception as ex: - return _wrap_response( - status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN - ) - - msg = f'called reminder. actor: {actor_type_name}.{actor_id}, reminder: {reminder_name}' - logger.debug(msg) - return _wrap_response(status.HTTP_200_OK, msg) - - async def register_actor(self, actor: Type[Actor], **kwargs) -> None: - await ActorRuntime.register_actor(actor, **kwargs) - logger.debug(f'registered actor: {actor.__class__.__name__}') +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Any, Optional, Type, List + +from fastapi import FastAPI, APIRouter, Request, Response, status # type: ignore +from fastapi.logger import logger +from fastapi.responses import JSONResponse + +from dapr.actor import Actor, ActorRuntime +from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN +from dapr.serializers import DefaultJSONSerializer + +DEFAULT_CONTENT_TYPE = 'application/json; utf-8' +DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id' + + +def _wrap_response( + status_code: int, + msg: Any, + error_code: Optional[str] = None, + content_type: Optional[str] = DEFAULT_CONTENT_TYPE, +): + resp = None + if isinstance(msg, str): + response_obj = { + 'message': msg, + } + if not (status_code >= 200 and status_code < 300) and error_code: + response_obj['errorCode'] = error_code + resp = JSONResponse(content=response_obj, status_code=status_code) + elif isinstance(msg, bytes): + resp = Response(content=msg, media_type=content_type) + else: + resp = JSONResponse(content=msg, status_code=status_code) + return resp + + +class DaprActor(object): + def __init__(self, app: FastAPI, router_tags: Optional[List[str]] = ['Actor']): + # router_tags should be added to all magic Dapr Actor methods implemented here + self._router_tags = router_tags + self._router = APIRouter() + self._dapr_serializer = DefaultJSONSerializer() + self.init_routes(self._router) + app.include_router(self._router) + + def init_routes(self, router: APIRouter): + @router.get('/healthz', tags=self._router_tags) + async def healthz(): + return {'status': 'ok'} + + @router.get('/dapr/config', tags=self._router_tags) + async def dapr_config(): + serialized = self._dapr_serializer.serialize(ActorRuntime.get_actor_config()) + return _wrap_response(status.HTTP_200_OK, serialized) + + @router.delete('/actors/{actor_type_name}/{actor_id}', tags=self._router_tags) + async def actor_deactivation(actor_type_name: str, actor_id: str): + try: + await ActorRuntime.deactivate(actor_type_name, actor_id) + except DaprInternalError as ex: + return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) + except Exception as ex: + return _wrap_response( + status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN + ) + + msg = f'deactivated actor: {actor_type_name}.{actor_id}' + logger.debug(msg) + return _wrap_response(status.HTTP_200_OK, msg) + + @router.put( + '/actors/{actor_type_name}/{actor_id}/method/{method_name}', tags=self._router_tags + ) + async def actor_method( + actor_type_name: str, actor_id: str, method_name: str, request: Request + ): + try: + # Read raw bytes from request stream + req_body = await request.body() + reentrancy_id = request.headers.get(DAPR_REENTRANCY_ID_HEADER) + result = await ActorRuntime.dispatch( + actor_type_name, actor_id, method_name, req_body, reentrancy_id + ) + except DaprInternalError as ex: + return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) + except Exception as ex: + return _wrap_response( + status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN + ) + + msg = f'called method. actor: {actor_type_name}.{actor_id}, method: {method_name}' + logger.debug(msg) + return _wrap_response(status.HTTP_200_OK, result) + + @router.put( + '/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}', tags=self._router_tags + ) + async def actor_timer( + actor_type_name: str, actor_id: str, timer_name: str, request: Request + ): + try: + # Read raw bytes from request stream + req_body = await request.body() + await ActorRuntime.fire_timer(actor_type_name, actor_id, timer_name, req_body) + except DaprInternalError as ex: + return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) + except Exception as ex: + return _wrap_response( + status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN + ) + + msg = f'called timer. actor: {actor_type_name}.{actor_id}, timer: {timer_name}' + logger.debug(msg) + return _wrap_response(status.HTTP_200_OK, msg) + + @router.put( + '/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}', + tags=self._router_tags, + ) + async def actor_reminder( + actor_type_name: str, actor_id: str, reminder_name: str, request: Request + ): + try: + # Read raw bytes from request stream + req_body = await request.body() + await ActorRuntime.fire_reminder(actor_type_name, actor_id, reminder_name, req_body) + except DaprInternalError as ex: + return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict()) + except Exception as ex: + return _wrap_response( + status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN + ) + + msg = f'called reminder. actor: {actor_type_name}.{actor_id}, reminder: {reminder_name}' + logger.debug(msg) + return _wrap_response(status.HTTP_200_OK, msg) + + async def register_actor(self, actor: Type[Actor], **kwargs) -> None: + await ActorRuntime.register_actor(actor, **kwargs) + logger.debug(f'registered actor: {actor.__class__.__name__}') diff --git a/ext/dapr-ext-fastapi/dapr/ext/fastapi/version.py b/ext/dapr-ext-fastapi/dapr/ext/fastapi/version.py index 287c4a57c..cc71c630a 100644 --- a/ext/dapr-ext-fastapi/dapr/ext/fastapi/version.py +++ b/ext/dapr-ext-fastapi/dapr/ext/fastapi/version.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -__version__ = '1.14.0rc1.dev' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +__version__ = '1.14.0rc1.dev' diff --git a/ext/dapr-ext-fastapi/setup.cfg b/ext/dapr-ext-fastapi/setup.cfg index f4f5ead43..774f577fe 100644 --- a/ext/dapr-ext-fastapi/setup.cfg +++ b/ext/dapr-ext-fastapi/setup.cfg @@ -1,36 +1,36 @@ -[metadata] -url = https://dapr.io/ -author = Dapr Authors -author_email = daprweb@microsoft.com -license = Apache -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 -project_urls = - Documentation = https://github.com/dapr/docs - Source = https://github.com/dapr/python-sdk - -[options] -python_requires = >=3.8 -packages = find_namespace: -include_package_data = True -install_requires = - dapr-dev >= 1.13.0rc1.dev - uvicorn >= 0.11.6 - fastapi >= 0.60.1 - -[options.packages.find] -include = - dapr.* - -exclude = - tests +[metadata] +url = https://dapr.io/ +author = Dapr Authors +author_email = daprweb@microsoft.com +license = Apache +license_file = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 +project_urls = + Documentation = https://github.com/dapr/docs + Source = https://github.com/dapr/python-sdk + +[options] +python_requires = >=3.8 +packages = find_namespace: +include_package_data = True +install_requires = + dapr-dev >= 1.13.0rc1.dev + uvicorn >= 0.11.6 + fastapi >= 0.60.1 + +[options.packages.find] +include = + dapr.* + +exclude = + tests diff --git a/ext/dapr-ext-fastapi/setup.py b/ext/dapr-ext-fastapi/setup.py index 8d6a400cf..6c9385ec4 100644 --- a/ext/dapr-ext-fastapi/setup.py +++ b/ext/dapr-ext-fastapi/setup.py @@ -1,64 +1,64 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os - -from setuptools import setup - -# Load version in dapr package. -version_info = {} -with open('dapr/ext/fastapi/version.py') as fp: - exec(fp.read(), version_info) -__version__ = version_info['__version__'] - - -def is_release(): - return '.dev' not in __version__ - - -name = 'dapr-ext-fastapi' -version = __version__ -description = 'The official release of Dapr FastAPI extension.' -long_description = """ -This is the FastAPI extension for Dapr. - -Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to -build resilient, stateless and stateful microservices that run on the cloud and edge and -embraces the diversity of languages and developer frameworks. - -Dapr codifies the best practices for building microservice applications into open, -independent, building blocks that enable you to build portable applications with the language -and framework of your choice. Each building block is independent and you can use one, some, -or all of them in your application. -""".lstrip() - -# Get build number from GITHUB_RUN_NUMBER environment variable -build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') - -if not is_release(): - name += '-dev' - version = f'{__version__}{build_number}' - description = 'The developmental release for Dapr FastAPI extension.' - long_description = 'This is the developmental release for Dapr FastAPI extension.' - -print(f'package name: {name}, version: {version}', flush=True) - - -setup( - name=name, - version=version, - description=description, - long_description=long_description, -) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from setuptools import setup + +# Load version in dapr package. +version_info = {} +with open('dapr/ext/fastapi/version.py') as fp: + exec(fp.read(), version_info) +__version__ = version_info['__version__'] + + +def is_release(): + return '.dev' not in __version__ + + +name = 'dapr-ext-fastapi' +version = __version__ +description = 'The official release of Dapr FastAPI extension.' +long_description = """ +This is the FastAPI extension for Dapr. + +Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to +build resilient, stateless and stateful microservices that run on the cloud and edge and +embraces the diversity of languages and developer frameworks. + +Dapr codifies the best practices for building microservice applications into open, +independent, building blocks that enable you to build portable applications with the language +and framework of your choice. Each building block is independent and you can use one, some, +or all of them in your application. +""".lstrip() + +# Get build number from GITHUB_RUN_NUMBER environment variable +build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') + +if not is_release(): + name += '-dev' + version = f'{__version__}{build_number}' + description = 'The developmental release for Dapr FastAPI extension.' + long_description = 'This is the developmental release for Dapr FastAPI extension.' + +print(f'package name: {name}, version: {version}', flush=True) + + +setup( + name=name, + version=version, + description=description, + long_description=long_description, +) diff --git a/ext/dapr-ext-fastapi/tests/test_app.py b/ext/dapr-ext-fastapi/tests/test_app.py index 831d55ebb..7eb6f82cc 100644 --- a/ext/dapr-ext-fastapi/tests/test_app.py +++ b/ext/dapr-ext-fastapi/tests/test_app.py @@ -1,157 +1,157 @@ -import unittest - -from fastapi import FastAPI -from fastapi.testclient import TestClient -from pydantic import BaseModel - -from dapr.ext.fastapi import DaprApp - - -class Message(BaseModel): - body: str - - -class DaprAppTest(unittest.TestCase): - def setUp(self): - self.app = FastAPI() - self.dapr_app = DaprApp(self.app) - self.client = TestClient(self.app) - - def test_subscribe_subscription_registered(self): - @self.dapr_app.subscribe(pubsub='pubsub', topic='test') - def event_handler(event_data: Message): - return 'default route' - - self.assertEqual(len(self.dapr_app._subscriptions), 1) - - self.assertIn('/dapr/subscribe', [route.path for route in self.app.router.routes]) - self.assertIn('/events/pubsub/test', [route.path for route in self.app.router.routes]) - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [ - { - 'pubsubname': 'pubsub', - 'topic': 'test', - 'route': '/events/pubsub/test', - 'metadata': {}, - } - ], - response.json(), - ) - - response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.text, '"default route"') - - def test_subscribe_with_route_subscription_registered_with_custom_route(self): - @self.dapr_app.subscribe(pubsub='pubsub', topic='test', route='/do-something') - def event_handler(event_data: Message): - return 'custom route' - - self.assertEqual(len(self.dapr_app._subscriptions), 1) - - self.assertIn('/dapr/subscribe', [route.path for route in self.app.router.routes]) - self.assertIn('/do-something', [route.path for route in self.app.router.routes]) - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [{'pubsubname': 'pubsub', 'topic': 'test', 'route': '/do-something', 'metadata': {}}], - response.json(), - ) - - response = self.client.post('/do-something', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.text, '"custom route"') - - def test_subscribe_metadata(self): - handler_metadata = {'rawPayload': 'true'} - - @self.dapr_app.subscribe(pubsub='pubsub', topic='test', metadata=handler_metadata) - def event_handler(event_data: Message): - return 'custom metadata' - - self.assertEqual((self.dapr_app._subscriptions[0]['metadata']['rawPayload']), 'true') - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [ - { - 'pubsubname': 'pubsub', - 'topic': 'test', - 'route': '/events/pubsub/test', - 'metadata': {'rawPayload': 'true'}, - } - ], - response.json(), - ) - - response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.text, '"custom metadata"') - - def test_router_tag(self): - app1 = FastAPI() - app2 = FastAPI() - app3 = FastAPI() - DaprApp(app_instance=app1, router_tags=['MyTag', 'PubSub']).subscribe( - pubsub='mypubsub', topic='test' - ) - DaprApp(app_instance=app2).subscribe(pubsub='mypubsub', topic='test') - DaprApp(app_instance=app3, router_tags=None).subscribe(pubsub='mypubsub', topic='test') - - PATHS_WITH_EXPECTED_TAGS = ['/dapr/subscribe', '/events/mypubsub/test'] - - foundTags = False - for route in app1.router.routes: - if hasattr(route, 'tags'): - self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) - self.assertEqual(['MyTag', 'PubSub'], route.tags) - foundTags = True - if not foundTags: - self.fail('No tags found') - - foundTags = False - for route in app2.router.routes: - if hasattr(route, 'tags'): - self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) - self.assertEqual(['PubSub'], route.tags) - foundTags = True - if not foundTags: - self.fail('No tags found') - - for route in app3.router.routes: - if hasattr(route, 'tags'): - if len(route.tags) > 0: - self.fail('Found tags on route that should not have any') - - def test_subscribe_dead_letter(self): - dead_letter_topic = 'dead-test' - - @self.dapr_app.subscribe(pubsub='pubsub', topic='test', dead_letter_topic=dead_letter_topic) - def event_handler(event_data: Message): - return 'dead letter test' - - self.assertEqual((self.dapr_app._subscriptions[0]['deadLetterTopic']), dead_letter_topic) - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [ - { - 'pubsubname': 'pubsub', - 'topic': 'test', - 'route': '/events/pubsub/test', - 'metadata': {}, - 'deadLetterTopic': dead_letter_topic, - } - ], - response.json(), - ) - - response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.text, '"dead letter test"') - - -if __name__ == '__main__': - unittest.main() +import unittest + +from fastapi import FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel + +from dapr.ext.fastapi import DaprApp + + +class Message(BaseModel): + body: str + + +class DaprAppTest(unittest.TestCase): + def setUp(self): + self.app = FastAPI() + self.dapr_app = DaprApp(self.app) + self.client = TestClient(self.app) + + def test_subscribe_subscription_registered(self): + @self.dapr_app.subscribe(pubsub='pubsub', topic='test') + def event_handler(event_data: Message): + return 'default route' + + self.assertEqual(len(self.dapr_app._subscriptions), 1) + + self.assertIn('/dapr/subscribe', [route.path for route in self.app.router.routes]) + self.assertIn('/events/pubsub/test', [route.path for route in self.app.router.routes]) + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [ + { + 'pubsubname': 'pubsub', + 'topic': 'test', + 'route': '/events/pubsub/test', + 'metadata': {}, + } + ], + response.json(), + ) + + response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.text, '"default route"') + + def test_subscribe_with_route_subscription_registered_with_custom_route(self): + @self.dapr_app.subscribe(pubsub='pubsub', topic='test', route='/do-something') + def event_handler(event_data: Message): + return 'custom route' + + self.assertEqual(len(self.dapr_app._subscriptions), 1) + + self.assertIn('/dapr/subscribe', [route.path for route in self.app.router.routes]) + self.assertIn('/do-something', [route.path for route in self.app.router.routes]) + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [{'pubsubname': 'pubsub', 'topic': 'test', 'route': '/do-something', 'metadata': {}}], + response.json(), + ) + + response = self.client.post('/do-something', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.text, '"custom route"') + + def test_subscribe_metadata(self): + handler_metadata = {'rawPayload': 'true'} + + @self.dapr_app.subscribe(pubsub='pubsub', topic='test', metadata=handler_metadata) + def event_handler(event_data: Message): + return 'custom metadata' + + self.assertEqual((self.dapr_app._subscriptions[0]['metadata']['rawPayload']), 'true') + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [ + { + 'pubsubname': 'pubsub', + 'topic': 'test', + 'route': '/events/pubsub/test', + 'metadata': {'rawPayload': 'true'}, + } + ], + response.json(), + ) + + response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.text, '"custom metadata"') + + def test_router_tag(self): + app1 = FastAPI() + app2 = FastAPI() + app3 = FastAPI() + DaprApp(app_instance=app1, router_tags=['MyTag', 'PubSub']).subscribe( + pubsub='mypubsub', topic='test' + ) + DaprApp(app_instance=app2).subscribe(pubsub='mypubsub', topic='test') + DaprApp(app_instance=app3, router_tags=None).subscribe(pubsub='mypubsub', topic='test') + + PATHS_WITH_EXPECTED_TAGS = ['/dapr/subscribe', '/events/mypubsub/test'] + + foundTags = False + for route in app1.router.routes: + if hasattr(route, 'tags'): + self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) + self.assertEqual(['MyTag', 'PubSub'], route.tags) + foundTags = True + if not foundTags: + self.fail('No tags found') + + foundTags = False + for route in app2.router.routes: + if hasattr(route, 'tags'): + self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) + self.assertEqual(['PubSub'], route.tags) + foundTags = True + if not foundTags: + self.fail('No tags found') + + for route in app3.router.routes: + if hasattr(route, 'tags'): + if len(route.tags) > 0: + self.fail('Found tags on route that should not have any') + + def test_subscribe_dead_letter(self): + dead_letter_topic = 'dead-test' + + @self.dapr_app.subscribe(pubsub='pubsub', topic='test', dead_letter_topic=dead_letter_topic) + def event_handler(event_data: Message): + return 'dead letter test' + + self.assertEqual((self.dapr_app._subscriptions[0]['deadLetterTopic']), dead_letter_topic) + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [ + { + 'pubsubname': 'pubsub', + 'topic': 'test', + 'route': '/events/pubsub/test', + 'metadata': {}, + 'deadLetterTopic': dead_letter_topic, + } + ], + response.json(), + ) + + response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.text, '"dead letter test"') + + +if __name__ == '__main__': + unittest.main() diff --git a/ext/dapr-ext-fastapi/tests/test_dapractor.py b/ext/dapr-ext-fastapi/tests/test_dapractor.py index ee863d726..e5d736001 100644 --- a/ext/dapr-ext-fastapi/tests/test_dapractor.py +++ b/ext/dapr-ext-fastapi/tests/test_dapractor.py @@ -1,89 +1,89 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import json -import unittest - -from fastapi import FastAPI - -from dapr.ext.fastapi.actor import DaprActor, _wrap_response - - -class DaprActorTest(unittest.TestCase): - def test_wrap_response_str(self): - r = _wrap_response(200, 'fake_message') - self.assertEqual({'message': 'fake_message'}, json.loads(r.body)) - self.assertEqual(200, r.status_code) - - def test_wrap_response_str_err(self): - r = _wrap_response(400, 'fake_message', 'ERR_FAKE') - self.assertEqual({'message': 'fake_message', 'errorCode': 'ERR_FAKE'}, json.loads(r.body)) - self.assertEqual(400, r.status_code) - - def test_wrap_response_bytes_text(self): - r = _wrap_response(200, b'fake_bytes_message', content_type='text/plain') - self.assertEqual(b'fake_bytes_message', r.body) - self.assertEqual(200, r.status_code) - self.assertEqual('text/plain', r.media_type) - - def test_wrap_response_obj(self): - fake_data = {'message': 'ok'} - r = _wrap_response(200, fake_data) - self.assertEqual(fake_data, json.loads(r.body)) - self.assertEqual(200, r.status_code) - - def test_router_tag(self): - app1 = FastAPI() - app2 = FastAPI() - app3 = FastAPI() - DaprActor(app=app1, router_tags=['MyTag', 'Actor']) - DaprActor(app=app2) - DaprActor(app=app3, router_tags=None) - - PATHS_WITH_EXPECTED_TAGS = [ - '/healthz', - '/dapr/config', - '/actors/{actor_type_name}/{actor_id}', - '/actors/{actor_type_name}/{actor_id}/method/{method_name}', - '/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}', - '/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}', - ] - - foundTags = False - for route in app1.router.routes: - if hasattr(route, 'tags'): - self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) - self.assertEqual(['MyTag', 'Actor'], route.tags) - foundTags = True - if not foundTags: - self.fail('No tags found') - - foundTags = False - for route in app2.router.routes: - if hasattr(route, 'tags'): - self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) - self.assertEqual(['Actor'], route.tags) - foundTags = True - if not foundTags: - self.fail('No tags found') - - for route in app3.router.routes: - if hasattr(route, 'tags'): - if len(route.tags) > 0: - self.fail('Found tags on route that should not have any') - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import json +import unittest + +from fastapi import FastAPI + +from dapr.ext.fastapi.actor import DaprActor, _wrap_response + + +class DaprActorTest(unittest.TestCase): + def test_wrap_response_str(self): + r = _wrap_response(200, 'fake_message') + self.assertEqual({'message': 'fake_message'}, json.loads(r.body)) + self.assertEqual(200, r.status_code) + + def test_wrap_response_str_err(self): + r = _wrap_response(400, 'fake_message', 'ERR_FAKE') + self.assertEqual({'message': 'fake_message', 'errorCode': 'ERR_FAKE'}, json.loads(r.body)) + self.assertEqual(400, r.status_code) + + def test_wrap_response_bytes_text(self): + r = _wrap_response(200, b'fake_bytes_message', content_type='text/plain') + self.assertEqual(b'fake_bytes_message', r.body) + self.assertEqual(200, r.status_code) + self.assertEqual('text/plain', r.media_type) + + def test_wrap_response_obj(self): + fake_data = {'message': 'ok'} + r = _wrap_response(200, fake_data) + self.assertEqual(fake_data, json.loads(r.body)) + self.assertEqual(200, r.status_code) + + def test_router_tag(self): + app1 = FastAPI() + app2 = FastAPI() + app3 = FastAPI() + DaprActor(app=app1, router_tags=['MyTag', 'Actor']) + DaprActor(app=app2) + DaprActor(app=app3, router_tags=None) + + PATHS_WITH_EXPECTED_TAGS = [ + '/healthz', + '/dapr/config', + '/actors/{actor_type_name}/{actor_id}', + '/actors/{actor_type_name}/{actor_id}/method/{method_name}', + '/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}', + '/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}', + ] + + foundTags = False + for route in app1.router.routes: + if hasattr(route, 'tags'): + self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) + self.assertEqual(['MyTag', 'Actor'], route.tags) + foundTags = True + if not foundTags: + self.fail('No tags found') + + foundTags = False + for route in app2.router.routes: + if hasattr(route, 'tags'): + self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS) + self.assertEqual(['Actor'], route.tags) + foundTags = True + if not foundTags: + self.fail('No tags found') + + for route in app3.router.routes: + if hasattr(route, 'tags'): + if len(route.tags) > 0: + self.fail('Found tags on route that should not have any') + + +if __name__ == '__main__': + unittest.main() diff --git a/ext/dapr-ext-grpc/LICENSE b/ext/dapr-ext-grpc/LICENSE index be033a7fd..da1ba6b93 100644 --- a/ext/dapr-ext-grpc/LICENSE +++ b/ext/dapr-ext-grpc/LICENSE @@ -1,203 +1,203 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 The Dapr Authors. - - and others that have contributed code to the public domain. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Dapr Authors. + + and others that have contributed code to the public domain. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/ext/dapr-ext-grpc/README.rst b/ext/dapr-ext-grpc/README.rst index 2c610e388..f90381d33 100644 --- a/ext/dapr-ext-grpc/README.rst +++ b/ext/dapr-ext-grpc/README.rst @@ -1,22 +1,22 @@ -dapr-ext-grpc extension -======================= - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/dapr-ext-grpc.svg - :target: https://pypi.org/project/dapr-ext-grpc/ - -This gRPC extension is used for gRPC appcallback - -Installation ------------- - -:: - - pip install dapr-ext-grpc - -References ----------- - -* `Dapr `_ -* `Dapr Python-SDK `_ +dapr-ext-grpc extension +======================= + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/dapr-ext-grpc.svg + :target: https://pypi.org/project/dapr-ext-grpc/ + +This gRPC extension is used for gRPC appcallback + +Installation +------------ + +:: + + pip install dapr-ext-grpc + +References +---------- + +* `Dapr `_ +* `Dapr Python-SDK `_ diff --git a/ext/dapr-ext-grpc/dapr/ext/grpc/__init__.py b/ext/dapr-ext-grpc/dapr/ext/grpc/__init__.py index 3a843ea92..2b6d8c057 100644 --- a/ext/dapr-ext-grpc/dapr/ext/grpc/__init__.py +++ b/ext/dapr-ext-grpc/dapr/ext/grpc/__init__.py @@ -1,29 +1,29 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.clients.grpc._request import InvokeMethodRequest, BindingRequest -from dapr.clients.grpc._response import InvokeMethodResponse, TopicEventResponse - -from dapr.ext.grpc.app import App, Rule # type:ignore - - -__all__ = [ - 'App', - 'Rule', - 'InvokeMethodRequest', - 'InvokeMethodResponse', - 'BindingRequest', - 'TopicEventResponse', -] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.clients.grpc._request import InvokeMethodRequest, BindingRequest +from dapr.clients.grpc._response import InvokeMethodResponse, TopicEventResponse + +from dapr.ext.grpc.app import App, Rule # type:ignore + + +__all__ = [ + 'App', + 'Rule', + 'InvokeMethodRequest', + 'InvokeMethodResponse', + 'BindingRequest', + 'TopicEventResponse', +] diff --git a/ext/dapr-ext-grpc/dapr/ext/grpc/_health_servicer.py b/ext/dapr-ext-grpc/dapr/ext/grpc/_health_servicer.py index 029dff745..da46c4042 100644 --- a/ext/dapr-ext-grpc/dapr/ext/grpc/_health_servicer.py +++ b/ext/dapr-ext-grpc/dapr/ext/grpc/_health_servicer.py @@ -1,32 +1,32 @@ -import grpc -from typing import Callable, Optional - -from dapr.proto import appcallback_service_v1 -from dapr.proto.runtime.v1.appcallback_pb2 import HealthCheckResponse - -HealthCheckCallable = Optional[Callable[[], None]] - - -class _HealthCheckServicer(appcallback_service_v1.AppCallbackHealthCheckServicer): - """The implementation of HealthCheck Server. - - :class:`App` provides useful decorators to register method, topic, input bindings. - """ - - def __init__(self): - self._health_check_cb: Optional[HealthCheckCallable] = None - - def register_health_check(self, cb: HealthCheckCallable) -> None: - if not cb: - raise ValueError('health check callback must be defined') - self._health_check_cb = cb - - def HealthCheck(self, request, context): - """Health check.""" - - if not self._health_check_cb: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - self._health_check_cb() - return HealthCheckResponse() +import grpc +from typing import Callable, Optional + +from dapr.proto import appcallback_service_v1 +from dapr.proto.runtime.v1.appcallback_pb2 import HealthCheckResponse + +HealthCheckCallable = Optional[Callable[[], None]] + + +class _HealthCheckServicer(appcallback_service_v1.AppCallbackHealthCheckServicer): + """The implementation of HealthCheck Server. + + :class:`App` provides useful decorators to register method, topic, input bindings. + """ + + def __init__(self): + self._health_check_cb: Optional[HealthCheckCallable] = None + + def register_health_check(self, cb: HealthCheckCallable) -> None: + if not cb: + raise ValueError('health check callback must be defined') + self._health_check_cb = cb + + def HealthCheck(self, request, context): + """Health check.""" + + if not self._health_check_cb: + context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + self._health_check_cb() + return HealthCheckResponse() diff --git a/ext/dapr-ext-grpc/dapr/ext/grpc/_servicer.py b/ext/dapr-ext-grpc/dapr/ext/grpc/_servicer.py index 2217d50fe..2264db049 100644 --- a/ext/dapr-ext-grpc/dapr/ext/grpc/_servicer.py +++ b/ext/dapr-ext-grpc/dapr/ext/grpc/_servicer.py @@ -1,226 +1,226 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import grpc - -from cloudevents.sdk.event import v1 # type: ignore -from typing import Callable, Dict, List, Optional, Tuple, Union - -from google.protobuf import empty_pb2 -from google.protobuf.message import Message as GrpcMessage -from google.protobuf.struct_pb2 import Struct - -from dapr.proto import appcallback_service_v1, common_v1, appcallback_v1 -from dapr.proto.runtime.v1.appcallback_pb2 import TopicEventRequest, BindingEventRequest -from dapr.proto.common.v1.common_pb2 import InvokeRequest -from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE -from dapr.clients.grpc._request import InvokeMethodRequest, BindingRequest -from dapr.clients.grpc._response import InvokeMethodResponse, TopicEventResponse - -InvokeMethodCallable = Callable[[InvokeMethodRequest], Union[str, bytes, InvokeMethodResponse]] -TopicSubscribeCallable = Callable[[v1.Event], Optional[TopicEventResponse]] -BindingCallable = Callable[[BindingRequest], None] - -DELIMITER = ':' - - -class Rule: - def __init__(self, match: str, priority: int) -> None: - self.match = match - self.priority = priority - - -class _RegisteredSubscription: - def __init__( - self, - subscription: appcallback_v1.TopicSubscription, - rules: List[Tuple[int, appcallback_v1.TopicRule]], - ): - self.subscription = subscription - self.rules = rules - - -class _CallbackServicer(appcallback_service_v1.AppCallbackServicer): - """The implementation of AppCallback Server. - - This internal class implements application server and provides helpers to register - method, topic, and input bindings. It implements the routing handling logic to route - mulitple methods, topics, and bindings. - - :class:`App` provides useful decorators to register method, topic, input bindings. - """ - - def __init__(self): - self._invoke_method_map: Dict[str, InvokeMethodCallable] = {} - self._topic_map: Dict[str, TopicSubscribeCallable] = {} - self._binding_map: Dict[str, BindingCallable] = {} - - self._registered_topics_map: Dict[str, _RegisteredSubscription] = {} - self._registered_topics: List[appcallback_v1.TopicSubscription] = [] - self._registered_bindings: List[str] = [] - - def register_method(self, method: str, cb: InvokeMethodCallable) -> None: - """Registers method for service invocation.""" - if method in self._invoke_method_map: - raise ValueError(f'{method} is already registered') - self._invoke_method_map[method] = cb - - def register_topic( - self, - pubsub_name: str, - topic: str, - cb: TopicSubscribeCallable, - metadata: Optional[Dict[str, str]], - dead_letter_topic: Optional[str] = None, - rule: Optional[Rule] = None, - disable_topic_validation: Optional[bool] = False, - ) -> None: - """Registers topic subscription for pubsub.""" - if not disable_topic_validation: - topic_key = pubsub_name + DELIMITER + topic - else: - topic_key = pubsub_name - pubsub_topic = topic_key + DELIMITER - if rule is not None: - path = getattr(cb, '__name__', rule.match) - pubsub_topic = pubsub_topic + path - if pubsub_topic in self._topic_map: - raise ValueError(f'{topic} is already registered with {pubsub_name}') - self._topic_map[pubsub_topic] = cb - - registered_topic = self._registered_topics_map.get(topic_key) - sub: appcallback_v1.TopicSubscription = appcallback_v1.TopicSubscription() - rules: List[Tuple[int, appcallback_v1.TopicRule]] = [] - if not registered_topic: - sub = appcallback_v1.TopicSubscription( - pubsub_name=pubsub_name, - topic=topic, - metadata=metadata, - routes=appcallback_v1.TopicRoutes(), - ) - if dead_letter_topic: - sub.dead_letter_topic = dead_letter_topic - registered_topic = _RegisteredSubscription(sub, rules) - self._registered_topics_map[topic_key] = registered_topic - self._registered_topics.append(sub) - - sub = registered_topic.subscription - rules = registered_topic.rules - - if rule: - path = getattr(cb, '__name__', rule.match) - rules.append((rule.priority, appcallback_v1.TopicRule(match=rule.match, path=path))) - rules.sort(key=lambda x: x[0]) - rs = [rule for id, rule in rules] - del sub.routes.rules[:] - sub.routes.rules.extend(rs) - - def register_binding(self, name: str, cb: BindingCallable) -> None: - """Registers input bindings.""" - if name in self._binding_map: - raise ValueError(f'{name} is already registered') - self._binding_map[name] = cb - self._registered_bindings.append(name) - - def OnInvoke(self, request: InvokeRequest, context): - """Invokes service method with InvokeRequest.""" - if request.method not in self._invoke_method_map: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - raise NotImplementedError(f'{request.method} method not implemented!') - - req = InvokeMethodRequest(request.data, request.content_type) - req.metadata = context.invocation_metadata() - resp = self._invoke_method_map[request.method](req) - - if not resp: - return common_v1.InvokeResponse() - - resp_data = InvokeMethodResponse() - if isinstance(resp, (bytes, str)): - resp_data.set_data(resp) - resp_data.content_type = DEFAULT_JSON_CONTENT_TYPE - elif isinstance(resp, GrpcMessage): - resp_data.set_data(resp) - elif isinstance(resp, InvokeMethodResponse): - resp_data = resp - else: - context.set_code(grpc.StatusCode.OUT_OF_RANGE) - context.set_details(f'{type(resp)} is the invalid return type.') - raise NotImplementedError(f'{request.method} method not implemented!') - - if len(resp_data.get_headers()) > 0: - context.send_initial_metadata(resp_data.get_headers()) - - content_type = '' - if resp_data.content_type: - content_type = resp_data.content_type - - return common_v1.InvokeResponse(data=resp_data.proto, content_type=content_type) - - def ListTopicSubscriptions(self, request, context): - """Lists all topics subscribed by this app.""" - return appcallback_v1.ListTopicSubscriptionsResponse(subscriptions=self._registered_topics) - - def OnTopicEvent(self, request: TopicEventRequest, context): - """Subscribes events from Pubsub.""" - pubsub_topic = request.pubsub_name + DELIMITER + request.topic + DELIMITER + request.path - no_validation_key = request.pubsub_name + DELIMITER + request.path - - if pubsub_topic not in self._topic_map: - if no_validation_key in self._topic_map: - pubsub_topic = no_validation_key - else: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - raise NotImplementedError(f'topic {request.topic} is not implemented!') - - customdata: Struct = request.extensions - extensions = dict() - for k, v in customdata.items(): - extensions[k] = v - for k, v in context.invocation_metadata(): - extensions['_metadata_' + k] = v - - event = v1.Event() - event.SetEventType(request.type) - event.SetEventID(request.id) - event.SetSource(request.source) - event.SetData(request.data) - event.SetContentType(request.data_content_type) - event.SetSubject(request.topic) - event.SetExtensions(extensions) - - response = self._topic_map[pubsub_topic](event) - if isinstance(response, TopicEventResponse): - return appcallback_v1.TopicEventResponse(status=response.status.value) - return empty_pb2.Empty() - - def ListInputBindings(self, request, context): - """Lists all input bindings subscribed by this app.""" - return appcallback_v1.ListInputBindingsResponse(bindings=self._registered_bindings) - - def OnBindingEvent(self, request: BindingEventRequest, context): - """Listens events from the input bindings - User application can save the states or send the events to the output - bindings optionally by returning BindingEventResponse. - """ - if request.name not in self._binding_map: - context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore - raise NotImplementedError(f'{request.name} binding not implemented!') - - req = BindingRequest(request.data, dict(request.metadata)) - req.metadata = context.invocation_metadata() - self._binding_map[request.name](req) - - # TODO: support output bindings options - return appcallback_v1.BindingEventResponse() +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import grpc + +from cloudevents.sdk.event import v1 # type: ignore +from typing import Callable, Dict, List, Optional, Tuple, Union + +from google.protobuf import empty_pb2 +from google.protobuf.message import Message as GrpcMessage +from google.protobuf.struct_pb2 import Struct + +from dapr.proto import appcallback_service_v1, common_v1, appcallback_v1 +from dapr.proto.runtime.v1.appcallback_pb2 import TopicEventRequest, BindingEventRequest +from dapr.proto.common.v1.common_pb2 import InvokeRequest +from dapr.clients.base import DEFAULT_JSON_CONTENT_TYPE +from dapr.clients.grpc._request import InvokeMethodRequest, BindingRequest +from dapr.clients.grpc._response import InvokeMethodResponse, TopicEventResponse + +InvokeMethodCallable = Callable[[InvokeMethodRequest], Union[str, bytes, InvokeMethodResponse]] +TopicSubscribeCallable = Callable[[v1.Event], Optional[TopicEventResponse]] +BindingCallable = Callable[[BindingRequest], None] + +DELIMITER = ':' + + +class Rule: + def __init__(self, match: str, priority: int) -> None: + self.match = match + self.priority = priority + + +class _RegisteredSubscription: + def __init__( + self, + subscription: appcallback_v1.TopicSubscription, + rules: List[Tuple[int, appcallback_v1.TopicRule]], + ): + self.subscription = subscription + self.rules = rules + + +class _CallbackServicer(appcallback_service_v1.AppCallbackServicer): + """The implementation of AppCallback Server. + + This internal class implements application server and provides helpers to register + method, topic, and input bindings. It implements the routing handling logic to route + mulitple methods, topics, and bindings. + + :class:`App` provides useful decorators to register method, topic, input bindings. + """ + + def __init__(self): + self._invoke_method_map: Dict[str, InvokeMethodCallable] = {} + self._topic_map: Dict[str, TopicSubscribeCallable] = {} + self._binding_map: Dict[str, BindingCallable] = {} + + self._registered_topics_map: Dict[str, _RegisteredSubscription] = {} + self._registered_topics: List[appcallback_v1.TopicSubscription] = [] + self._registered_bindings: List[str] = [] + + def register_method(self, method: str, cb: InvokeMethodCallable) -> None: + """Registers method for service invocation.""" + if method in self._invoke_method_map: + raise ValueError(f'{method} is already registered') + self._invoke_method_map[method] = cb + + def register_topic( + self, + pubsub_name: str, + topic: str, + cb: TopicSubscribeCallable, + metadata: Optional[Dict[str, str]], + dead_letter_topic: Optional[str] = None, + rule: Optional[Rule] = None, + disable_topic_validation: Optional[bool] = False, + ) -> None: + """Registers topic subscription for pubsub.""" + if not disable_topic_validation: + topic_key = pubsub_name + DELIMITER + topic + else: + topic_key = pubsub_name + pubsub_topic = topic_key + DELIMITER + if rule is not None: + path = getattr(cb, '__name__', rule.match) + pubsub_topic = pubsub_topic + path + if pubsub_topic in self._topic_map: + raise ValueError(f'{topic} is already registered with {pubsub_name}') + self._topic_map[pubsub_topic] = cb + + registered_topic = self._registered_topics_map.get(topic_key) + sub: appcallback_v1.TopicSubscription = appcallback_v1.TopicSubscription() + rules: List[Tuple[int, appcallback_v1.TopicRule]] = [] + if not registered_topic: + sub = appcallback_v1.TopicSubscription( + pubsub_name=pubsub_name, + topic=topic, + metadata=metadata, + routes=appcallback_v1.TopicRoutes(), + ) + if dead_letter_topic: + sub.dead_letter_topic = dead_letter_topic + registered_topic = _RegisteredSubscription(sub, rules) + self._registered_topics_map[topic_key] = registered_topic + self._registered_topics.append(sub) + + sub = registered_topic.subscription + rules = registered_topic.rules + + if rule: + path = getattr(cb, '__name__', rule.match) + rules.append((rule.priority, appcallback_v1.TopicRule(match=rule.match, path=path))) + rules.sort(key=lambda x: x[0]) + rs = [rule for id, rule in rules] + del sub.routes.rules[:] + sub.routes.rules.extend(rs) + + def register_binding(self, name: str, cb: BindingCallable) -> None: + """Registers input bindings.""" + if name in self._binding_map: + raise ValueError(f'{name} is already registered') + self._binding_map[name] = cb + self._registered_bindings.append(name) + + def OnInvoke(self, request: InvokeRequest, context): + """Invokes service method with InvokeRequest.""" + if request.method not in self._invoke_method_map: + context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore + raise NotImplementedError(f'{request.method} method not implemented!') + + req = InvokeMethodRequest(request.data, request.content_type) + req.metadata = context.invocation_metadata() + resp = self._invoke_method_map[request.method](req) + + if not resp: + return common_v1.InvokeResponse() + + resp_data = InvokeMethodResponse() + if isinstance(resp, (bytes, str)): + resp_data.set_data(resp) + resp_data.content_type = DEFAULT_JSON_CONTENT_TYPE + elif isinstance(resp, GrpcMessage): + resp_data.set_data(resp) + elif isinstance(resp, InvokeMethodResponse): + resp_data = resp + else: + context.set_code(grpc.StatusCode.OUT_OF_RANGE) + context.set_details(f'{type(resp)} is the invalid return type.') + raise NotImplementedError(f'{request.method} method not implemented!') + + if len(resp_data.get_headers()) > 0: + context.send_initial_metadata(resp_data.get_headers()) + + content_type = '' + if resp_data.content_type: + content_type = resp_data.content_type + + return common_v1.InvokeResponse(data=resp_data.proto, content_type=content_type) + + def ListTopicSubscriptions(self, request, context): + """Lists all topics subscribed by this app.""" + return appcallback_v1.ListTopicSubscriptionsResponse(subscriptions=self._registered_topics) + + def OnTopicEvent(self, request: TopicEventRequest, context): + """Subscribes events from Pubsub.""" + pubsub_topic = request.pubsub_name + DELIMITER + request.topic + DELIMITER + request.path + no_validation_key = request.pubsub_name + DELIMITER + request.path + + if pubsub_topic not in self._topic_map: + if no_validation_key in self._topic_map: + pubsub_topic = no_validation_key + else: + context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore + raise NotImplementedError(f'topic {request.topic} is not implemented!') + + customdata: Struct = request.extensions + extensions = dict() + for k, v in customdata.items(): + extensions[k] = v + for k, v in context.invocation_metadata(): + extensions['_metadata_' + k] = v + + event = v1.Event() + event.SetEventType(request.type) + event.SetEventID(request.id) + event.SetSource(request.source) + event.SetData(request.data) + event.SetContentType(request.data_content_type) + event.SetSubject(request.topic) + event.SetExtensions(extensions) + + response = self._topic_map[pubsub_topic](event) + if isinstance(response, TopicEventResponse): + return appcallback_v1.TopicEventResponse(status=response.status.value) + return empty_pb2.Empty() + + def ListInputBindings(self, request, context): + """Lists all input bindings subscribed by this app.""" + return appcallback_v1.ListInputBindingsResponse(bindings=self._registered_bindings) + + def OnBindingEvent(self, request: BindingEventRequest, context): + """Listens events from the input bindings + User application can save the states or send the events to the output + bindings optionally by returning BindingEventResponse. + """ + if request.name not in self._binding_map: + context.set_code(grpc.StatusCode.UNIMPLEMENTED) # type: ignore + raise NotImplementedError(f'{request.name} binding not implemented!') + + req = BindingRequest(request.data, dict(request.metadata)) + req.metadata = context.invocation_metadata() + self._binding_map[request.name](req) + + # TODO: support output bindings options + return appcallback_v1.BindingEventResponse() diff --git a/ext/dapr-ext-grpc/dapr/ext/grpc/app.py b/ext/dapr-ext-grpc/dapr/ext/grpc/app.py index 13a0bec69..72951205e 100644 --- a/ext/dapr-ext-grpc/dapr/ext/grpc/app.py +++ b/ext/dapr-ext-grpc/dapr/ext/grpc/app.py @@ -1,200 +1,200 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import grpc - -from concurrent import futures -from typing import Dict, Optional - -from dapr.conf import settings -from dapr.ext.grpc._servicer import _CallbackServicer, Rule # type: ignore -from dapr.ext.grpc._health_servicer import _HealthCheckServicer # type: ignore -from dapr.proto import appcallback_service_v1 - - -class App: - """App object implements a Dapr application callback which can interact with Dapr runtime. - Once its object is initiated, it will act as a central registry for service invocation, - subscribing topic, and input bindings. - - You can create a :class:`App` instance in your main module: - - from dapr.ext.grpc import App - app = App() - """ - - def __init__(self, max_grpc_message_length: Optional[int] = None, **kwargs): - """Inits App object and creates gRPC server. - - Args: - max_grpc_messsage_length (int, optional): The maximum grpc send and receive - message length in bytes. Only used when kwargs are not set. - kwargs: arguments to grpc.server() - """ - self._servicer = _CallbackServicer() - self._health_check_servicer = _HealthCheckServicer() - if not kwargs: - options = [] - if max_grpc_message_length is not None: - options = [ - ('grpc.max_send_message_length', max_grpc_message_length), - ('grpc.max_receive_message_length', max_grpc_message_length), - ] - self._server = grpc.server( # type: ignore - futures.ThreadPoolExecutor(max_workers=10), options=options - ) - else: - self._server = grpc.server(**kwargs) # type: ignore - appcallback_service_v1.add_AppCallbackServicer_to_server(self._servicer, self._server) - appcallback_service_v1.add_AppCallbackHealthCheckServicer_to_server( - self._health_check_servicer, self._server - ) - - def __del__(self): - self.stop() - - def add_external_service(self, servicer_callback, external_servicer): - """Adds an external gRPC service to the same server""" - servicer_callback(external_servicer, self._server) - - def register_health_check(self, health_check_callback): - """Adds a health check callback - - The below example adds a basic health check to check Dapr gRPC is running - - @app.register_health_check(lambda: None) - """ - self._health_check_servicer.register_health_check(health_check_callback) - - def run(self, app_port: Optional[int] = None, listen_address: Optional[str] = None) -> None: - """Starts app gRPC server and waits until :class:`App`.stop() is called. - - Args: - app_port (int, optional): The port on which to listen for incoming gRPC calls. - Defaults to settings.GRPC_APP_PORT. - listen_address (str, optional): The IP address on which to listen for incoming gRPC - calls. Defaults to [::] (all IP addresses). - """ - if app_port is None: - app_port = settings.GRPC_APP_PORT - self._server.add_insecure_port(f'{listen_address if listen_address else "[::]"}:{app_port}') - self._server.start() - self._server.wait_for_termination() - - def stop(self) -> None: - """Stops app server.""" - self._server.stop(0) - - def method(self, name: str): - """A decorator that is used to register the method for the service invocation. - - Return JSON formatted data response:: - - @app.method('start') - def start(request: InvokeMethodRequest): - - ... - - return json.dumps() - - Return Protocol buffer response:: - - @app.method('start') - def start(request: InvokeMethodRequest): - - ... - - return CustomProtoResponse(data='hello world') - - - Specify Response header:: - - @app.method('start') - def start(request: InvokeMethodRequest): - - ... - - resp = InvokeMethodResponse('hello world', 'text/plain') - resp.headers = ('key', 'value') - - return resp - - Args: - name (str): name of invoked method - """ - - def decorator(func): - self._servicer.register_method(name, func) - - return decorator - - def subscribe( - self, - pubsub_name: str, - topic: str, - metadata: Optional[Dict[str, str]] = {}, - dead_letter_topic: Optional[str] = None, - rule: Optional[Rule] = None, - disable_topic_validation: Optional[bool] = False, - ): - """A decorator that is used to register the subscribing topic method. - - The below example registers 'topic' subscription topic and pass custom - metadata to pubsub component:: - - from cloudevents.sdk.event import v1 - - @app.subscribe('pubsub_name', 'topic', metadata=(('session-id', 'session-id-value'),)) - def topic(event: v1.Event) -> None: - ... - - Args: - pubsub_name (str): the name of the pubsub component - topic (str): the topic name which is subscribed - metadata (dict, optional): metadata which will be passed to pubsub component - during initialization - dead_letter_topic (str, optional): the dead letter topic name for the subscription - """ - - def decorator(func): - self._servicer.register_topic( - pubsub_name, - topic, - func, - metadata, - dead_letter_topic, - rule, - disable_topic_validation, - ) - - return decorator - - def binding(self, name: str): - """A decorator that is used to register input binding. - - The below registers input binding which this application subscribes: - - @app.binding('input') - def input(request: BindingRequest) -> None: - ... - - Args: - name (str): the name of invoked method - """ - - def decorator(func): - self._servicer.register_binding(name, func) - - return decorator +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import grpc + +from concurrent import futures +from typing import Dict, Optional + +from dapr.conf import settings +from dapr.ext.grpc._servicer import _CallbackServicer, Rule # type: ignore +from dapr.ext.grpc._health_servicer import _HealthCheckServicer # type: ignore +from dapr.proto import appcallback_service_v1 + + +class App: + """App object implements a Dapr application callback which can interact with Dapr runtime. + Once its object is initiated, it will act as a central registry for service invocation, + subscribing topic, and input bindings. + + You can create a :class:`App` instance in your main module: + + from dapr.ext.grpc import App + app = App() + """ + + def __init__(self, max_grpc_message_length: Optional[int] = None, **kwargs): + """Inits App object and creates gRPC server. + + Args: + max_grpc_messsage_length (int, optional): The maximum grpc send and receive + message length in bytes. Only used when kwargs are not set. + kwargs: arguments to grpc.server() + """ + self._servicer = _CallbackServicer() + self._health_check_servicer = _HealthCheckServicer() + if not kwargs: + options = [] + if max_grpc_message_length is not None: + options = [ + ('grpc.max_send_message_length', max_grpc_message_length), + ('grpc.max_receive_message_length', max_grpc_message_length), + ] + self._server = grpc.server( # type: ignore + futures.ThreadPoolExecutor(max_workers=10), options=options + ) + else: + self._server = grpc.server(**kwargs) # type: ignore + appcallback_service_v1.add_AppCallbackServicer_to_server(self._servicer, self._server) + appcallback_service_v1.add_AppCallbackHealthCheckServicer_to_server( + self._health_check_servicer, self._server + ) + + def __del__(self): + self.stop() + + def add_external_service(self, servicer_callback, external_servicer): + """Adds an external gRPC service to the same server""" + servicer_callback(external_servicer, self._server) + + def register_health_check(self, health_check_callback): + """Adds a health check callback + + The below example adds a basic health check to check Dapr gRPC is running + + @app.register_health_check(lambda: None) + """ + self._health_check_servicer.register_health_check(health_check_callback) + + def run(self, app_port: Optional[int] = None, listen_address: Optional[str] = None) -> None: + """Starts app gRPC server and waits until :class:`App`.stop() is called. + + Args: + app_port (int, optional): The port on which to listen for incoming gRPC calls. + Defaults to settings.GRPC_APP_PORT. + listen_address (str, optional): The IP address on which to listen for incoming gRPC + calls. Defaults to [::] (all IP addresses). + """ + if app_port is None: + app_port = settings.GRPC_APP_PORT + self._server.add_insecure_port(f'{listen_address if listen_address else "[::]"}:{app_port}') + self._server.start() + self._server.wait_for_termination() + + def stop(self) -> None: + """Stops app server.""" + self._server.stop(0) + + def method(self, name: str): + """A decorator that is used to register the method for the service invocation. + + Return JSON formatted data response:: + + @app.method('start') + def start(request: InvokeMethodRequest): + + ... + + return json.dumps() + + Return Protocol buffer response:: + + @app.method('start') + def start(request: InvokeMethodRequest): + + ... + + return CustomProtoResponse(data='hello world') + + + Specify Response header:: + + @app.method('start') + def start(request: InvokeMethodRequest): + + ... + + resp = InvokeMethodResponse('hello world', 'text/plain') + resp.headers = ('key', 'value') + + return resp + + Args: + name (str): name of invoked method + """ + + def decorator(func): + self._servicer.register_method(name, func) + + return decorator + + def subscribe( + self, + pubsub_name: str, + topic: str, + metadata: Optional[Dict[str, str]] = {}, + dead_letter_topic: Optional[str] = None, + rule: Optional[Rule] = None, + disable_topic_validation: Optional[bool] = False, + ): + """A decorator that is used to register the subscribing topic method. + + The below example registers 'topic' subscription topic and pass custom + metadata to pubsub component:: + + from cloudevents.sdk.event import v1 + + @app.subscribe('pubsub_name', 'topic', metadata=(('session-id', 'session-id-value'),)) + def topic(event: v1.Event) -> None: + ... + + Args: + pubsub_name (str): the name of the pubsub component + topic (str): the topic name which is subscribed + metadata (dict, optional): metadata which will be passed to pubsub component + during initialization + dead_letter_topic (str, optional): the dead letter topic name for the subscription + """ + + def decorator(func): + self._servicer.register_topic( + pubsub_name, + topic, + func, + metadata, + dead_letter_topic, + rule, + disable_topic_validation, + ) + + return decorator + + def binding(self, name: str): + """A decorator that is used to register input binding. + + The below registers input binding which this application subscribes: + + @app.binding('input') + def input(request: BindingRequest) -> None: + ... + + Args: + name (str): the name of invoked method + """ + + def decorator(func): + self._servicer.register_binding(name, func) + + return decorator diff --git a/ext/dapr-ext-grpc/dapr/ext/grpc/version.py b/ext/dapr-ext-grpc/dapr/ext/grpc/version.py index 287c4a57c..cc71c630a 100644 --- a/ext/dapr-ext-grpc/dapr/ext/grpc/version.py +++ b/ext/dapr-ext-grpc/dapr/ext/grpc/version.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -__version__ = '1.14.0rc1.dev' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +__version__ = '1.14.0rc1.dev' diff --git a/ext/dapr-ext-grpc/setup.cfg b/ext/dapr-ext-grpc/setup.cfg index 7a25b628e..dc46a7764 100644 --- a/ext/dapr-ext-grpc/setup.cfg +++ b/ext/dapr-ext-grpc/setup.cfg @@ -1,35 +1,35 @@ -[metadata] -url = https://dapr.io/ -author = Dapr Authors -author_email = daprweb@microsoft.com -license = Apache -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 -project_urls = - Documentation = https://github.com/dapr/docs - Source = https://github.com/dapr/python-sdk - -[options] -python_requires = >=3.8 -packages = find_namespace: -include_package_data = True -install_requires = - dapr-dev >= 1.13.0rc1.dev - cloudevents >= 1.0.0 - -[options.packages.find] -include = - dapr.* - -exclude = - tests +[metadata] +url = https://dapr.io/ +author = Dapr Authors +author_email = daprweb@microsoft.com +license = Apache +license_file = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 +project_urls = + Documentation = https://github.com/dapr/docs + Source = https://github.com/dapr/python-sdk + +[options] +python_requires = >=3.8 +packages = find_namespace: +include_package_data = True +install_requires = + dapr-dev >= 1.13.0rc1.dev + cloudevents >= 1.0.0 + +[options.packages.find] +include = + dapr.* + +exclude = + tests diff --git a/ext/dapr-ext-grpc/setup.py b/ext/dapr-ext-grpc/setup.py index 67d282b68..ee1b4692d 100644 --- a/ext/dapr-ext-grpc/setup.py +++ b/ext/dapr-ext-grpc/setup.py @@ -1,64 +1,64 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os - -from setuptools import setup - -# Load version in dapr package. -version_info = {} -with open('dapr/ext/grpc/version.py') as fp: - exec(fp.read(), version_info) -__version__ = version_info['__version__'] - - -def is_release(): - return '.dev' not in __version__ - - -name = 'dapr-ext-grpc' -version = __version__ -description = 'The official release of Dapr Python SDK gRPC Extension.' -long_description = """ -This is the gRPC extension for Dapr. - -Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to -build resilient, stateless and stateful microservices that run on the cloud and edge and -embraces the diversity of languages and developer frameworks. - -Dapr codifies the best practices for building microservice applications into open, -independent, building blocks that enable you to build portable applications with the language -and framework of your choice. Each building block is independent and you can use one, some, -or all of them in your application. -""".lstrip() - -# Get build number from GITHUB_RUN_NUMBER environment variable -build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') - -if not is_release(): - name += '-dev' - version = f'{__version__}{build_number}' - description = 'The developmental release for Dapr gRPC AppCallback.' - long_description = 'This is the developmental release for Dapr gRPC AppCallback.' - -print(f'package name: {name}, version: {version}', flush=True) - - -setup( - name=name, - version=version, - description=description, - long_description=long_description, -) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from setuptools import setup + +# Load version in dapr package. +version_info = {} +with open('dapr/ext/grpc/version.py') as fp: + exec(fp.read(), version_info) +__version__ = version_info['__version__'] + + +def is_release(): + return '.dev' not in __version__ + + +name = 'dapr-ext-grpc' +version = __version__ +description = 'The official release of Dapr Python SDK gRPC Extension.' +long_description = """ +This is the gRPC extension for Dapr. + +Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to +build resilient, stateless and stateful microservices that run on the cloud and edge and +embraces the diversity of languages and developer frameworks. + +Dapr codifies the best practices for building microservice applications into open, +independent, building blocks that enable you to build portable applications with the language +and framework of your choice. Each building block is independent and you can use one, some, +or all of them in your application. +""".lstrip() + +# Get build number from GITHUB_RUN_NUMBER environment variable +build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') + +if not is_release(): + name += '-dev' + version = f'{__version__}{build_number}' + description = 'The developmental release for Dapr gRPC AppCallback.' + long_description = 'This is the developmental release for Dapr gRPC AppCallback.' + +print(f'package name: {name}, version: {version}', flush=True) + + +setup( + name=name, + version=version, + description=description, + long_description=long_description, +) diff --git a/ext/dapr-ext-grpc/tests/test_app.py b/ext/dapr-ext-grpc/tests/test_app.py index 2a33dd668..c7a1bdfbc 100644 --- a/ext/dapr-ext-grpc/tests/test_app.py +++ b/ext/dapr-ext-grpc/tests/test_app.py @@ -1,90 +1,90 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from cloudevents.sdk.event import v1 -from dapr.ext.grpc import App, Rule, InvokeMethodRequest, BindingRequest - - -class AppTests(unittest.TestCase): - def setUp(self): - self._app = App() - - def test_method_decorator(self): - @self._app.method('Method1') - def method1(request: InvokeMethodRequest): - pass - - @self._app.method('Method2') - def method2(request: InvokeMethodRequest): - pass - - method_map = self._app._servicer._invoke_method_map - self.assertIn('AppTests.test_method_decorator..method1', str(method_map['Method1'])) - self.assertIn('AppTests.test_method_decorator..method2', str(method_map['Method2'])) - - def test_binding_decorator(self): - @self._app.binding('binding1') - def binding1(request: BindingRequest): - pass - - binding_map = self._app._servicer._binding_map - self.assertIn( - 'AppTests.test_binding_decorator..binding1', str(binding_map['binding1']) - ) - - def test_subscribe_decorator(self): - @self._app.subscribe(pubsub_name='pubsub', topic='topic') - def handle_default(event: v1.Event) -> None: - pass - - @self._app.subscribe( - pubsub_name='pubsub', topic='topic', rule=Rule('event.type == "test"', 1) - ) - def handle_test_event(event: v1.Event) -> None: - pass - - @self._app.subscribe(pubsub_name='pubsub', topic='topic2', dead_letter_topic='topic2_dead') - def handle_dead_letter(event: v1.Event) -> None: - pass - - subscription_map = self._app._servicer._topic_map - self.assertIn( - 'AppTests.test_subscribe_decorator..handle_default', - str(subscription_map['pubsub:topic:']), - ) - self.assertIn( - 'AppTests.test_subscribe_decorator..handle_test_event', - str(subscription_map['pubsub:topic:handle_test_event']), - ) - self.assertIn( - 'AppTests.test_subscribe_decorator..handle_dead_letter', - str(subscription_map['pubsub:topic2:']), - ) - - def test_register_health_check(self): - def health_check_cb(): - pass - - self._app.register_health_check(health_check_cb) - registered_cb = self._app._health_check_servicer._health_check_cb - self.assertIn( - 'AppTests.test_register_health_check..health_check_cb', str(registered_cb) - ) - - def test_no_health_check(self): - registered_cb = self._app._health_check_servicer._health_check_cb - self.assertIsNone(registered_cb) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from cloudevents.sdk.event import v1 +from dapr.ext.grpc import App, Rule, InvokeMethodRequest, BindingRequest + + +class AppTests(unittest.TestCase): + def setUp(self): + self._app = App() + + def test_method_decorator(self): + @self._app.method('Method1') + def method1(request: InvokeMethodRequest): + pass + + @self._app.method('Method2') + def method2(request: InvokeMethodRequest): + pass + + method_map = self._app._servicer._invoke_method_map + self.assertIn('AppTests.test_method_decorator..method1', str(method_map['Method1'])) + self.assertIn('AppTests.test_method_decorator..method2', str(method_map['Method2'])) + + def test_binding_decorator(self): + @self._app.binding('binding1') + def binding1(request: BindingRequest): + pass + + binding_map = self._app._servicer._binding_map + self.assertIn( + 'AppTests.test_binding_decorator..binding1', str(binding_map['binding1']) + ) + + def test_subscribe_decorator(self): + @self._app.subscribe(pubsub_name='pubsub', topic='topic') + def handle_default(event: v1.Event) -> None: + pass + + @self._app.subscribe( + pubsub_name='pubsub', topic='topic', rule=Rule('event.type == "test"', 1) + ) + def handle_test_event(event: v1.Event) -> None: + pass + + @self._app.subscribe(pubsub_name='pubsub', topic='topic2', dead_letter_topic='topic2_dead') + def handle_dead_letter(event: v1.Event) -> None: + pass + + subscription_map = self._app._servicer._topic_map + self.assertIn( + 'AppTests.test_subscribe_decorator..handle_default', + str(subscription_map['pubsub:topic:']), + ) + self.assertIn( + 'AppTests.test_subscribe_decorator..handle_test_event', + str(subscription_map['pubsub:topic:handle_test_event']), + ) + self.assertIn( + 'AppTests.test_subscribe_decorator..handle_dead_letter', + str(subscription_map['pubsub:topic2:']), + ) + + def test_register_health_check(self): + def health_check_cb(): + pass + + self._app.register_health_check(health_check_cb) + registered_cb = self._app._health_check_servicer._health_check_cb + self.assertIn( + 'AppTests.test_register_health_check..health_check_cb', str(registered_cb) + ) + + def test_no_health_check(self): + registered_cb = self._app._health_check_servicer._health_check_cb + self.assertIsNone(registered_cb) diff --git a/ext/dapr-ext-grpc/tests/test_health_servicer.py b/ext/dapr-ext-grpc/tests/test_health_servicer.py index 4e19af46a..2726ff4fb 100644 --- a/ext/dapr-ext-grpc/tests/test_health_servicer.py +++ b/ext/dapr-ext-grpc/tests/test_health_servicer.py @@ -1,20 +1,20 @@ -import unittest -from unittest.mock import MagicMock - -from dapr.ext.grpc._health_servicer import _HealthCheckServicer - - -class OnInvokeTests(unittest.TestCase): - def setUp(self): - self._health_servicer = _HealthCheckServicer() - - def test_healthcheck_cb_called(self): - health_cb = MagicMock() - self._health_servicer.register_health_check(health_cb) - self._health_servicer.HealthCheck(None, MagicMock()) - health_cb.assert_called_once() - - def test_no_healthcheck_cb(self): - with self.assertRaises(NotImplementedError) as exception_context: - self._health_servicer.HealthCheck(None, MagicMock()) - self.assertIn('Method not implemented!', exception_context.exception.args[0]) +import unittest +from unittest.mock import MagicMock + +from dapr.ext.grpc._health_servicer import _HealthCheckServicer + + +class OnInvokeTests(unittest.TestCase): + def setUp(self): + self._health_servicer = _HealthCheckServicer() + + def test_healthcheck_cb_called(self): + health_cb = MagicMock() + self._health_servicer.register_health_check(health_cb) + self._health_servicer.HealthCheck(None, MagicMock()) + health_cb.assert_called_once() + + def test_no_healthcheck_cb(self): + with self.assertRaises(NotImplementedError) as exception_context: + self._health_servicer.HealthCheck(None, MagicMock()) + self.assertIn('Method not implemented!', exception_context.exception.args[0]) diff --git a/ext/dapr-ext-grpc/tests/test_servicier.py b/ext/dapr-ext-grpc/tests/test_servicier.py index 2447eea3c..2f703ff3d 100644 --- a/ext/dapr-ext-grpc/tests/test_servicier.py +++ b/ext/dapr-ext-grpc/tests/test_servicier.py @@ -1,229 +1,229 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from unittest.mock import MagicMock, Mock - -from dapr.clients.grpc._request import InvokeMethodRequest -from dapr.clients.grpc._response import InvokeMethodResponse, TopicEventResponse -from dapr.ext.grpc._servicer import _CallbackServicer -from dapr.proto import common_v1, appcallback_v1 - -from google.protobuf.any_pb2 import Any as GrpcAny - - -class OnInvokeTests(unittest.TestCase): - def setUp(self): - self._servicer = _CallbackServicer() - - def _on_invoke(self, method_name, method_cb): - self._servicer.register_method(method_name, method_cb) - - # fake context - fake_context = MagicMock() - fake_context.invocation_metadata.return_value = ( - ('key1', 'value1'), - ('key2', 'value1'), - ) - - return self._servicer.OnInvoke( - common_v1.InvokeRequest(method=method_name, data=GrpcAny()), - fake_context, - ) - - def test_on_invoke_return_str(self): - def method_cb(request: InvokeMethodRequest): - return 'method_str_cb' - - resp = self._on_invoke('method_str', method_cb) - - self.assertEqual(b'method_str_cb', resp.data.value) - - def test_on_invoke_return_bytes(self): - def method_cb(request: InvokeMethodRequest): - return b'method_str_cb' - - resp = self._on_invoke('method_bytes', method_cb) - - self.assertEqual(b'method_str_cb', resp.data.value) - - def test_on_invoke_return_proto(self): - def method_cb(request: InvokeMethodRequest): - return common_v1.StateItem(key='fake_key') - - resp = self._on_invoke('method_proto', method_cb) - - state = common_v1.StateItem() - resp.data.Unpack(state) - - self.assertEqual('fake_key', state.key) - - def test_on_invoke_return_invoke_method_response(self): - def method_cb(request: InvokeMethodRequest): - return InvokeMethodResponse( - data='fake_data', - content_type='text/plain', - ) - - resp = self._on_invoke('method_resp', method_cb) - - self.assertEqual(b'fake_data', resp.data.value) - self.assertEqual('text/plain', resp.content_type) - - def test_on_invoke_invalid_response(self): - def method_cb(request: InvokeMethodRequest): - return 1000 - - with self.assertRaises(NotImplementedError): - self._on_invoke('method_resp', method_cb) - - -class TopicSubscriptionTests(unittest.TestCase): - def setUp(self): - self._servicer = _CallbackServicer() - self._topic1_method = Mock() - self._topic2_method = Mock() - self._topic3_method = Mock() - self._topic3_method.return_value = TopicEventResponse('success') - self._topic4_method = Mock() - - self._servicer.register_topic('pubsub1', 'topic1', self._topic1_method, {'session': 'key'}) - self._servicer.register_topic('pubsub1', 'topic3', self._topic3_method, {'session': 'key'}) - self._servicer.register_topic('pubsub2', 'topic2', self._topic2_method, {'session': 'key'}) - self._servicer.register_topic('pubsub2', 'topic3', self._topic3_method, {'session': 'key'}) - self._servicer.register_topic( - 'pubsub3', - 'topic4', - self._topic4_method, - {'session': 'key'}, - disable_topic_validation=True, - ) - - # fake context - self.fake_context = MagicMock() - self.fake_context.invocation_metadata.return_value = ( - ('key1', 'value1'), - ('key2', 'value1'), - ) - - def test_duplicated_topic(self): - with self.assertRaises(ValueError): - self._servicer.register_topic( - 'pubsub1', 'topic1', self._topic1_method, {'session': 'key'} - ) - - def test_list_topic_subscription(self): - resp = self._servicer.ListTopicSubscriptions(None, None) - self.assertEqual('pubsub1', resp.subscriptions[0].pubsub_name) - self.assertEqual('topic1', resp.subscriptions[0].topic) - self.assertEqual({'session': 'key'}, resp.subscriptions[0].metadata) - self.assertEqual('pubsub1', resp.subscriptions[1].pubsub_name) - self.assertEqual('topic3', resp.subscriptions[1].topic) - self.assertEqual({'session': 'key'}, resp.subscriptions[1].metadata) - self.assertEqual('pubsub2', resp.subscriptions[2].pubsub_name) - self.assertEqual('topic2', resp.subscriptions[2].topic) - self.assertEqual({'session': 'key'}, resp.subscriptions[2].metadata) - self.assertEqual('pubsub2', resp.subscriptions[3].pubsub_name) - self.assertEqual('topic3', resp.subscriptions[3].topic) - self.assertEqual({'session': 'key'}, resp.subscriptions[3].metadata) - self.assertEqual('topic4', resp.subscriptions[4].topic) - self.assertEqual({'session': 'key'}, resp.subscriptions[4].metadata) - - def test_topic_event(self): - self._servicer.OnTopicEvent( - appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic1'), - self.fake_context, - ) - - self._topic1_method.assert_called_once() - - def test_topic3_event_called_once(self): - self._servicer.OnTopicEvent( - appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic3'), - self.fake_context, - ) - - self._topic3_method.assert_called_once() - - def test_topic3_event_response(self): - response = self._servicer.OnTopicEvent( - appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic3'), - self.fake_context, - ) - self.assertIsInstance(response, appcallback_v1.TopicEventResponse) - self.assertEqual( - response.status, appcallback_v1.TopicEventResponse.TopicEventResponseStatus.SUCCESS - ) - - def test_disable_topic_validation(self): - self._servicer.OnTopicEvent( - appcallback_v1.TopicEventRequest(pubsub_name='pubsub3', topic='should_be_ignored'), - self.fake_context, - ) - - self._topic4_method.assert_called_once() - - def test_non_registered_topic(self): - with self.assertRaises(NotImplementedError): - self._servicer.OnTopicEvent( - appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic_non_existed'), - self.fake_context, - ) - - -class BindingTests(unittest.TestCase): - def setUp(self): - self._servicer = _CallbackServicer() - self._binding1_method = Mock() - self._binding2_method = Mock() - - self._servicer.register_binding('binding1', self._binding1_method) - self._servicer.register_binding('binding2', self._binding2_method) - - # fake context - self.fake_context = MagicMock() - self.fake_context.invocation_metadata.return_value = ( - ('key1', 'value1'), - ('key2', 'value1'), - ) - - def test_duplicated_binding(self): - with self.assertRaises(ValueError): - self._servicer.register_binding('binding1', self._binding1_method) - - def test_list_bindings(self): - resp = self._servicer.ListInputBindings(None, None) - self.assertEqual('binding1', resp.bindings[0]) - self.assertEqual('binding2', resp.bindings[1]) - - def test_binding_event(self): - self._servicer.OnBindingEvent( - appcallback_v1.BindingEventRequest(name='binding1'), - self.fake_context, - ) - - self._binding1_method.assert_called_once() - - def test_non_registered_binding(self): - with self.assertRaises(NotImplementedError): - self._servicer.OnBindingEvent( - appcallback_v1.BindingEventRequest(name='binding3'), - self.fake_context, - ) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from unittest.mock import MagicMock, Mock + +from dapr.clients.grpc._request import InvokeMethodRequest +from dapr.clients.grpc._response import InvokeMethodResponse, TopicEventResponse +from dapr.ext.grpc._servicer import _CallbackServicer +from dapr.proto import common_v1, appcallback_v1 + +from google.protobuf.any_pb2 import Any as GrpcAny + + +class OnInvokeTests(unittest.TestCase): + def setUp(self): + self._servicer = _CallbackServicer() + + def _on_invoke(self, method_name, method_cb): + self._servicer.register_method(method_name, method_cb) + + # fake context + fake_context = MagicMock() + fake_context.invocation_metadata.return_value = ( + ('key1', 'value1'), + ('key2', 'value1'), + ) + + return self._servicer.OnInvoke( + common_v1.InvokeRequest(method=method_name, data=GrpcAny()), + fake_context, + ) + + def test_on_invoke_return_str(self): + def method_cb(request: InvokeMethodRequest): + return 'method_str_cb' + + resp = self._on_invoke('method_str', method_cb) + + self.assertEqual(b'method_str_cb', resp.data.value) + + def test_on_invoke_return_bytes(self): + def method_cb(request: InvokeMethodRequest): + return b'method_str_cb' + + resp = self._on_invoke('method_bytes', method_cb) + + self.assertEqual(b'method_str_cb', resp.data.value) + + def test_on_invoke_return_proto(self): + def method_cb(request: InvokeMethodRequest): + return common_v1.StateItem(key='fake_key') + + resp = self._on_invoke('method_proto', method_cb) + + state = common_v1.StateItem() + resp.data.Unpack(state) + + self.assertEqual('fake_key', state.key) + + def test_on_invoke_return_invoke_method_response(self): + def method_cb(request: InvokeMethodRequest): + return InvokeMethodResponse( + data='fake_data', + content_type='text/plain', + ) + + resp = self._on_invoke('method_resp', method_cb) + + self.assertEqual(b'fake_data', resp.data.value) + self.assertEqual('text/plain', resp.content_type) + + def test_on_invoke_invalid_response(self): + def method_cb(request: InvokeMethodRequest): + return 1000 + + with self.assertRaises(NotImplementedError): + self._on_invoke('method_resp', method_cb) + + +class TopicSubscriptionTests(unittest.TestCase): + def setUp(self): + self._servicer = _CallbackServicer() + self._topic1_method = Mock() + self._topic2_method = Mock() + self._topic3_method = Mock() + self._topic3_method.return_value = TopicEventResponse('success') + self._topic4_method = Mock() + + self._servicer.register_topic('pubsub1', 'topic1', self._topic1_method, {'session': 'key'}) + self._servicer.register_topic('pubsub1', 'topic3', self._topic3_method, {'session': 'key'}) + self._servicer.register_topic('pubsub2', 'topic2', self._topic2_method, {'session': 'key'}) + self._servicer.register_topic('pubsub2', 'topic3', self._topic3_method, {'session': 'key'}) + self._servicer.register_topic( + 'pubsub3', + 'topic4', + self._topic4_method, + {'session': 'key'}, + disable_topic_validation=True, + ) + + # fake context + self.fake_context = MagicMock() + self.fake_context.invocation_metadata.return_value = ( + ('key1', 'value1'), + ('key2', 'value1'), + ) + + def test_duplicated_topic(self): + with self.assertRaises(ValueError): + self._servicer.register_topic( + 'pubsub1', 'topic1', self._topic1_method, {'session': 'key'} + ) + + def test_list_topic_subscription(self): + resp = self._servicer.ListTopicSubscriptions(None, None) + self.assertEqual('pubsub1', resp.subscriptions[0].pubsub_name) + self.assertEqual('topic1', resp.subscriptions[0].topic) + self.assertEqual({'session': 'key'}, resp.subscriptions[0].metadata) + self.assertEqual('pubsub1', resp.subscriptions[1].pubsub_name) + self.assertEqual('topic3', resp.subscriptions[1].topic) + self.assertEqual({'session': 'key'}, resp.subscriptions[1].metadata) + self.assertEqual('pubsub2', resp.subscriptions[2].pubsub_name) + self.assertEqual('topic2', resp.subscriptions[2].topic) + self.assertEqual({'session': 'key'}, resp.subscriptions[2].metadata) + self.assertEqual('pubsub2', resp.subscriptions[3].pubsub_name) + self.assertEqual('topic3', resp.subscriptions[3].topic) + self.assertEqual({'session': 'key'}, resp.subscriptions[3].metadata) + self.assertEqual('topic4', resp.subscriptions[4].topic) + self.assertEqual({'session': 'key'}, resp.subscriptions[4].metadata) + + def test_topic_event(self): + self._servicer.OnTopicEvent( + appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic1'), + self.fake_context, + ) + + self._topic1_method.assert_called_once() + + def test_topic3_event_called_once(self): + self._servicer.OnTopicEvent( + appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic3'), + self.fake_context, + ) + + self._topic3_method.assert_called_once() + + def test_topic3_event_response(self): + response = self._servicer.OnTopicEvent( + appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic3'), + self.fake_context, + ) + self.assertIsInstance(response, appcallback_v1.TopicEventResponse) + self.assertEqual( + response.status, appcallback_v1.TopicEventResponse.TopicEventResponseStatus.SUCCESS + ) + + def test_disable_topic_validation(self): + self._servicer.OnTopicEvent( + appcallback_v1.TopicEventRequest(pubsub_name='pubsub3', topic='should_be_ignored'), + self.fake_context, + ) + + self._topic4_method.assert_called_once() + + def test_non_registered_topic(self): + with self.assertRaises(NotImplementedError): + self._servicer.OnTopicEvent( + appcallback_v1.TopicEventRequest(pubsub_name='pubsub1', topic='topic_non_existed'), + self.fake_context, + ) + + +class BindingTests(unittest.TestCase): + def setUp(self): + self._servicer = _CallbackServicer() + self._binding1_method = Mock() + self._binding2_method = Mock() + + self._servicer.register_binding('binding1', self._binding1_method) + self._servicer.register_binding('binding2', self._binding2_method) + + # fake context + self.fake_context = MagicMock() + self.fake_context.invocation_metadata.return_value = ( + ('key1', 'value1'), + ('key2', 'value1'), + ) + + def test_duplicated_binding(self): + with self.assertRaises(ValueError): + self._servicer.register_binding('binding1', self._binding1_method) + + def test_list_bindings(self): + resp = self._servicer.ListInputBindings(None, None) + self.assertEqual('binding1', resp.bindings[0]) + self.assertEqual('binding2', resp.bindings[1]) + + def test_binding_event(self): + self._servicer.OnBindingEvent( + appcallback_v1.BindingEventRequest(name='binding1'), + self.fake_context, + ) + + self._binding1_method.assert_called_once() + + def test_non_registered_binding(self): + with self.assertRaises(NotImplementedError): + self._servicer.OnBindingEvent( + appcallback_v1.BindingEventRequest(name='binding3'), + self.fake_context, + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/ext/dapr-ext-workflow/LICENSE b/ext/dapr-ext-workflow/LICENSE index be033a7fd..da1ba6b93 100644 --- a/ext/dapr-ext-workflow/LICENSE +++ b/ext/dapr-ext-workflow/LICENSE @@ -1,203 +1,203 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 The Dapr Authors. - - and others that have contributed code to the public domain. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Dapr Authors. + + and others that have contributed code to the public domain. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/ext/dapr-ext-workflow/README.rst b/ext/dapr-ext-workflow/README.rst index aa0003c6e..528e934b7 100644 --- a/ext/dapr-ext-workflow/README.rst +++ b/ext/dapr-ext-workflow/README.rst @@ -1,23 +1,23 @@ -dapr-ext-workflow extension -=========================== - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/dapr-ext-workflow.svg - :target: https://pypi.org/project/dapr-ext-workflow/ - -This is the workflow authoring extension for Dapr Workflow - - -Installation ------------- - -:: - - pip install dapr-ext-workflow - -References ----------- - -* `Dapr `_ -* `Dapr Python-SDK `_ +dapr-ext-workflow extension +=========================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/dapr-ext-workflow.svg + :target: https://pypi.org/project/dapr-ext-workflow/ + +This is the workflow authoring extension for Dapr Workflow + + +Installation +------------ + +:: + + pip install dapr-ext-workflow + +References +---------- + +* `Dapr `_ +* `Dapr Python-SDK `_ diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/__init__.py b/ext/dapr-ext-workflow/dapr/ext/workflow/__init__.py index f78615112..e2271da01 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/__init__.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/__init__.py @@ -1,35 +1,35 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -# Import your main classes here -from dapr.ext.workflow.workflow_runtime import WorkflowRuntime, alternate_name -from dapr.ext.workflow.dapr_workflow_client import DaprWorkflowClient -from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext, when_all, when_any -from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext -from dapr.ext.workflow.workflow_state import WorkflowState, WorkflowStatus -from dapr.ext.workflow.retry_policy import RetryPolicy - -__all__ = [ - 'WorkflowRuntime', - 'DaprWorkflowClient', - 'DaprWorkflowContext', - 'WorkflowActivityContext', - 'WorkflowState', - 'WorkflowStatus', - 'when_all', - 'when_any', - 'alternate_name', - 'RetryPolicy', -] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +# Import your main classes here +from dapr.ext.workflow.workflow_runtime import WorkflowRuntime, alternate_name +from dapr.ext.workflow.dapr_workflow_client import DaprWorkflowClient +from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext, when_all, when_any +from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext +from dapr.ext.workflow.workflow_state import WorkflowState, WorkflowStatus +from dapr.ext.workflow.retry_policy import RetryPolicy + +__all__ = [ + 'WorkflowRuntime', + 'DaprWorkflowClient', + 'DaprWorkflowContext', + 'WorkflowActivityContext', + 'WorkflowState', + 'WorkflowStatus', + 'when_all', + 'when_any', + 'alternate_name', + 'RetryPolicy', +] diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_client.py b/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_client.py index 19f49981c..4b9240229 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_client.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_client.py @@ -1,248 +1,248 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from __future__ import annotations -from datetime import datetime -from typing import Any, Optional, TypeVar - -from durabletask import client - -from dapr.ext.workflow.workflow_state import WorkflowState -from dapr.ext.workflow.workflow_context import Workflow -from dapr.ext.workflow.util import getAddress - -from dapr.clients import DaprInternalError -from dapr.clients.http.client import DAPR_API_TOKEN_HEADER -from dapr.conf import settings -from dapr.conf.helpers import GrpcEndpoint -from dapr.ext.workflow.logger import LoggerOptions, Logger - -T = TypeVar('T') -TInput = TypeVar('TInput') -TOutput = TypeVar('TOutput') - - -class DaprWorkflowClient: - """Defines client operations for managing Dapr Workflow instances. - - This is an alternative to the general purpose Dapr client. It uses a gRPC connection to send - commands directly to the workflow engine, bypassing the Dapr API layer. - - This client is intended to be used by workflow application, not by general purpose - application. - """ - - def __init__( - self, - host: Optional[str] = None, - port: Optional[str] = None, - logger_options: Optional[LoggerOptions] = None, - ): - address = getAddress(host, port) - - try: - uri = GrpcEndpoint(address) - except ValueError as error: - raise DaprInternalError(f'{error}') from error - - self._logger = Logger('DaprWorkflowClient', logger_options) - - metadata = tuple() - if settings.DAPR_API_TOKEN: - metadata = ((DAPR_API_TOKEN_HEADER, settings.DAPR_API_TOKEN),) - options = self._logger.get_options() - self.__obj = client.TaskHubGrpcClient( - host_address=uri.endpoint, - metadata=metadata, - secure_channel=uri.tls, - log_handler=options.log_handler, - log_formatter=options.log_formatter, - ) - - def schedule_new_workflow( - self, - workflow: Workflow, - *, - input: Optional[TInput] = None, - instance_id: Optional[str] = None, - start_at: Optional[datetime] = None, - ) -> str: - """Schedules a new workflow instance for execution. - - Args: - workflow: The workflow to schedule. - input: The optional input to pass to the scheduled workflow instance. This must be a - serializable value. - instance_id: The unique ID of the workflow instance to schedule. If not specified, a - new GUID value is used. - start_at: The time when the workflow instance should start executing. - If not specified or if a date-time in the past is specified, the workflow instance will - be scheduled immediately. - - Returns: - The ID of the scheduled workflow instance. - """ - if hasattr(workflow, '_dapr_alternate_name'): - return self.__obj.schedule_new_orchestration( - workflow.__dict__['_dapr_alternate_name'], - input=input, - instance_id=instance_id, - start_at=start_at, - ) - return self.__obj.schedule_new_orchestration( - workflow.__name__, input=input, instance_id=instance_id, start_at=start_at - ) - - def get_workflow_state( - self, instance_id: str, *, fetch_payloads: bool = True - ) -> Optional[WorkflowState]: - """Fetches runtime state for the specified workflow instance. - - Args: - instanceId: The unique ID of the workflow instance to fetch. - fetch_payloads: If true, fetches the input, output payloads and custom status - for the workflow instance. Defaults to false. - - Returns: - The current state of the workflow instance, or None if the workflow instance does not - exist. - - """ - state = self.__obj.get_orchestration_state(instance_id, fetch_payloads=fetch_payloads) - return WorkflowState(state) if state else None - - def wait_for_workflow_start( - self, instance_id: str, *, fetch_payloads: bool = False, timeout_in_seconds: int = 60 - ) -> Optional[WorkflowState]: - """Waits for a workflow to start running and returns a WorkflowState object that contains - metadata about the started workflow. - - A "started" workflow instance is any instance not in the WorkflowRuntimeStatus.Pending - state. This method will return a completed task if the workflow has already started - running or has already completed. - - Args: - instance_id: The unique ID of the workflow instance to wait for. - fetch_payloads: If true, fetches the input, output payloads and custom status for - the workflow instance. Defaults to false. - timeout_in_seconds: The maximum time to wait for the workflow instance to start running. - Defaults to 60 seconds. - - Returns: - WorkflowState record that describes the workflow instance and its execution status. - If the specified workflow isn't found, the WorkflowState.Exists value will be false. - """ - state = self.__obj.wait_for_orchestration_start( - instance_id, fetch_payloads=fetch_payloads, timeout=timeout_in_seconds - ) - return WorkflowState(state) if state else None - - def wait_for_workflow_completion( - self, instance_id: str, *, fetch_payloads: bool = True, timeout_in_seconds: int = 60 - ) -> Optional[WorkflowState]: - """Waits for a workflow to complete and returns a WorkflowState object that contains - metadata about the started instance. - - A "completed" workflow instance is any instance in one of the terminal states. For - example, the WorkflowRuntimeStatus.Completed, WorkflowRuntimeStatus.Failed or - WorkflowRuntimeStatus.Terminated states. - - Workflows are long-running and could take hours, days, or months before completing. - Workflows can also be eternal, in which case they'll never complete unless terminated. - In such cases, this call may block indefinitely, so care must be taken to ensure - appropriate timeouts are enforced using timeout parameter. - - If a workflow instance is already complete when this method is called, the method - will return immediately. - - Args: - instance_id: The unique ID of the workflow instance to wait for. - fetch_payloads: If true, fetches the input, output payloads and custom status - for the workflow instance. Defaults to true. - timeout_in_seconds: The maximum time in seconds to wait for the workflow instance to - complete. Defaults to 60 seconds. - - Returns: - WorkflowState record that describes the workflow instance and its execution status. - """ - state = self.__obj.wait_for_orchestration_completion( - instance_id, fetch_payloads=fetch_payloads, timeout=timeout_in_seconds - ) - return WorkflowState(state) if state else None - - def raise_workflow_event( - self, instance_id: str, event_name: str, *, data: Optional[Any] = None - ): - """Sends an event notification message to a waiting workflow instance. - In order to handle the event, the target workflow instance must be waiting for an - event named value of "eventName" param using the wait_for_external_event API. - If the target workflow instance is not yet waiting for an event named param "eventName" - value, then the event will be saved in the workflow instance state and dispatched - immediately when the workflow calls wait_for_external_event. - This event saving occurs even if the workflow has canceled its wait operation before - the event was received. - - Workflows can wait for the same event name multiple times, so sending multiple events - with the same name is allowed. Each external event received by a workflow will complete - just one task returned by the wait_for_external_event method. - - Raised events for a completed or non-existent workflow instance will be silently - discarded. - - Args: - instanceId: The ID of the workflow instance that will handle the event. - eventName: The name of the event. Event names are case-insensitive. - data: The serializable data payload to include with the event. - """ - return self.__obj.raise_orchestration_event(instance_id, event_name, data=data) - - def terminate_workflow(self, instance_id: str, *, output: Optional[Any] = None): - """Terminates a running workflow instance and updates its runtime status to - WorkflowRuntimeStatus.Terminated This method internally enqueues a "terminate" message in - the task hub. When the task hub worker processes this message, it will update the runtime - status of the target instance to WorkflowRuntimeStatus.Terminated. You can use - wait_for_workflow_completion to wait for the instance to reach the terminated state. - - Terminating a workflow will terminate all child workflows that were started by - the workflow instance. - - However, terminating a workflow has no effect on any in-flight activity function - executions that were started by the terminated workflow instance. - - At the time of writing, there is no way to terminate an in-flight activity execution. - - Args: - instance_id: The ID of the workflow instance to terminate. - output: The optional output to set for the terminated workflow instance. - - """ - return self.__obj.terminate_orchestration(instance_id, output=output) - - def pause_workflow(self, instance_id: str): - """Suspends a workflow instance, halting processing of it until resume_workflow is used to - resume the workflow. - - Args: - instance_id: The instance ID of the workflow to suspend. - """ - return self.__obj.suspend_orchestration(instance_id) - - def resume_workflow(self, instance_id: str): - """Resumes a workflow instance that was suspended via pause_workflow. - - Args: - instance_id: The instance ID of the workflow to resume. - """ - return self.__obj.resume_orchestration(instance_id) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from __future__ import annotations +from datetime import datetime +from typing import Any, Optional, TypeVar + +from durabletask import client + +from dapr.ext.workflow.workflow_state import WorkflowState +from dapr.ext.workflow.workflow_context import Workflow +from dapr.ext.workflow.util import getAddress + +from dapr.clients import DaprInternalError +from dapr.clients.http.client import DAPR_API_TOKEN_HEADER +from dapr.conf import settings +from dapr.conf.helpers import GrpcEndpoint +from dapr.ext.workflow.logger import LoggerOptions, Logger + +T = TypeVar('T') +TInput = TypeVar('TInput') +TOutput = TypeVar('TOutput') + + +class DaprWorkflowClient: + """Defines client operations for managing Dapr Workflow instances. + + This is an alternative to the general purpose Dapr client. It uses a gRPC connection to send + commands directly to the workflow engine, bypassing the Dapr API layer. + + This client is intended to be used by workflow application, not by general purpose + application. + """ + + def __init__( + self, + host: Optional[str] = None, + port: Optional[str] = None, + logger_options: Optional[LoggerOptions] = None, + ): + address = getAddress(host, port) + + try: + uri = GrpcEndpoint(address) + except ValueError as error: + raise DaprInternalError(f'{error}') from error + + self._logger = Logger('DaprWorkflowClient', logger_options) + + metadata = tuple() + if settings.DAPR_API_TOKEN: + metadata = ((DAPR_API_TOKEN_HEADER, settings.DAPR_API_TOKEN),) + options = self._logger.get_options() + self.__obj = client.TaskHubGrpcClient( + host_address=uri.endpoint, + metadata=metadata, + secure_channel=uri.tls, + log_handler=options.log_handler, + log_formatter=options.log_formatter, + ) + + def schedule_new_workflow( + self, + workflow: Workflow, + *, + input: Optional[TInput] = None, + instance_id: Optional[str] = None, + start_at: Optional[datetime] = None, + ) -> str: + """Schedules a new workflow instance for execution. + + Args: + workflow: The workflow to schedule. + input: The optional input to pass to the scheduled workflow instance. This must be a + serializable value. + instance_id: The unique ID of the workflow instance to schedule. If not specified, a + new GUID value is used. + start_at: The time when the workflow instance should start executing. + If not specified or if a date-time in the past is specified, the workflow instance will + be scheduled immediately. + + Returns: + The ID of the scheduled workflow instance. + """ + if hasattr(workflow, '_dapr_alternate_name'): + return self.__obj.schedule_new_orchestration( + workflow.__dict__['_dapr_alternate_name'], + input=input, + instance_id=instance_id, + start_at=start_at, + ) + return self.__obj.schedule_new_orchestration( + workflow.__name__, input=input, instance_id=instance_id, start_at=start_at + ) + + def get_workflow_state( + self, instance_id: str, *, fetch_payloads: bool = True + ) -> Optional[WorkflowState]: + """Fetches runtime state for the specified workflow instance. + + Args: + instanceId: The unique ID of the workflow instance to fetch. + fetch_payloads: If true, fetches the input, output payloads and custom status + for the workflow instance. Defaults to false. + + Returns: + The current state of the workflow instance, or None if the workflow instance does not + exist. + + """ + state = self.__obj.get_orchestration_state(instance_id, fetch_payloads=fetch_payloads) + return WorkflowState(state) if state else None + + def wait_for_workflow_start( + self, instance_id: str, *, fetch_payloads: bool = False, timeout_in_seconds: int = 60 + ) -> Optional[WorkflowState]: + """Waits for a workflow to start running and returns a WorkflowState object that contains + metadata about the started workflow. + + A "started" workflow instance is any instance not in the WorkflowRuntimeStatus.Pending + state. This method will return a completed task if the workflow has already started + running or has already completed. + + Args: + instance_id: The unique ID of the workflow instance to wait for. + fetch_payloads: If true, fetches the input, output payloads and custom status for + the workflow instance. Defaults to false. + timeout_in_seconds: The maximum time to wait for the workflow instance to start running. + Defaults to 60 seconds. + + Returns: + WorkflowState record that describes the workflow instance and its execution status. + If the specified workflow isn't found, the WorkflowState.Exists value will be false. + """ + state = self.__obj.wait_for_orchestration_start( + instance_id, fetch_payloads=fetch_payloads, timeout=timeout_in_seconds + ) + return WorkflowState(state) if state else None + + def wait_for_workflow_completion( + self, instance_id: str, *, fetch_payloads: bool = True, timeout_in_seconds: int = 60 + ) -> Optional[WorkflowState]: + """Waits for a workflow to complete and returns a WorkflowState object that contains + metadata about the started instance. + + A "completed" workflow instance is any instance in one of the terminal states. For + example, the WorkflowRuntimeStatus.Completed, WorkflowRuntimeStatus.Failed or + WorkflowRuntimeStatus.Terminated states. + + Workflows are long-running and could take hours, days, or months before completing. + Workflows can also be eternal, in which case they'll never complete unless terminated. + In such cases, this call may block indefinitely, so care must be taken to ensure + appropriate timeouts are enforced using timeout parameter. + + If a workflow instance is already complete when this method is called, the method + will return immediately. + + Args: + instance_id: The unique ID of the workflow instance to wait for. + fetch_payloads: If true, fetches the input, output payloads and custom status + for the workflow instance. Defaults to true. + timeout_in_seconds: The maximum time in seconds to wait for the workflow instance to + complete. Defaults to 60 seconds. + + Returns: + WorkflowState record that describes the workflow instance and its execution status. + """ + state = self.__obj.wait_for_orchestration_completion( + instance_id, fetch_payloads=fetch_payloads, timeout=timeout_in_seconds + ) + return WorkflowState(state) if state else None + + def raise_workflow_event( + self, instance_id: str, event_name: str, *, data: Optional[Any] = None + ): + """Sends an event notification message to a waiting workflow instance. + In order to handle the event, the target workflow instance must be waiting for an + event named value of "eventName" param using the wait_for_external_event API. + If the target workflow instance is not yet waiting for an event named param "eventName" + value, then the event will be saved in the workflow instance state and dispatched + immediately when the workflow calls wait_for_external_event. + This event saving occurs even if the workflow has canceled its wait operation before + the event was received. + + Workflows can wait for the same event name multiple times, so sending multiple events + with the same name is allowed. Each external event received by a workflow will complete + just one task returned by the wait_for_external_event method. + + Raised events for a completed or non-existent workflow instance will be silently + discarded. + + Args: + instanceId: The ID of the workflow instance that will handle the event. + eventName: The name of the event. Event names are case-insensitive. + data: The serializable data payload to include with the event. + """ + return self.__obj.raise_orchestration_event(instance_id, event_name, data=data) + + def terminate_workflow(self, instance_id: str, *, output: Optional[Any] = None): + """Terminates a running workflow instance and updates its runtime status to + WorkflowRuntimeStatus.Terminated This method internally enqueues a "terminate" message in + the task hub. When the task hub worker processes this message, it will update the runtime + status of the target instance to WorkflowRuntimeStatus.Terminated. You can use + wait_for_workflow_completion to wait for the instance to reach the terminated state. + + Terminating a workflow will terminate all child workflows that were started by + the workflow instance. + + However, terminating a workflow has no effect on any in-flight activity function + executions that were started by the terminated workflow instance. + + At the time of writing, there is no way to terminate an in-flight activity execution. + + Args: + instance_id: The ID of the workflow instance to terminate. + output: The optional output to set for the terminated workflow instance. + + """ + return self.__obj.terminate_orchestration(instance_id, output=output) + + def pause_workflow(self, instance_id: str): + """Suspends a workflow instance, halting processing of it until resume_workflow is used to + resume the workflow. + + Args: + instance_id: The instance ID of the workflow to suspend. + """ + return self.__obj.suspend_orchestration(instance_id) + + def resume_workflow(self, instance_id: str): + """Resumes a workflow instance that was suspended via pause_workflow. + + Args: + instance_id: The instance ID of the workflow to resume. + """ + return self.__obj.resume_orchestration(instance_id) diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py b/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py index dbcccd64a..37a44f3da 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/dapr_workflow_context.py @@ -1,121 +1,121 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Any, Callable, List, Optional, TypeVar, Union -from datetime import datetime, timedelta - -from durabletask import task - -from dapr.ext.workflow.workflow_context import WorkflowContext, Workflow -from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext -from dapr.ext.workflow.logger import LoggerOptions, Logger -from dapr.ext.workflow.retry_policy import RetryPolicy - -T = TypeVar('T') -TInput = TypeVar('TInput') -TOutput = TypeVar('TOutput') - - -class DaprWorkflowContext(WorkflowContext): - """DaprWorkflowContext that provides proxy access to internal OrchestrationContext instance.""" - - def __init__( - self, ctx: task.OrchestrationContext, logger_options: Optional[LoggerOptions] = None - ): - self.__obj = ctx - self._logger = Logger('DaprWorkflowContext', logger_options) - - # provide proxy access to regular attributes of wrapped object - def __getattr__(self, name): - return getattr(self.__obj, name) - - @property - def instance_id(self) -> str: - return self.__obj.instance_id - - @property - def current_utc_datetime(self) -> datetime: - return self.__obj.current_utc_datetime - - @property - def is_replaying(self) -> bool: - return self.__obj.is_replaying - - def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task: - self._logger.debug(f'{self.instance_id}: Creating timer to fire at {fire_at} time') - return self.__obj.create_timer(fire_at) - - def call_activity( - self, - activity: Callable[[WorkflowActivityContext, TInput], TOutput], - *, - input: TInput = None, - retry_policy: Optional[RetryPolicy] = None, - ) -> task.Task[TOutput]: - self._logger.debug(f'{self.instance_id}: Creating activity {activity.__name__}') - if hasattr(activity, '_dapr_alternate_name'): - act = activity.__dict__['_dapr_alternate_name'] - else: - # this case should ideally never happen - act = activity.__name__ - if retry_policy is None: - return self.__obj.call_activity(activity=act, input=input) - return self.__obj.call_activity(activity=act, input=input, retry_policy=retry_policy.obj) - - def call_child_workflow( - self, - workflow: Workflow, - *, - input: Optional[TInput] = None, - instance_id: Optional[str] = None, - retry_policy: Optional[RetryPolicy] = None, - ) -> task.Task[TOutput]: - self._logger.debug(f'{self.instance_id}: Creating child workflow {workflow.__name__}') - - def wf(ctx: task.OrchestrationContext, inp: TInput): - daprWfContext = DaprWorkflowContext(ctx, self._logger.get_options()) - return workflow(daprWfContext, inp) - - # copy workflow name so durabletask.worker can find the orchestrator in its registry - - if hasattr(workflow, '_dapr_alternate_name'): - wf.__name__ = workflow.__dict__['_dapr_alternate_name'] - else: - # this case should ideally never happen - wf.__name__ = workflow.__name__ - if retry_policy is None: - return self.__obj.call_sub_orchestrator(wf, input=input, instance_id=instance_id) - return self.__obj.call_sub_orchestrator( - wf, input=input, instance_id=instance_id, retry_policy=retry_policy.obj - ) - - def wait_for_external_event(self, name: str) -> task.Task: - self._logger.debug(f'{self.instance_id}: Waiting for external event {name}') - return self.__obj.wait_for_external_event(name) - - def continue_as_new(self, new_input: Any, *, save_events: bool = False) -> None: - self._logger.debug(f'{self.instance_id}: Continuing as new') - self.__obj.continue_as_new(new_input, save_events=save_events) - - -def when_all(tasks: List[task.Task[T]]) -> task.WhenAllTask[T]: - """Returns a task that completes when all of the provided tasks complete or when one of the - tasks fail.""" - return task.when_all(tasks) - - -def when_any(tasks: List[task.Task]) -> task.WhenAnyTask: - """Returns a task that completes when any of the provided tasks complete or fail.""" - return task.when_any(tasks) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Any, Callable, List, Optional, TypeVar, Union +from datetime import datetime, timedelta + +from durabletask import task + +from dapr.ext.workflow.workflow_context import WorkflowContext, Workflow +from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext +from dapr.ext.workflow.logger import LoggerOptions, Logger +from dapr.ext.workflow.retry_policy import RetryPolicy + +T = TypeVar('T') +TInput = TypeVar('TInput') +TOutput = TypeVar('TOutput') + + +class DaprWorkflowContext(WorkflowContext): + """DaprWorkflowContext that provides proxy access to internal OrchestrationContext instance.""" + + def __init__( + self, ctx: task.OrchestrationContext, logger_options: Optional[LoggerOptions] = None + ): + self.__obj = ctx + self._logger = Logger('DaprWorkflowContext', logger_options) + + # provide proxy access to regular attributes of wrapped object + def __getattr__(self, name): + return getattr(self.__obj, name) + + @property + def instance_id(self) -> str: + return self.__obj.instance_id + + @property + def current_utc_datetime(self) -> datetime: + return self.__obj.current_utc_datetime + + @property + def is_replaying(self) -> bool: + return self.__obj.is_replaying + + def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task: + self._logger.debug(f'{self.instance_id}: Creating timer to fire at {fire_at} time') + return self.__obj.create_timer(fire_at) + + def call_activity( + self, + activity: Callable[[WorkflowActivityContext, TInput], TOutput], + *, + input: TInput = None, + retry_policy: Optional[RetryPolicy] = None, + ) -> task.Task[TOutput]: + self._logger.debug(f'{self.instance_id}: Creating activity {activity.__name__}') + if hasattr(activity, '_dapr_alternate_name'): + act = activity.__dict__['_dapr_alternate_name'] + else: + # this case should ideally never happen + act = activity.__name__ + if retry_policy is None: + return self.__obj.call_activity(activity=act, input=input) + return self.__obj.call_activity(activity=act, input=input, retry_policy=retry_policy.obj) + + def call_child_workflow( + self, + workflow: Workflow, + *, + input: Optional[TInput] = None, + instance_id: Optional[str] = None, + retry_policy: Optional[RetryPolicy] = None, + ) -> task.Task[TOutput]: + self._logger.debug(f'{self.instance_id}: Creating child workflow {workflow.__name__}') + + def wf(ctx: task.OrchestrationContext, inp: TInput): + daprWfContext = DaprWorkflowContext(ctx, self._logger.get_options()) + return workflow(daprWfContext, inp) + + # copy workflow name so durabletask.worker can find the orchestrator in its registry + + if hasattr(workflow, '_dapr_alternate_name'): + wf.__name__ = workflow.__dict__['_dapr_alternate_name'] + else: + # this case should ideally never happen + wf.__name__ = workflow.__name__ + if retry_policy is None: + return self.__obj.call_sub_orchestrator(wf, input=input, instance_id=instance_id) + return self.__obj.call_sub_orchestrator( + wf, input=input, instance_id=instance_id, retry_policy=retry_policy.obj + ) + + def wait_for_external_event(self, name: str) -> task.Task: + self._logger.debug(f'{self.instance_id}: Waiting for external event {name}') + return self.__obj.wait_for_external_event(name) + + def continue_as_new(self, new_input: Any, *, save_events: bool = False) -> None: + self._logger.debug(f'{self.instance_id}: Continuing as new') + self.__obj.continue_as_new(new_input, save_events=save_events) + + +def when_all(tasks: List[task.Task[T]]) -> task.WhenAllTask[T]: + """Returns a task that completes when all of the provided tasks complete or when one of the + tasks fail.""" + return task.when_all(tasks) + + +def when_any(tasks: List[task.Task]) -> task.WhenAnyTask: + """Returns a task that completes when any of the provided tasks complete or fail.""" + return task.when_any(tasks) diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/logger/__init__.py b/ext/dapr-ext-workflow/dapr/ext/workflow/logger/__init__.py index 5583bde7e..267295547 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/logger/__init__.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/logger/__init__.py @@ -1,4 +1,4 @@ -from dapr.ext.workflow.logger.options import LoggerOptions -from dapr.ext.workflow.logger.logger import Logger - -__all__ = ['LoggerOptions', 'Logger'] +from dapr.ext.workflow.logger.options import LoggerOptions +from dapr.ext.workflow.logger.logger import Logger + +__all__ = ['LoggerOptions', 'Logger'] diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/logger/logger.py b/ext/dapr-ext-workflow/dapr/ext/workflow/logger/logger.py index 6b0f3fec4..d4b1221a9 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/logger/logger.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/logger/logger.py @@ -1,35 +1,35 @@ -import logging -from typing import Union -from dapr.ext.workflow.logger.options import LoggerOptions - - -class Logger: - def __init__(self, name: str, options: Union[LoggerOptions, None] = None): - # If options is None, then create a new LoggerOptions object - if options is None: - options = LoggerOptions() - log_handler = options.log_handler - log_handler.setLevel(options.log_level) - log_handler.setFormatter(options.log_formatter) - logger = logging.getLogger(name) - logger.handlers.append(log_handler) - self._logger_options = options - self._logger = logger - - def get_options(self) -> LoggerOptions: - return self._logger_options - - def debug(self, msg, *args, **kwargs): - self._logger.debug(msg, *args, **kwargs) - - def info(self, msg, *args, **kwargs): - self._logger.info(msg, *args, **kwargs) - - def warning(self, msg, *args, **kwargs): - self._logger.warning(msg, *args, **kwargs) - - def error(self, msg, *args, **kwargs): - self._logger.error(msg, *args, **kwargs) - - def critical(self, msg, *args, **kwargs): - self._logger.critical(msg, *args, **kwargs) +import logging +from typing import Union +from dapr.ext.workflow.logger.options import LoggerOptions + + +class Logger: + def __init__(self, name: str, options: Union[LoggerOptions, None] = None): + # If options is None, then create a new LoggerOptions object + if options is None: + options = LoggerOptions() + log_handler = options.log_handler + log_handler.setLevel(options.log_level) + log_handler.setFormatter(options.log_formatter) + logger = logging.getLogger(name) + logger.handlers.append(log_handler) + self._logger_options = options + self._logger = logger + + def get_options(self) -> LoggerOptions: + return self._logger_options + + def debug(self, msg, *args, **kwargs): + self._logger.debug(msg, *args, **kwargs) + + def info(self, msg, *args, **kwargs): + self._logger.info(msg, *args, **kwargs) + + def warning(self, msg, *args, **kwargs): + self._logger.warning(msg, *args, **kwargs) + + def error(self, msg, *args, **kwargs): + self._logger.error(msg, *args, **kwargs) + + def critical(self, msg, *args, **kwargs): + self._logger.critical(msg, *args, **kwargs) diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/logger/options.py b/ext/dapr-ext-workflow/dapr/ext/workflow/logger/options.py index 0be44c52b..7d071c0b9 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/logger/options.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/logger/options.py @@ -1,41 +1,41 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Union -import logging - - -class LoggerOptions: - def __init__( - self, - log_level: Union[str, None] = None, - log_handler: Union[logging.Handler, None] = None, - log_formatter: Union[logging.Formatter, None] = None, - ): - # Set default log level to INFO if none is provided - if log_level is None: - log_level = logging.INFO - # Add a default log handler if none is provided - if log_handler is None: - log_handler = logging.StreamHandler() - # Set a default log formatter if none is provided - if log_formatter is None: - log_formatter = logging.Formatter( - fmt='%(asctime)s.%(msecs)03d %(name)s %(levelname)s: %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - ) - self.log_level = log_level - self.log_handler = log_handler - self.log_formatter = log_formatter +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Union +import logging + + +class LoggerOptions: + def __init__( + self, + log_level: Union[str, None] = None, + log_handler: Union[logging.Handler, None] = None, + log_formatter: Union[logging.Formatter, None] = None, + ): + # Set default log level to INFO if none is provided + if log_level is None: + log_level = logging.INFO + # Add a default log handler if none is provided + if log_handler is None: + log_handler = logging.StreamHandler() + # Set a default log formatter if none is provided + if log_formatter is None: + log_formatter = logging.Formatter( + fmt='%(asctime)s.%(msecs)03d %(name)s %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + ) + self.log_level = log_level + self.log_handler = log_handler + self.log_formatter = log_formatter diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/retry_policy.py b/ext/dapr-ext-workflow/dapr/ext/workflow/retry_policy.py index af1f5ea9e..67ee3912b 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/retry_policy.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/retry_policy.py @@ -1,98 +1,98 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Optional, TypeVar -from datetime import timedelta - -from durabletask import task - -T = TypeVar('T') -TInput = TypeVar('TInput') -TOutput = TypeVar('TOutput') - - -class RetryPolicy: - """Represents the retry policy for a workflow or activity function.""" - - def __init__( - self, - *, - first_retry_interval: timedelta, - max_number_of_attempts: int, - backoff_coefficient: Optional[float] = 1.0, - max_retry_interval: Optional[timedelta] = None, - retry_timeout: Optional[timedelta] = None, - ): - """Creates a new RetryPolicy instance. - - Args: - first_retry_interval(timedelta): The retry interval to use for the first retry attempt. - max_number_of_attempts(int): The maximum number of retry attempts. - backoff_coefficient(Optional[float]): The backoff coefficient to use for calculating - the next retry interval. - max_retry_interval(Optional[timedelta]): The maximum retry interval to use for any - retry attempt. - retry_timeout(Optional[timedelta]): The maximum amount of time to spend retrying the - operation. - """ - # validate inputs - if first_retry_interval < timedelta(seconds=0): - raise ValueError('first_retry_interval must be >= 0') - if max_number_of_attempts < 1: - raise ValueError('max_number_of_attempts must be >= 1') - if backoff_coefficient is not None and backoff_coefficient < 1: - raise ValueError('backoff_coefficient must be >= 1') - if max_retry_interval is not None and max_retry_interval < timedelta(seconds=0): - raise ValueError('max_retry_interval must be >= 0') - if retry_timeout is not None and retry_timeout < timedelta(seconds=0): - raise ValueError('retry_timeout must be >= 0') - - self._obj = task.RetryPolicy( - first_retry_interval=first_retry_interval, - max_number_of_attempts=max_number_of_attempts, - backoff_coefficient=backoff_coefficient, - max_retry_interval=max_retry_interval, - retry_timeout=retry_timeout, - ) - - @property - def obj(self) -> task.RetryPolicy: - """Returns the underlying RetryPolicy object.""" - return self._obj - - @property - def first_retry_interval(self) -> timedelta: - """The retry interval to use for the first retry attempt.""" - return self._obj._first_retry_interval - - @property - def max_number_of_attempts(self) -> int: - """The maximum number of retry attempts.""" - return self._obj._max_number_of_attempts - - @property - def backoff_coefficient(self) -> Optional[float]: - """The backoff coefficient to use for calculating the next retry interval.""" - return self._obj._backoff_coefficient - - @property - def max_retry_interval(self) -> Optional[timedelta]: - """The maximum retry interval to use for any retry attempt.""" - return self._obj._max_retry_interval - - @property - def retry_timeout(self) -> Optional[timedelta]: - """The maximum amount of time to spend retrying the operation.""" - return self._obj._retry_timeout +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Optional, TypeVar +from datetime import timedelta + +from durabletask import task + +T = TypeVar('T') +TInput = TypeVar('TInput') +TOutput = TypeVar('TOutput') + + +class RetryPolicy: + """Represents the retry policy for a workflow or activity function.""" + + def __init__( + self, + *, + first_retry_interval: timedelta, + max_number_of_attempts: int, + backoff_coefficient: Optional[float] = 1.0, + max_retry_interval: Optional[timedelta] = None, + retry_timeout: Optional[timedelta] = None, + ): + """Creates a new RetryPolicy instance. + + Args: + first_retry_interval(timedelta): The retry interval to use for the first retry attempt. + max_number_of_attempts(int): The maximum number of retry attempts. + backoff_coefficient(Optional[float]): The backoff coefficient to use for calculating + the next retry interval. + max_retry_interval(Optional[timedelta]): The maximum retry interval to use for any + retry attempt. + retry_timeout(Optional[timedelta]): The maximum amount of time to spend retrying the + operation. + """ + # validate inputs + if first_retry_interval < timedelta(seconds=0): + raise ValueError('first_retry_interval must be >= 0') + if max_number_of_attempts < 1: + raise ValueError('max_number_of_attempts must be >= 1') + if backoff_coefficient is not None and backoff_coefficient < 1: + raise ValueError('backoff_coefficient must be >= 1') + if max_retry_interval is not None and max_retry_interval < timedelta(seconds=0): + raise ValueError('max_retry_interval must be >= 0') + if retry_timeout is not None and retry_timeout < timedelta(seconds=0): + raise ValueError('retry_timeout must be >= 0') + + self._obj = task.RetryPolicy( + first_retry_interval=first_retry_interval, + max_number_of_attempts=max_number_of_attempts, + backoff_coefficient=backoff_coefficient, + max_retry_interval=max_retry_interval, + retry_timeout=retry_timeout, + ) + + @property + def obj(self) -> task.RetryPolicy: + """Returns the underlying RetryPolicy object.""" + return self._obj + + @property + def first_retry_interval(self) -> timedelta: + """The retry interval to use for the first retry attempt.""" + return self._obj._first_retry_interval + + @property + def max_number_of_attempts(self) -> int: + """The maximum number of retry attempts.""" + return self._obj._max_number_of_attempts + + @property + def backoff_coefficient(self) -> Optional[float]: + """The backoff coefficient to use for calculating the next retry interval.""" + return self._obj._backoff_coefficient + + @property + def max_retry_interval(self) -> Optional[timedelta]: + """The maximum retry interval to use for any retry attempt.""" + return self._obj._max_retry_interval + + @property + def retry_timeout(self) -> Optional[timedelta]: + """The maximum amount of time to spend retrying the operation.""" + return self._obj._retry_timeout diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/util.py b/ext/dapr-ext-workflow/dapr/ext/workflow/util.py index 648bc973d..cef38219a 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/util.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/util.py @@ -1,31 +1,31 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Optional - -from dapr.conf import settings - - -def getAddress(host: Optional[str] = None, port: Optional[str] = None) -> str: - if not host and not port: - address = settings.DAPR_GRPC_ENDPOINT or ( - f'{settings.DAPR_RUNTIME_HOST}:' f'{settings.DAPR_GRPC_PORT}' - ) - else: - host = host or settings.DAPR_RUNTIME_HOST - port = port or settings.DAPR_GRPC_PORT - address = f'{host}:{port}' - - return address +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Optional + +from dapr.conf import settings + + +def getAddress(host: Optional[str] = None, port: Optional[str] = None) -> str: + if not host and not port: + address = settings.DAPR_GRPC_ENDPOINT or ( + f'{settings.DAPR_RUNTIME_HOST}:' f'{settings.DAPR_GRPC_PORT}' + ) + else: + host = host or settings.DAPR_RUNTIME_HOST + port = port or settings.DAPR_GRPC_PORT + address = f'{host}:{port}' + + return address diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/version.py b/ext/dapr-ext-workflow/dapr/ext/workflow/version.py index 494a56947..b2f9d9d0c 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/version.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/version.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -__version__ = '0.5.0rc1.dev' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +__version__ = '0.5.0rc1.dev' diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_activity_context.py b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_activity_context.py index f460e8013..0437b2cd6 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_activity_context.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_activity_context.py @@ -1,47 +1,47 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from __future__ import annotations -from typing import Callable, TypeVar - -from durabletask import task - -T = TypeVar('T') -TInput = TypeVar('TInput') -TOutput = TypeVar('TOutput') - - -class WorkflowActivityContext: - """Defines properties and methods for task activity context objects.""" - - def __init__(self, ctx: task.ActivityContext): - self.__obj = ctx - - @property - def workflow_id(self) -> str: - """Gets the unique ID of the current workflow instance""" - return self.__obj.orchestration_id - - @property - def task_id(self) -> int: - """Gets the unique ID of the current workflow task""" - return self.__obj.task_id - - def get_inner_context(self) -> task.ActivityContext: - return self.__obj - - -# Activities are simple functions that can be scheduled by workflows -Activity = Callable[..., TOutput] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from __future__ import annotations +from typing import Callable, TypeVar + +from durabletask import task + +T = TypeVar('T') +TInput = TypeVar('TInput') +TOutput = TypeVar('TOutput') + + +class WorkflowActivityContext: + """Defines properties and methods for task activity context objects.""" + + def __init__(self, ctx: task.ActivityContext): + self.__obj = ctx + + @property + def workflow_id(self) -> str: + """Gets the unique ID of the current workflow instance""" + return self.__obj.orchestration_id + + @property + def task_id(self) -> int: + """Gets the unique ID of the current workflow task""" + return self.__obj.task_id + + def get_inner_context(self) -> task.ActivityContext: + return self.__obj + + +# Activities are simple functions that can be scheduled by workflows +Activity = Callable[..., TOutput] diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py index e0e3c7368..352b66830 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_context.py @@ -1,184 +1,184 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from __future__ import annotations -from abc import ABC, abstractmethod -from datetime import datetime, timedelta -from typing import Any, Callable, Generator, Optional, TypeVar, Union - -from durabletask import task - -from dapr.ext.workflow.workflow_activity_context import Activity - -T = TypeVar('T') -TInput = TypeVar('TInput') -TOutput = TypeVar('TOutput') - - -class WorkflowContext(ABC): - """Context object used by workflow implementations to perform actions such as scheduling - activities, durable timers, waiting for external events, and for getting basic information - about the current workflow instance. - """ - - @property - @abstractmethod - def instance_id(self) -> str: - """Get the ID of the current workflow instance. - - The instance ID is generated and fixed when the workflow - is scheduled. It can be either auto-generated, in which case it is - formatted as a UUID, or it can be user-specified with any format. - - Returns - ------- - str - The ID of the current workflow. - """ - pass - - @property - @abstractmethod - def current_utc_datetime(self) -> datetime: - """Get the current date/time as UTC. - - This date/time value is derived from the workflow history. It - always returns the same value at specific points in the workflow - function code, making it deterministic and safe for replay. - - Returns - ------- - datetime - The current timestamp in a way that is safe for use by workflow functions - """ - pass - - @property - @abstractmethod - def is_replaying(self) -> bool: - """Get the value indicating whether the workflow is replaying from history. - - This property is useful when there is logic that needs to run only when - the workflow is _not_ replaying. For example, certain - types of application logging may become too noisy when duplicated as - part of workflow replay. The workflow code could check - to see whether the function is being replayed and then issue the log - statements when this value is `false`. - - Returns - ------- - bool - Value indicating whether the workflow is currently replaying. - """ - pass - - @abstractmethod - def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task: - """Create a Timer Task to fire after at the specified deadline. - - Parameters - ---------- - fire_at: datetime.datetime | datetime.timedelta - The time for the timer to trigger. Can be specified as a `datetime` or a `timedelta`. - - Returns - ------- - Task - A Durable Timer Task that schedules the timer to wake up the orchestrator - """ - pass - - @abstractmethod - def call_activity( - self, activity: Activity[TOutput], *, input: Optional[TInput] = None - ) -> task.Task[TOutput]: - """Schedule an activity for execution. - - Parameters - ---------- - activity: Activity[TInput, TOutput] - A reference to the activity function to call. - input: TInput | None - The JSON-serializable input (or None) to pass to the activity. - return_type: task.Task[TOutput] - The JSON-serializable output type to expect from the activity result. - - Returns - ------- - Task - A Durable Task that completes when the called activity function completes or fails. - """ - pass - - @abstractmethod - def call_child_workflow( - self, - orchestrator: Workflow[TOutput], - *, - input: Optional[TInput] = None, - instance_id: Optional[str] = None, - ) -> task.Task[TOutput]: - """Schedule child-workflow function for execution. - - Parameters - ---------- - orchestrator: Orchestrator[TInput, TOutput] - A reference to the orchestrator function to call. - input: TInput - The optional JSON-serializable input to pass to the orchestrator function. - instance_id: str - A unique ID to use for the sub-orchestration instance. If not specified, a - random UUID will be used. - - Returns - ------- - Task - A Durable Task that completes when the called child-workflow completes or fails. - """ - pass - - @abstractmethod - def wait_for_external_event(self, name: str) -> task.Task: - """Wait asynchronously for an event to be raised with the name `name`. - - Parameters - ---------- - name : str - The event name of the event that the task is waiting for. - - Returns - ------- - Task[TOutput] - A Durable Task that completes when the event is received. - """ - pass - - @abstractmethod - def continue_as_new(self, new_input: Any, *, save_events: bool = False) -> None: - """Continue the orchestration execution as a new instance. - - Parameters - ---------- - new_input : Any - The new input to use for the new orchestration instance. - save_events : bool - A flag indicating whether to add any unprocessed external events in the new - orchestration history. - """ - pass - - -# Workflows are generators that yield tasks and receive/return any type -Workflow = Callable[..., Union[Generator[task.Task, Any, Any], TOutput]] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from __future__ import annotations +from abc import ABC, abstractmethod +from datetime import datetime, timedelta +from typing import Any, Callable, Generator, Optional, TypeVar, Union + +from durabletask import task + +from dapr.ext.workflow.workflow_activity_context import Activity + +T = TypeVar('T') +TInput = TypeVar('TInput') +TOutput = TypeVar('TOutput') + + +class WorkflowContext(ABC): + """Context object used by workflow implementations to perform actions such as scheduling + activities, durable timers, waiting for external events, and for getting basic information + about the current workflow instance. + """ + + @property + @abstractmethod + def instance_id(self) -> str: + """Get the ID of the current workflow instance. + + The instance ID is generated and fixed when the workflow + is scheduled. It can be either auto-generated, in which case it is + formatted as a UUID, or it can be user-specified with any format. + + Returns + ------- + str + The ID of the current workflow. + """ + pass + + @property + @abstractmethod + def current_utc_datetime(self) -> datetime: + """Get the current date/time as UTC. + + This date/time value is derived from the workflow history. It + always returns the same value at specific points in the workflow + function code, making it deterministic and safe for replay. + + Returns + ------- + datetime + The current timestamp in a way that is safe for use by workflow functions + """ + pass + + @property + @abstractmethod + def is_replaying(self) -> bool: + """Get the value indicating whether the workflow is replaying from history. + + This property is useful when there is logic that needs to run only when + the workflow is _not_ replaying. For example, certain + types of application logging may become too noisy when duplicated as + part of workflow replay. The workflow code could check + to see whether the function is being replayed and then issue the log + statements when this value is `false`. + + Returns + ------- + bool + Value indicating whether the workflow is currently replaying. + """ + pass + + @abstractmethod + def create_timer(self, fire_at: Union[datetime, timedelta]) -> task.Task: + """Create a Timer Task to fire after at the specified deadline. + + Parameters + ---------- + fire_at: datetime.datetime | datetime.timedelta + The time for the timer to trigger. Can be specified as a `datetime` or a `timedelta`. + + Returns + ------- + Task + A Durable Timer Task that schedules the timer to wake up the orchestrator + """ + pass + + @abstractmethod + def call_activity( + self, activity: Activity[TOutput], *, input: Optional[TInput] = None + ) -> task.Task[TOutput]: + """Schedule an activity for execution. + + Parameters + ---------- + activity: Activity[TInput, TOutput] + A reference to the activity function to call. + input: TInput | None + The JSON-serializable input (or None) to pass to the activity. + return_type: task.Task[TOutput] + The JSON-serializable output type to expect from the activity result. + + Returns + ------- + Task + A Durable Task that completes when the called activity function completes or fails. + """ + pass + + @abstractmethod + def call_child_workflow( + self, + orchestrator: Workflow[TOutput], + *, + input: Optional[TInput] = None, + instance_id: Optional[str] = None, + ) -> task.Task[TOutput]: + """Schedule child-workflow function for execution. + + Parameters + ---------- + orchestrator: Orchestrator[TInput, TOutput] + A reference to the orchestrator function to call. + input: TInput + The optional JSON-serializable input to pass to the orchestrator function. + instance_id: str + A unique ID to use for the sub-orchestration instance. If not specified, a + random UUID will be used. + + Returns + ------- + Task + A Durable Task that completes when the called child-workflow completes or fails. + """ + pass + + @abstractmethod + def wait_for_external_event(self, name: str) -> task.Task: + """Wait asynchronously for an event to be raised with the name `name`. + + Parameters + ---------- + name : str + The event name of the event that the task is waiting for. + + Returns + ------- + Task[TOutput] + A Durable Task that completes when the event is received. + """ + pass + + @abstractmethod + def continue_as_new(self, new_input: Any, *, save_events: bool = False) -> None: + """Continue the orchestration execution as a new instance. + + Parameters + ---------- + new_input : Any + The new input to use for the new orchestration instance. + save_events : bool + A flag indicating whether to add any unprocessed external events in the new + orchestration history. + """ + pass + + +# Workflows are generators that yield tasks and receive/return any type +Workflow = Callable[..., Union[Generator[task.Task, Any, Any], TOutput]] diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_runtime.py b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_runtime.py index d1f02b354..65cff3245 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_runtime.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_runtime.py @@ -1,261 +1,261 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import inspect -from functools import wraps -from typing import Optional, TypeVar - -from durabletask import worker, task - -from dapr.ext.workflow.workflow_context import Workflow -from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext -from dapr.ext.workflow.workflow_activity_context import Activity, WorkflowActivityContext -from dapr.ext.workflow.util import getAddress - -from dapr.clients import DaprInternalError -from dapr.clients.http.client import DAPR_API_TOKEN_HEADER -from dapr.conf import settings -from dapr.conf.helpers import GrpcEndpoint -from dapr.ext.workflow.logger import LoggerOptions, Logger - -T = TypeVar('T') -TInput = TypeVar('TInput') -TOutput = TypeVar('TOutput') - - -class WorkflowRuntime: - """WorkflowRuntime is the entry point for registering workflows and activities.""" - - def __init__( - self, - host: Optional[str] = None, - port: Optional[str] = None, - logger_options: Optional[LoggerOptions] = None, - ): - self._logger = Logger('WorkflowRuntime', logger_options) - metadata = tuple() - if settings.DAPR_API_TOKEN: - metadata = ((DAPR_API_TOKEN_HEADER, settings.DAPR_API_TOKEN),) - address = getAddress(host, port) - - try: - uri = GrpcEndpoint(address) - except ValueError as error: - raise DaprInternalError(f'{error}') from error - - options = self._logger.get_options() - self.__worker = worker.TaskHubGrpcWorker( - host_address=uri.endpoint, - metadata=metadata, - secure_channel=uri.tls, - log_handler=options.log_handler, - log_formatter=options.log_formatter, - ) - - def register_workflow(self, fn: Workflow, *, name: Optional[str] = None): - self._logger.info(f"Registering workflow '{fn.__name__}' with runtime") - - def orchestrationWrapper(ctx: task.OrchestrationContext, inp: Optional[TInput] = None): - """Responsible to call Workflow function in orchestrationWrapper""" - daprWfContext = DaprWorkflowContext(ctx, self._logger.get_options()) - if inp is None: - return fn(daprWfContext) - return fn(daprWfContext, inp) - - if hasattr(fn, '_workflow_registered'): - # whenever a workflow is registered, it has a _dapr_alternate_name attribute - alt_name = fn.__dict__['_dapr_alternate_name'] - raise ValueError(f'Workflow {fn.__name__} already registered as {alt_name}') - if hasattr(fn, '_dapr_alternate_name'): - alt_name = fn._dapr_alternate_name - if name is not None: - m = f'Workflow {fn.__name__} already has an alternate name {alt_name}' - raise ValueError(m) - else: - fn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ - - self.__worker._registry.add_named_orchestrator( - fn.__dict__['_dapr_alternate_name'], orchestrationWrapper - ) - fn.__dict__['_workflow_registered'] = True - - def register_activity(self, fn: Activity, *, name: Optional[str] = None): - """Registers a workflow activity as a function that takes - a specified input type and returns a specified output type. - """ - self._logger.info(f"Registering activity '{fn.__name__}' with runtime") - - def activityWrapper(ctx: task.ActivityContext, inp: Optional[TInput] = None): - """Responsible to call Activity function in activityWrapper""" - wfActivityContext = WorkflowActivityContext(ctx) - if inp is None: - return fn(wfActivityContext) - return fn(wfActivityContext, inp) - - if hasattr(fn, '_activity_registered'): - # whenever an activity is registered, it has a _dapr_alternate_name attribute - alt_name = fn.__dict__['_dapr_alternate_name'] - raise ValueError(f'Activity {fn.__name__} already registered as {alt_name}') - if hasattr(fn, '_dapr_alternate_name'): - alt_name = fn._dapr_alternate_name - if name is not None: - m = f'Activity {fn.__name__} already has an alternate name {alt_name}' - raise ValueError(m) - else: - fn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ - - self.__worker._registry.add_named_activity( - fn.__dict__['_dapr_alternate_name'], activityWrapper - ) - fn.__dict__['_activity_registered'] = True - - def start(self): - """Starts the listening for work items on a background thread.""" - self.__worker.start() - - def shutdown(self): - """Stops the listening for work items on a background thread.""" - self.__worker.stop() - - def workflow(self, __fn: Workflow = None, *, name: Optional[str] = None): - """Decorator to register a workflow function. - - This example shows how to register a workflow function with a name: - - from dapr.ext.workflow import WorkflowRuntime - wfr = WorkflowRuntime() - - @wfr.workflow(name="add") - def add(ctx, x: int, y: int) -> int: - return x + y - - This example shows how to register a workflow function without - an alternate name: - - from dapr.ext.workflow import WorkflowRuntime - wfr = WorkflowRuntime() - - @wfr.workflow - def add(ctx, x: int, y: int) -> int: - return x + y - - Args: - name (Optional[str], optional): Name to identify the workflow function as in - the workflow runtime. Defaults to None. - """ - - def wrapper(fn: Workflow): - self.register_workflow(fn, name=name) - - @wraps(fn) - def innerfn(): - return fn - - if hasattr(fn, '_dapr_alternate_name'): - innerfn.__dict__['_dapr_alternate_name'] = fn.__dict__['_dapr_alternate_name'] - else: - innerfn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ - innerfn.__signature__ = inspect.signature(fn) - return innerfn - - if __fn: - # This case is true when the decorator is used without arguments - # and the function to be decorated is passed as the first argument. - return wrapper(__fn) - - return wrapper - - def activity(self, __fn: Activity = None, *, name: Optional[str] = None): - """Decorator to register an activity function. - - This example shows how to register an activity function with an alternate name: - - from dapr.ext.workflow import WorkflowRuntime - wfr = WorkflowRuntime() - - @wfr.activity(name="add") - def add(ctx, x: int, y: int) -> int: - return x + y - - This example shows how to register an activity function without an alternate name: - - from dapr.ext.workflow import WorkflowRuntime - wfr = WorkflowRuntime() - - @wfr.activity - def add(ctx, x: int, y: int) -> int: - return x + y - - Args: - name (Optional[str], optional): Name to identify the activity function as in - the workflow runtime. Defaults to None. - """ - - def wrapper(fn: Activity): - self.register_activity(fn, name=name) - - @wraps(fn) - def innerfn(): - return fn - - if hasattr(fn, '_dapr_alternate_name'): - innerfn.__dict__['_dapr_alternate_name'] = fn.__dict__['_dapr_alternate_name'] - else: - innerfn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ - innerfn.__signature__ = inspect.signature(fn) - return innerfn - - if __fn: - # This case is true when the decorator is used without arguments - # and the function to be decorated is passed as the first argument. - return wrapper(__fn) - - return wrapper - - -def alternate_name(name: Optional[str] = None): - """Decorator to register a workflow or activity function with an alternate name. - - This example shows how to register a workflow function with an alternate name: - - from dapr.ext.workflow import WorkflowRuntime - wfr = WorkflowRuntime() - - @wfr.workflow - @alternate_name(add") - def add(ctx, x: int, y: int) -> int: - return x + y - - Args: - name (Optional[str], optional): Name to identify the workflow or activity function as in - the workflow runtime. Defaults to None. - """ - - def wrapper(fn: any): - if hasattr(fn, '_dapr_alternate_name'): - raise ValueError( - f'Function {fn.__name__} already has an alternate name {fn._dapr_alternate_name}' - ) - fn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ - - @wraps(fn) - def innerfn(*args, **kwargs): - return fn(*args, **kwargs) - - innerfn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ - innerfn.__signature__ = inspect.signature(fn) - return innerfn - - return wrapper +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import inspect +from functools import wraps +from typing import Optional, TypeVar + +from durabletask import worker, task + +from dapr.ext.workflow.workflow_context import Workflow +from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext +from dapr.ext.workflow.workflow_activity_context import Activity, WorkflowActivityContext +from dapr.ext.workflow.util import getAddress + +from dapr.clients import DaprInternalError +from dapr.clients.http.client import DAPR_API_TOKEN_HEADER +from dapr.conf import settings +from dapr.conf.helpers import GrpcEndpoint +from dapr.ext.workflow.logger import LoggerOptions, Logger + +T = TypeVar('T') +TInput = TypeVar('TInput') +TOutput = TypeVar('TOutput') + + +class WorkflowRuntime: + """WorkflowRuntime is the entry point for registering workflows and activities.""" + + def __init__( + self, + host: Optional[str] = None, + port: Optional[str] = None, + logger_options: Optional[LoggerOptions] = None, + ): + self._logger = Logger('WorkflowRuntime', logger_options) + metadata = tuple() + if settings.DAPR_API_TOKEN: + metadata = ((DAPR_API_TOKEN_HEADER, settings.DAPR_API_TOKEN),) + address = getAddress(host, port) + + try: + uri = GrpcEndpoint(address) + except ValueError as error: + raise DaprInternalError(f'{error}') from error + + options = self._logger.get_options() + self.__worker = worker.TaskHubGrpcWorker( + host_address=uri.endpoint, + metadata=metadata, + secure_channel=uri.tls, + log_handler=options.log_handler, + log_formatter=options.log_formatter, + ) + + def register_workflow(self, fn: Workflow, *, name: Optional[str] = None): + self._logger.info(f"Registering workflow '{fn.__name__}' with runtime") + + def orchestrationWrapper(ctx: task.OrchestrationContext, inp: Optional[TInput] = None): + """Responsible to call Workflow function in orchestrationWrapper""" + daprWfContext = DaprWorkflowContext(ctx, self._logger.get_options()) + if inp is None: + return fn(daprWfContext) + return fn(daprWfContext, inp) + + if hasattr(fn, '_workflow_registered'): + # whenever a workflow is registered, it has a _dapr_alternate_name attribute + alt_name = fn.__dict__['_dapr_alternate_name'] + raise ValueError(f'Workflow {fn.__name__} already registered as {alt_name}') + if hasattr(fn, '_dapr_alternate_name'): + alt_name = fn._dapr_alternate_name + if name is not None: + m = f'Workflow {fn.__name__} already has an alternate name {alt_name}' + raise ValueError(m) + else: + fn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ + + self.__worker._registry.add_named_orchestrator( + fn.__dict__['_dapr_alternate_name'], orchestrationWrapper + ) + fn.__dict__['_workflow_registered'] = True + + def register_activity(self, fn: Activity, *, name: Optional[str] = None): + """Registers a workflow activity as a function that takes + a specified input type and returns a specified output type. + """ + self._logger.info(f"Registering activity '{fn.__name__}' with runtime") + + def activityWrapper(ctx: task.ActivityContext, inp: Optional[TInput] = None): + """Responsible to call Activity function in activityWrapper""" + wfActivityContext = WorkflowActivityContext(ctx) + if inp is None: + return fn(wfActivityContext) + return fn(wfActivityContext, inp) + + if hasattr(fn, '_activity_registered'): + # whenever an activity is registered, it has a _dapr_alternate_name attribute + alt_name = fn.__dict__['_dapr_alternate_name'] + raise ValueError(f'Activity {fn.__name__} already registered as {alt_name}') + if hasattr(fn, '_dapr_alternate_name'): + alt_name = fn._dapr_alternate_name + if name is not None: + m = f'Activity {fn.__name__} already has an alternate name {alt_name}' + raise ValueError(m) + else: + fn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ + + self.__worker._registry.add_named_activity( + fn.__dict__['_dapr_alternate_name'], activityWrapper + ) + fn.__dict__['_activity_registered'] = True + + def start(self): + """Starts the listening for work items on a background thread.""" + self.__worker.start() + + def shutdown(self): + """Stops the listening for work items on a background thread.""" + self.__worker.stop() + + def workflow(self, __fn: Workflow = None, *, name: Optional[str] = None): + """Decorator to register a workflow function. + + This example shows how to register a workflow function with a name: + + from dapr.ext.workflow import WorkflowRuntime + wfr = WorkflowRuntime() + + @wfr.workflow(name="add") + def add(ctx, x: int, y: int) -> int: + return x + y + + This example shows how to register a workflow function without + an alternate name: + + from dapr.ext.workflow import WorkflowRuntime + wfr = WorkflowRuntime() + + @wfr.workflow + def add(ctx, x: int, y: int) -> int: + return x + y + + Args: + name (Optional[str], optional): Name to identify the workflow function as in + the workflow runtime. Defaults to None. + """ + + def wrapper(fn: Workflow): + self.register_workflow(fn, name=name) + + @wraps(fn) + def innerfn(): + return fn + + if hasattr(fn, '_dapr_alternate_name'): + innerfn.__dict__['_dapr_alternate_name'] = fn.__dict__['_dapr_alternate_name'] + else: + innerfn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ + innerfn.__signature__ = inspect.signature(fn) + return innerfn + + if __fn: + # This case is true when the decorator is used without arguments + # and the function to be decorated is passed as the first argument. + return wrapper(__fn) + + return wrapper + + def activity(self, __fn: Activity = None, *, name: Optional[str] = None): + """Decorator to register an activity function. + + This example shows how to register an activity function with an alternate name: + + from dapr.ext.workflow import WorkflowRuntime + wfr = WorkflowRuntime() + + @wfr.activity(name="add") + def add(ctx, x: int, y: int) -> int: + return x + y + + This example shows how to register an activity function without an alternate name: + + from dapr.ext.workflow import WorkflowRuntime + wfr = WorkflowRuntime() + + @wfr.activity + def add(ctx, x: int, y: int) -> int: + return x + y + + Args: + name (Optional[str], optional): Name to identify the activity function as in + the workflow runtime. Defaults to None. + """ + + def wrapper(fn: Activity): + self.register_activity(fn, name=name) + + @wraps(fn) + def innerfn(): + return fn + + if hasattr(fn, '_dapr_alternate_name'): + innerfn.__dict__['_dapr_alternate_name'] = fn.__dict__['_dapr_alternate_name'] + else: + innerfn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ + innerfn.__signature__ = inspect.signature(fn) + return innerfn + + if __fn: + # This case is true when the decorator is used without arguments + # and the function to be decorated is passed as the first argument. + return wrapper(__fn) + + return wrapper + + +def alternate_name(name: Optional[str] = None): + """Decorator to register a workflow or activity function with an alternate name. + + This example shows how to register a workflow function with an alternate name: + + from dapr.ext.workflow import WorkflowRuntime + wfr = WorkflowRuntime() + + @wfr.workflow + @alternate_name(add") + def add(ctx, x: int, y: int) -> int: + return x + y + + Args: + name (Optional[str], optional): Name to identify the workflow or activity function as in + the workflow runtime. Defaults to None. + """ + + def wrapper(fn: any): + if hasattr(fn, '_dapr_alternate_name'): + raise ValueError( + f'Function {fn.__name__} already has an alternate name {fn._dapr_alternate_name}' + ) + fn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ + + @wraps(fn) + def innerfn(*args, **kwargs): + return fn(*args, **kwargs) + + innerfn.__dict__['_dapr_alternate_name'] = name if name else fn.__name__ + innerfn.__signature__ = inspect.signature(fn) + return innerfn + + return wrapper diff --git a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_state.py b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_state.py index 10847fc54..1381d82d2 100644 --- a/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_state.py +++ b/ext/dapr-ext-workflow/dapr/ext/workflow/workflow_state.py @@ -1,79 +1,79 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from enum import Enum -import json - -from durabletask import client - - -class WorkflowStatus(Enum): - UNKNOWN = 0 - RUNNING = 1 - COMPLETED = 2 - FAILED = 3 - TERMINATED = 4 - PENDING = 5 - SUSPENDED = 6 - - -class WorkflowState: - """Represents a snapshot of a workflow instance's current state, including runtime status.""" - - def __init__(self, state: client.OrchestrationState): - self.__obj = state - - # provide proxy access to regular attributes of wrapped object - def __getattr__(self, name): - return getattr(self.__obj, name) - - @property - def runtime_status(self) -> WorkflowStatus: - if self.__obj.runtime_status == client.OrchestrationStatus.RUNNING: - return WorkflowStatus.RUNNING - elif self.__obj.runtime_status == client.OrchestrationStatus.COMPLETED: - return WorkflowStatus.COMPLETED - elif self.__obj.runtime_status == client.OrchestrationStatus.FAILED: - return WorkflowStatus.FAILED - elif self.__obj.runtime_status == client.OrchestrationStatus.TERMINATED: - return WorkflowStatus.TERMINATED - elif self.__obj.runtime_status == client.OrchestrationStatus.PENDING: - return WorkflowStatus.PENDING - elif self.__obj.runtime_status == client.OrchestrationStatus.SUSPENDED: - return WorkflowStatus.SUSPENDED - else: - return WorkflowStatus.UNKNOWN - - def __str__(self) -> str: - return json.dumps(self.to_json(), indent=4, sort_keys=True, default=str) - - def to_json(self): - state_dict = { - 'instance_id': self.__obj.instance_id, - 'name': self.__obj.name, - 'runtime_status': self.__obj.runtime_status.name, - 'created_at': self.__obj.created_at, - 'last_updated_at': self.__obj.last_updated_at, - 'serialized_input': self.__obj.serialized_input, - 'serialized_output': self.__obj.serialized_output, - 'serialized_custom_status': self.__obj.serialized_custom_status, - } - if self.__obj.failure_details is not None: - state_dict['failure_details'] = { - 'message': self.__obj.failure_details.message, - 'error_type': self.__obj.failure_details.error_type, - 'stack_trace': self.__obj.failure_details.stack_trace, - } - return state_dict +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from enum import Enum +import json + +from durabletask import client + + +class WorkflowStatus(Enum): + UNKNOWN = 0 + RUNNING = 1 + COMPLETED = 2 + FAILED = 3 + TERMINATED = 4 + PENDING = 5 + SUSPENDED = 6 + + +class WorkflowState: + """Represents a snapshot of a workflow instance's current state, including runtime status.""" + + def __init__(self, state: client.OrchestrationState): + self.__obj = state + + # provide proxy access to regular attributes of wrapped object + def __getattr__(self, name): + return getattr(self.__obj, name) + + @property + def runtime_status(self) -> WorkflowStatus: + if self.__obj.runtime_status == client.OrchestrationStatus.RUNNING: + return WorkflowStatus.RUNNING + elif self.__obj.runtime_status == client.OrchestrationStatus.COMPLETED: + return WorkflowStatus.COMPLETED + elif self.__obj.runtime_status == client.OrchestrationStatus.FAILED: + return WorkflowStatus.FAILED + elif self.__obj.runtime_status == client.OrchestrationStatus.TERMINATED: + return WorkflowStatus.TERMINATED + elif self.__obj.runtime_status == client.OrchestrationStatus.PENDING: + return WorkflowStatus.PENDING + elif self.__obj.runtime_status == client.OrchestrationStatus.SUSPENDED: + return WorkflowStatus.SUSPENDED + else: + return WorkflowStatus.UNKNOWN + + def __str__(self) -> str: + return json.dumps(self.to_json(), indent=4, sort_keys=True, default=str) + + def to_json(self): + state_dict = { + 'instance_id': self.__obj.instance_id, + 'name': self.__obj.name, + 'runtime_status': self.__obj.runtime_status.name, + 'created_at': self.__obj.created_at, + 'last_updated_at': self.__obj.last_updated_at, + 'serialized_input': self.__obj.serialized_input, + 'serialized_output': self.__obj.serialized_output, + 'serialized_custom_status': self.__obj.serialized_custom_status, + } + if self.__obj.failure_details is not None: + state_dict['failure_details'] = { + 'message': self.__obj.failure_details.message, + 'error_type': self.__obj.failure_details.error_type, + 'stack_trace': self.__obj.failure_details.stack_trace, + } + return state_dict diff --git a/ext/dapr-ext-workflow/setup.cfg b/ext/dapr-ext-workflow/setup.cfg index 475b2bdf9..de8f2ffb9 100644 --- a/ext/dapr-ext-workflow/setup.cfg +++ b/ext/dapr-ext-workflow/setup.cfg @@ -1,35 +1,35 @@ -[metadata] -url = https://dapr.io/ -author = Dapr Authors -author_email = daprweb@microsoft.com -license = Apache -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 -project_urls = - Documentation = https://github.com/dapr/docs - Source = https://github.com/dapr/python-sdk - -[options] -python_requires = >=3.8 -packages = find_namespace: -include_package_data = True -install_requires = - dapr-dev >= 1.13.0rc1.dev - durabletask >= 0.1.1a1 - -[options.packages.find] -include = - dapr.* - -exclude = - tests +[metadata] +url = https://dapr.io/ +author = Dapr Authors +author_email = daprweb@microsoft.com +license = Apache +license_file = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 +project_urls = + Documentation = https://github.com/dapr/docs + Source = https://github.com/dapr/python-sdk + +[options] +python_requires = >=3.8 +packages = find_namespace: +include_package_data = True +install_requires = + dapr-dev >= 1.13.0rc1.dev + durabletask >= 0.1.1a1 + +[options.packages.find] +include = + dapr.* + +exclude = + tests diff --git a/ext/dapr-ext-workflow/setup.py b/ext/dapr-ext-workflow/setup.py index e66837f7c..9a6218e53 100644 --- a/ext/dapr-ext-workflow/setup.py +++ b/ext/dapr-ext-workflow/setup.py @@ -1,64 +1,64 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os - -from setuptools import setup - -# Load version in dapr package. -version_info = {} -with open('dapr/ext/workflow/version.py') as fp: - exec(fp.read(), version_info) -__version__ = version_info['__version__'] - - -def is_release(): - return '.dev' not in __version__ - - -name = 'dapr-ext-workflow' -version = __version__ -description = 'The official release of Dapr Python SDK Workflow Authoring Extension.' -long_description = """ -This is the Workflow authoring extension for Dapr. - -Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to -build resilient, stateless and stateful microservices that run on the cloud and edge and -embraces the diversity of languages and developer frameworks. - -Dapr codifies the best practices for building microservice applications into open, -independent, building blocks that enable you to build portable applications with the language -and framework of your choice. Each building block is independent and you can use one, some, -or all of them in your application. -""".lstrip() - -# Get build number from GITHUB_RUN_NUMBER environment variable -build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') - -if not is_release(): - name += '-dev' - version = f'{__version__}{build_number}' - description = 'The developmental release for Dapr Workflow Authoring.' - long_description = 'This is the developmental release for Dapr Workflow Authoring.' - -print(f'package name: {name}, version: {version}', flush=True) - - -setup( - name=name, - version=version, - description=description, - long_description=long_description, -) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from setuptools import setup + +# Load version in dapr package. +version_info = {} +with open('dapr/ext/workflow/version.py') as fp: + exec(fp.read(), version_info) +__version__ = version_info['__version__'] + + +def is_release(): + return '.dev' not in __version__ + + +name = 'dapr-ext-workflow' +version = __version__ +description = 'The official release of Dapr Python SDK Workflow Authoring Extension.' +long_description = """ +This is the Workflow authoring extension for Dapr. + +Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to +build resilient, stateless and stateful microservices that run on the cloud and edge and +embraces the diversity of languages and developer frameworks. + +Dapr codifies the best practices for building microservice applications into open, +independent, building blocks that enable you to build portable applications with the language +and framework of your choice. Each building block is independent and you can use one, some, +or all of them in your application. +""".lstrip() + +# Get build number from GITHUB_RUN_NUMBER environment variable +build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') + +if not is_release(): + name += '-dev' + version = f'{__version__}{build_number}' + description = 'The developmental release for Dapr Workflow Authoring.' + long_description = 'This is the developmental release for Dapr Workflow Authoring.' + +print(f'package name: {name}, version: {version}', flush=True) + + +setup( + name=name, + version=version, + description=description, + long_description=long_description, +) diff --git a/ext/dapr-ext-workflow/tests/__init__.py b/ext/dapr-ext-workflow/tests/__init__.py index a47978853..3239faf3e 100644 --- a/ext/dapr-ext-workflow/tests/__init__.py +++ b/ext/dapr-ext-workflow/tests/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py b/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py index 6b3c9ad3c..04fbf56ed 100644 --- a/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py +++ b/ext/dapr-ext-workflow/tests/test_dapr_workflow_context.py @@ -1,67 +1,67 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from datetime import datetime -from unittest import mock -import unittest -from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext -from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext -from durabletask import worker - -mock_date_time = datetime(2023, 4, 27) -mock_instance_id = 'instance001' -mock_create_timer = 'create_timer' -mock_call_activity = 'call_activity' -mock_call_sub_orchestrator = 'call_sub_orchestrator' - - -class FakeOrchestrationContext: - def __init__(self): - self.instance_id = mock_instance_id - - def create_timer(self, fire_at): - return mock_create_timer - - def call_activity(self, activity, input): - return mock_call_activity - - def call_sub_orchestrator(self, orchestrator, input, instance_id): - return mock_call_sub_orchestrator - - -class DaprWorkflowContextTest(unittest.TestCase): - def mock_client_activity(ctx: WorkflowActivityContext, input): - print(f'{input}!', flush=True) - - def mock_client_child_wf(ctx: DaprWorkflowContext, input): - print(f'{input}') - - def test_workflow_context_functions(self): - with mock.patch( - 'durabletask.worker._RuntimeOrchestrationContext', - return_value=FakeOrchestrationContext(), - ): - fakeContext = worker._RuntimeOrchestrationContext(mock_instance_id) - dapr_wf_ctx = DaprWorkflowContext(fakeContext) - call_activity_result = dapr_wf_ctx.call_activity(self.mock_client_activity, input=None) - assert call_activity_result == mock_call_activity - - call_sub_orchestrator_result = dapr_wf_ctx.call_child_workflow( - self.mock_client_child_wf - ) - assert call_sub_orchestrator_result == mock_call_sub_orchestrator - - create_timer_result = dapr_wf_ctx.create_timer(mock_date_time) - assert create_timer_result == mock_create_timer +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from datetime import datetime +from unittest import mock +import unittest +from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext +from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext +from durabletask import worker + +mock_date_time = datetime(2023, 4, 27) +mock_instance_id = 'instance001' +mock_create_timer = 'create_timer' +mock_call_activity = 'call_activity' +mock_call_sub_orchestrator = 'call_sub_orchestrator' + + +class FakeOrchestrationContext: + def __init__(self): + self.instance_id = mock_instance_id + + def create_timer(self, fire_at): + return mock_create_timer + + def call_activity(self, activity, input): + return mock_call_activity + + def call_sub_orchestrator(self, orchestrator, input, instance_id): + return mock_call_sub_orchestrator + + +class DaprWorkflowContextTest(unittest.TestCase): + def mock_client_activity(ctx: WorkflowActivityContext, input): + print(f'{input}!', flush=True) + + def mock_client_child_wf(ctx: DaprWorkflowContext, input): + print(f'{input}') + + def test_workflow_context_functions(self): + with mock.patch( + 'durabletask.worker._RuntimeOrchestrationContext', + return_value=FakeOrchestrationContext(), + ): + fakeContext = worker._RuntimeOrchestrationContext(mock_instance_id) + dapr_wf_ctx = DaprWorkflowContext(fakeContext) + call_activity_result = dapr_wf_ctx.call_activity(self.mock_client_activity, input=None) + assert call_activity_result == mock_call_activity + + call_sub_orchestrator_result = dapr_wf_ctx.call_child_workflow( + self.mock_client_child_wf + ) + assert call_sub_orchestrator_result == mock_call_sub_orchestrator + + create_timer_result = dapr_wf_ctx.create_timer(mock_date_time) + assert create_timer_result == mock_create_timer diff --git a/ext/dapr-ext-workflow/tests/test_workflow_activity_context.py b/ext/dapr-ext-workflow/tests/test_workflow_activity_context.py index a45b8b7cd..b1755eb79 100644 --- a/ext/dapr-ext-workflow/tests/test_workflow_activity_context.py +++ b/ext/dapr-ext-workflow/tests/test_workflow_activity_context.py @@ -1,46 +1,46 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -from unittest import mock -from durabletask import task -from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext - -mock_orchestration_id = 'orchestration001' -mock_task = 10 - - -class FakeActivityContext: - @property - def orchestration_id(self): - return mock_orchestration_id - - @property - def task_id(self): - return mock_task - - -class WorkflowActivityContextTest(unittest.TestCase): - def test_workflow_activity_context(self): - with mock.patch('durabletask.task.ActivityContext', return_value=FakeActivityContext()): - fake_act_ctx = task.ActivityContext( - orchestration_id=mock_orchestration_id, task_id=mock_task - ) - act_ctx = WorkflowActivityContext(fake_act_ctx) - actual_orchestration_id = act_ctx.workflow_id - assert actual_orchestration_id == mock_orchestration_id - - actual_task_id = act_ctx.task_id - assert actual_task_id == mock_task +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +from unittest import mock +from durabletask import task +from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext + +mock_orchestration_id = 'orchestration001' +mock_task = 10 + + +class FakeActivityContext: + @property + def orchestration_id(self): + return mock_orchestration_id + + @property + def task_id(self): + return mock_task + + +class WorkflowActivityContextTest(unittest.TestCase): + def test_workflow_activity_context(self): + with mock.patch('durabletask.task.ActivityContext', return_value=FakeActivityContext()): + fake_act_ctx = task.ActivityContext( + orchestration_id=mock_orchestration_id, task_id=mock_task + ) + act_ctx = WorkflowActivityContext(fake_act_ctx) + actual_orchestration_id = act_ctx.workflow_id + assert actual_orchestration_id == mock_orchestration_id + + actual_task_id = act_ctx.task_id + assert actual_task_id == mock_task diff --git a/ext/dapr-ext-workflow/tests/test_workflow_client.py b/ext/dapr-ext-workflow/tests/test_workflow_client.py index 4a7f93b95..9fc315e50 100644 --- a/ext/dapr-ext-workflow/tests/test_workflow_client.py +++ b/ext/dapr-ext-workflow/tests/test_workflow_client.py @@ -1,121 +1,121 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from datetime import datetime -from typing import Any, Union -import unittest -from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext -from unittest import mock -from dapr.ext.workflow.dapr_workflow_client import DaprWorkflowClient -from durabletask import client - -mock_schedule_result = 'workflow001' -mock_raise_event_result = 'event001' -mock_terminate_result = 'terminate001' -mock_suspend_result = 'suspend001' -mock_resume_result = 'resume001' -mockInstanceId = 'instance001' - - -class FakeTaskHubGrpcClient: - def schedule_new_orchestration(self, workflow, input, instance_id, start_at): - return mock_schedule_result - - def get_orchestration_state(self, instance_id, fetch_payloads): - return self._inner_get_orchestration_state(instance_id, client.OrchestrationStatus.PENDING) - - def wait_for_orchestration_start(self, instance_id, fetch_payloads, timeout): - return self._inner_get_orchestration_state(instance_id, client.OrchestrationStatus.RUNNING) - - def wait_for_orchestration_completion(self, instance_id, fetch_payloads, timeout): - return self._inner_get_orchestration_state( - instance_id, client.OrchestrationStatus.COMPLETED - ) - - def raise_orchestration_event( - self, instance_id: str, event_name: str, *, data: Union[Any, None] = None - ): - return mock_raise_event_result - - def terminate_orchestration(self, instance_id: str, *, output: Union[Any, None] = None): - return mock_terminate_result - - def suspend_orchestration(self, instance_id: str): - return mock_suspend_result - - def resume_orchestration(self, instance_id: str): - return mock_resume_result - - def _inner_get_orchestration_state(self, instance_id, state: client.OrchestrationStatus): - return client.OrchestrationState( - instance_id=instance_id, - name='', - runtime_status=state, - created_at=datetime.now(), - last_updated_at=datetime.now(), - serialized_input=None, - serialized_output=None, - serialized_custom_status=None, - failure_details=None, - ) - - -class WorkflowClientTest(unittest.TestCase): - def mock_client_wf(ctx: DaprWorkflowContext, input): - print(f'{input}') - - def test_client_functions(self): - with mock.patch( - 'durabletask.client.TaskHubGrpcClient', return_value=FakeTaskHubGrpcClient() - ): - wfClient = DaprWorkflowClient() - actual_schedule_result = wfClient.schedule_new_workflow( - workflow=self.mock_client_wf, input='Hi Chef!' - ) - assert actual_schedule_result == mock_schedule_result - - actual_get_result = wfClient.get_workflow_state( - instance_id=mockInstanceId, fetch_payloads=True - ) - assert actual_get_result.runtime_status.name == 'PENDING' - assert actual_get_result.instance_id == mockInstanceId - - actual_wait_start_result = wfClient.wait_for_workflow_start( - instance_id=mockInstanceId, timeout_in_seconds=30 - ) - assert actual_wait_start_result.runtime_status.name == 'RUNNING' - assert actual_wait_start_result.instance_id == mockInstanceId - - actual_wait_completion_result = wfClient.wait_for_workflow_completion( - instance_id=mockInstanceId, timeout_in_seconds=30 - ) - assert actual_wait_completion_result.runtime_status.name == 'COMPLETED' - assert actual_wait_completion_result.instance_id == mockInstanceId - - actual_raise_event_result = wfClient.raise_workflow_event( - instance_id=mockInstanceId, event_name='test_event', data='test_data' - ) - assert actual_raise_event_result == mock_raise_event_result - - actual_terminate_result = wfClient.terminate_workflow( - instance_id=mockInstanceId, output='test_output' - ) - assert actual_terminate_result == mock_terminate_result - - actual_suspend_result = wfClient.pause_workflow(instance_id=mockInstanceId) - assert actual_suspend_result == mock_suspend_result - - actual_resume_result = wfClient.resume_workflow(instance_id=mockInstanceId) - assert actual_resume_result == mock_resume_result +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from datetime import datetime +from typing import Any, Union +import unittest +from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext +from unittest import mock +from dapr.ext.workflow.dapr_workflow_client import DaprWorkflowClient +from durabletask import client + +mock_schedule_result = 'workflow001' +mock_raise_event_result = 'event001' +mock_terminate_result = 'terminate001' +mock_suspend_result = 'suspend001' +mock_resume_result = 'resume001' +mockInstanceId = 'instance001' + + +class FakeTaskHubGrpcClient: + def schedule_new_orchestration(self, workflow, input, instance_id, start_at): + return mock_schedule_result + + def get_orchestration_state(self, instance_id, fetch_payloads): + return self._inner_get_orchestration_state(instance_id, client.OrchestrationStatus.PENDING) + + def wait_for_orchestration_start(self, instance_id, fetch_payloads, timeout): + return self._inner_get_orchestration_state(instance_id, client.OrchestrationStatus.RUNNING) + + def wait_for_orchestration_completion(self, instance_id, fetch_payloads, timeout): + return self._inner_get_orchestration_state( + instance_id, client.OrchestrationStatus.COMPLETED + ) + + def raise_orchestration_event( + self, instance_id: str, event_name: str, *, data: Union[Any, None] = None + ): + return mock_raise_event_result + + def terminate_orchestration(self, instance_id: str, *, output: Union[Any, None] = None): + return mock_terminate_result + + def suspend_orchestration(self, instance_id: str): + return mock_suspend_result + + def resume_orchestration(self, instance_id: str): + return mock_resume_result + + def _inner_get_orchestration_state(self, instance_id, state: client.OrchestrationStatus): + return client.OrchestrationState( + instance_id=instance_id, + name='', + runtime_status=state, + created_at=datetime.now(), + last_updated_at=datetime.now(), + serialized_input=None, + serialized_output=None, + serialized_custom_status=None, + failure_details=None, + ) + + +class WorkflowClientTest(unittest.TestCase): + def mock_client_wf(ctx: DaprWorkflowContext, input): + print(f'{input}') + + def test_client_functions(self): + with mock.patch( + 'durabletask.client.TaskHubGrpcClient', return_value=FakeTaskHubGrpcClient() + ): + wfClient = DaprWorkflowClient() + actual_schedule_result = wfClient.schedule_new_workflow( + workflow=self.mock_client_wf, input='Hi Chef!' + ) + assert actual_schedule_result == mock_schedule_result + + actual_get_result = wfClient.get_workflow_state( + instance_id=mockInstanceId, fetch_payloads=True + ) + assert actual_get_result.runtime_status.name == 'PENDING' + assert actual_get_result.instance_id == mockInstanceId + + actual_wait_start_result = wfClient.wait_for_workflow_start( + instance_id=mockInstanceId, timeout_in_seconds=30 + ) + assert actual_wait_start_result.runtime_status.name == 'RUNNING' + assert actual_wait_start_result.instance_id == mockInstanceId + + actual_wait_completion_result = wfClient.wait_for_workflow_completion( + instance_id=mockInstanceId, timeout_in_seconds=30 + ) + assert actual_wait_completion_result.runtime_status.name == 'COMPLETED' + assert actual_wait_completion_result.instance_id == mockInstanceId + + actual_raise_event_result = wfClient.raise_workflow_event( + instance_id=mockInstanceId, event_name='test_event', data='test_data' + ) + assert actual_raise_event_result == mock_raise_event_result + + actual_terminate_result = wfClient.terminate_workflow( + instance_id=mockInstanceId, output='test_output' + ) + assert actual_terminate_result == mock_terminate_result + + actual_suspend_result = wfClient.pause_workflow(instance_id=mockInstanceId) + assert actual_suspend_result == mock_suspend_result + + actual_resume_result = wfClient.resume_workflow(instance_id=mockInstanceId) + assert actual_resume_result == mock_resume_result diff --git a/ext/dapr-ext-workflow/tests/test_workflow_runtime.py b/ext/dapr-ext-workflow/tests/test_workflow_runtime.py index 02d6c6f3b..720e9e9c5 100644 --- a/ext/dapr-ext-workflow/tests/test_workflow_runtime.py +++ b/ext/dapr-ext-workflow/tests/test_workflow_runtime.py @@ -1,172 +1,172 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import List -import unittest -from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext -from unittest import mock -from dapr.ext.workflow.workflow_runtime import WorkflowRuntime, alternate_name -from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext - -listOrchestrators: List[str] = [] -listActivities: List[str] = [] - - -class FakeTaskHubGrpcWorker: - def add_named_orchestrator(self, name: str, fn): - listOrchestrators.append(name) - - def add_named_activity(self, name: str, fn): - listActivities.append(name) - - -class WorkflowRuntimeTest(unittest.TestCase): - def setUp(self): - listActivities.clear() - listOrchestrators.clear() - mock.patch('durabletask.worker._Registry', return_value=FakeTaskHubGrpcWorker()).start() - self.runtime_options = WorkflowRuntime() - if hasattr(self.mock_client_wf, '_dapr_alternate_name'): - del self.mock_client_wf.__dict__['_dapr_alternate_name'] - if hasattr(self.mock_client_activity, '_dapr_alternate_name'): - del self.mock_client_activity.__dict__['_dapr_alternate_name'] - if hasattr(self.mock_client_wf, '_workflow_registered'): - del self.mock_client_wf.__dict__['_workflow_registered'] - if hasattr(self.mock_client_activity, '_activity_registered'): - del self.mock_client_activity.__dict__['_activity_registered'] - - def mock_client_wf(ctx: DaprWorkflowContext, input): - print(f'{input}') - - def mock_client_activity(ctx: WorkflowActivityContext, input): - print(f'{input}!', flush=True) - - def test_register(self): - self.runtime_options.register_workflow(self.mock_client_wf, name='mock_client_wf') - wanted_orchestrator = [self.mock_client_wf.__name__] - assert listOrchestrators == wanted_orchestrator - assert self.mock_client_wf._dapr_alternate_name == 'mock_client_wf' - assert self.mock_client_wf._workflow_registered - - self.runtime_options.register_activity(self.mock_client_activity) - wanted_activity = [self.mock_client_activity.__name__] - assert listActivities == wanted_activity - assert self.mock_client_activity._activity_registered - - def test_decorator_register(self): - client_wf = (self.runtime_options.workflow())(self.mock_client_wf) - wanted_orchestrator = [self.mock_client_wf.__name__] - assert listOrchestrators == wanted_orchestrator - assert client_wf._dapr_alternate_name == self.mock_client_wf.__name__ - assert self.mock_client_wf._workflow_registered - - client_activity = (self.runtime_options.activity())(self.mock_client_activity) - wanted_activity = [self.mock_client_activity.__name__] - assert listActivities == wanted_activity - assert client_activity._dapr_alternate_name == self.mock_client_activity.__name__ - assert self.mock_client_activity._activity_registered - - def test_both_decorator_and_register(self): - client_wf = (self.runtime_options.workflow(name='test_wf'))(self.mock_client_wf) - wanted_orchestrator = ['test_wf'] - assert listOrchestrators == wanted_orchestrator - assert client_wf._dapr_alternate_name == 'test_wf' - assert self.mock_client_wf._workflow_registered - - self.runtime_options.register_activity(self.mock_client_activity, name='test_act') - wanted_activity = ['test_act'] - assert listActivities == wanted_activity - assert hasattr(self.mock_client_activity, '_dapr_alternate_name') - assert self.mock_client_activity._activity_registered - - def test_register_wf_act_using_both_decorator_and_method(self): - client_wf = (self.runtime_options.workflow(name='test_wf'))(self.mock_client_wf) - - wanted_orchestrator = ['test_wf'] - assert listOrchestrators == wanted_orchestrator - assert client_wf._dapr_alternate_name == 'test_wf' - with self.assertRaises(ValueError) as exeception_context: - self.runtime_options.register_workflow(self.mock_client_wf) - wf_name = self.mock_client_wf.__name__ - self.assertEqual( - exeception_context.exception.args[0], - f'Workflow {wf_name} already registered as test_wf', - ) - - client_act = (self.runtime_options.activity(name='test_act'))(self.mock_client_activity) - wanted_activity = ['test_act'] - assert listActivities == wanted_activity - assert client_act._dapr_alternate_name == 'test_act' - with self.assertRaises(ValueError) as exeception_context: - self.runtime_options.register_activity(self.mock_client_activity) - act_name = self.mock_client_activity.__name__ - self.assertEqual( - exeception_context.exception.args[0], - f'Activity {act_name} already registered as test_act', - ) - - def test_duplicate_dapr_alternate_name_registration(self): - client_wf = (alternate_name(name='test'))(self.mock_client_wf) - with self.assertRaises(ValueError) as exeception_context: - (self.runtime_options.workflow(name='random'))(client_wf) - self.assertEqual( - exeception_context.exception.args[0], - f'Workflow {client_wf.__name__} already has an alternate name test', - ) - - client_act = (alternate_name(name='test'))(self.mock_client_activity) - with self.assertRaises(ValueError) as exeception_context: - (self.runtime_options.activity(name='random'))(client_act) - self.assertEqual( - exeception_context.exception.args[0], - f'Activity {client_act.__name__} already has an alternate name test', - ) - - def test_register_wf_act_using_both_decorator_and_method_without_name(self): - client_wf = (self.runtime_options.workflow())(self.mock_client_wf) - - wanted_orchestrator = ['mock_client_wf'] - assert listOrchestrators == wanted_orchestrator - assert client_wf._dapr_alternate_name == 'mock_client_wf' - with self.assertRaises(ValueError) as exeception_context: - self.runtime_options.register_workflow(self.mock_client_wf, name='test_wf') - wf_name = self.mock_client_wf.__name__ - self.assertEqual( - exeception_context.exception.args[0], - f'Workflow {wf_name} already registered as mock_client_wf', - ) - - client_act = (self.runtime_options.activity())(self.mock_client_activity) - wanted_activity = ['mock_client_activity'] - assert listActivities == wanted_activity - assert client_act._dapr_alternate_name == 'mock_client_activity' - with self.assertRaises(ValueError) as exeception_context: - self.runtime_options.register_activity(self.mock_client_activity, name='test_act') - act_name = self.mock_client_activity.__name__ - self.assertEqual( - exeception_context.exception.args[0], - f'Activity {act_name} already registered as mock_client_activity', - ) - - def test_decorator_register_optinal_name(self): - client_wf = (self.runtime_options.workflow(name='test_wf'))(self.mock_client_wf) - wanted_orchestrator = ['test_wf'] - assert listOrchestrators == wanted_orchestrator - assert client_wf._dapr_alternate_name == 'test_wf' - - client_act = (self.runtime_options.activity(name='test_act'))(self.mock_client_activity) - wanted_activity = ['test_act'] - assert listActivities == wanted_activity - assert client_act._dapr_alternate_name == 'test_act' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import List +import unittest +from dapr.ext.workflow.dapr_workflow_context import DaprWorkflowContext +from unittest import mock +from dapr.ext.workflow.workflow_runtime import WorkflowRuntime, alternate_name +from dapr.ext.workflow.workflow_activity_context import WorkflowActivityContext + +listOrchestrators: List[str] = [] +listActivities: List[str] = [] + + +class FakeTaskHubGrpcWorker: + def add_named_orchestrator(self, name: str, fn): + listOrchestrators.append(name) + + def add_named_activity(self, name: str, fn): + listActivities.append(name) + + +class WorkflowRuntimeTest(unittest.TestCase): + def setUp(self): + listActivities.clear() + listOrchestrators.clear() + mock.patch('durabletask.worker._Registry', return_value=FakeTaskHubGrpcWorker()).start() + self.runtime_options = WorkflowRuntime() + if hasattr(self.mock_client_wf, '_dapr_alternate_name'): + del self.mock_client_wf.__dict__['_dapr_alternate_name'] + if hasattr(self.mock_client_activity, '_dapr_alternate_name'): + del self.mock_client_activity.__dict__['_dapr_alternate_name'] + if hasattr(self.mock_client_wf, '_workflow_registered'): + del self.mock_client_wf.__dict__['_workflow_registered'] + if hasattr(self.mock_client_activity, '_activity_registered'): + del self.mock_client_activity.__dict__['_activity_registered'] + + def mock_client_wf(ctx: DaprWorkflowContext, input): + print(f'{input}') + + def mock_client_activity(ctx: WorkflowActivityContext, input): + print(f'{input}!', flush=True) + + def test_register(self): + self.runtime_options.register_workflow(self.mock_client_wf, name='mock_client_wf') + wanted_orchestrator = [self.mock_client_wf.__name__] + assert listOrchestrators == wanted_orchestrator + assert self.mock_client_wf._dapr_alternate_name == 'mock_client_wf' + assert self.mock_client_wf._workflow_registered + + self.runtime_options.register_activity(self.mock_client_activity) + wanted_activity = [self.mock_client_activity.__name__] + assert listActivities == wanted_activity + assert self.mock_client_activity._activity_registered + + def test_decorator_register(self): + client_wf = (self.runtime_options.workflow())(self.mock_client_wf) + wanted_orchestrator = [self.mock_client_wf.__name__] + assert listOrchestrators == wanted_orchestrator + assert client_wf._dapr_alternate_name == self.mock_client_wf.__name__ + assert self.mock_client_wf._workflow_registered + + client_activity = (self.runtime_options.activity())(self.mock_client_activity) + wanted_activity = [self.mock_client_activity.__name__] + assert listActivities == wanted_activity + assert client_activity._dapr_alternate_name == self.mock_client_activity.__name__ + assert self.mock_client_activity._activity_registered + + def test_both_decorator_and_register(self): + client_wf = (self.runtime_options.workflow(name='test_wf'))(self.mock_client_wf) + wanted_orchestrator = ['test_wf'] + assert listOrchestrators == wanted_orchestrator + assert client_wf._dapr_alternate_name == 'test_wf' + assert self.mock_client_wf._workflow_registered + + self.runtime_options.register_activity(self.mock_client_activity, name='test_act') + wanted_activity = ['test_act'] + assert listActivities == wanted_activity + assert hasattr(self.mock_client_activity, '_dapr_alternate_name') + assert self.mock_client_activity._activity_registered + + def test_register_wf_act_using_both_decorator_and_method(self): + client_wf = (self.runtime_options.workflow(name='test_wf'))(self.mock_client_wf) + + wanted_orchestrator = ['test_wf'] + assert listOrchestrators == wanted_orchestrator + assert client_wf._dapr_alternate_name == 'test_wf' + with self.assertRaises(ValueError) as exeception_context: + self.runtime_options.register_workflow(self.mock_client_wf) + wf_name = self.mock_client_wf.__name__ + self.assertEqual( + exeception_context.exception.args[0], + f'Workflow {wf_name} already registered as test_wf', + ) + + client_act = (self.runtime_options.activity(name='test_act'))(self.mock_client_activity) + wanted_activity = ['test_act'] + assert listActivities == wanted_activity + assert client_act._dapr_alternate_name == 'test_act' + with self.assertRaises(ValueError) as exeception_context: + self.runtime_options.register_activity(self.mock_client_activity) + act_name = self.mock_client_activity.__name__ + self.assertEqual( + exeception_context.exception.args[0], + f'Activity {act_name} already registered as test_act', + ) + + def test_duplicate_dapr_alternate_name_registration(self): + client_wf = (alternate_name(name='test'))(self.mock_client_wf) + with self.assertRaises(ValueError) as exeception_context: + (self.runtime_options.workflow(name='random'))(client_wf) + self.assertEqual( + exeception_context.exception.args[0], + f'Workflow {client_wf.__name__} already has an alternate name test', + ) + + client_act = (alternate_name(name='test'))(self.mock_client_activity) + with self.assertRaises(ValueError) as exeception_context: + (self.runtime_options.activity(name='random'))(client_act) + self.assertEqual( + exeception_context.exception.args[0], + f'Activity {client_act.__name__} already has an alternate name test', + ) + + def test_register_wf_act_using_both_decorator_and_method_without_name(self): + client_wf = (self.runtime_options.workflow())(self.mock_client_wf) + + wanted_orchestrator = ['mock_client_wf'] + assert listOrchestrators == wanted_orchestrator + assert client_wf._dapr_alternate_name == 'mock_client_wf' + with self.assertRaises(ValueError) as exeception_context: + self.runtime_options.register_workflow(self.mock_client_wf, name='test_wf') + wf_name = self.mock_client_wf.__name__ + self.assertEqual( + exeception_context.exception.args[0], + f'Workflow {wf_name} already registered as mock_client_wf', + ) + + client_act = (self.runtime_options.activity())(self.mock_client_activity) + wanted_activity = ['mock_client_activity'] + assert listActivities == wanted_activity + assert client_act._dapr_alternate_name == 'mock_client_activity' + with self.assertRaises(ValueError) as exeception_context: + self.runtime_options.register_activity(self.mock_client_activity, name='test_act') + act_name = self.mock_client_activity.__name__ + self.assertEqual( + exeception_context.exception.args[0], + f'Activity {act_name} already registered as mock_client_activity', + ) + + def test_decorator_register_optinal_name(self): + client_wf = (self.runtime_options.workflow(name='test_wf'))(self.mock_client_wf) + wanted_orchestrator = ['test_wf'] + assert listOrchestrators == wanted_orchestrator + assert client_wf._dapr_alternate_name == 'test_wf' + + client_act = (self.runtime_options.activity(name='test_act'))(self.mock_client_activity) + wanted_activity = ['test_act'] + assert listActivities == wanted_activity + assert client_act._dapr_alternate_name == 'test_act' diff --git a/ext/dapr-ext-workflow/tests/test_workflow_util.py b/ext/dapr-ext-workflow/tests/test_workflow_util.py index 878ee7374..1d207b246 100644 --- a/ext/dapr-ext-workflow/tests/test_workflow_util.py +++ b/ext/dapr-ext-workflow/tests/test_workflow_util.py @@ -1,29 +1,29 @@ -import unittest -from dapr.ext.workflow.util import getAddress -from unittest.mock import patch - -from dapr.conf import settings - - -class DaprWorkflowUtilTest(unittest.TestCase): - def test_get_address_default(self): - expected = f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}' - self.assertEqual(expected, getAddress()) - - def test_get_address_with_constructor_arguments(self): - self.assertEqual('test.com:5000', getAddress('test.com', '5000')) - - def test_get_address_with_partial_constructor_arguments(self): - expected = f'{settings.DAPR_RUNTIME_HOST}:5000' - self.assertEqual(expected, getAddress(port='5000')) - - expected = f'test.com:{settings.DAPR_GRPC_PORT}' - self.assertEqual(expected, getAddress(host='test.com')) - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') - def test_get_address_with_constructor_arguments_and_env_variable(self): - self.assertEqual('test.com:5000', getAddress('test.com', '5000')) - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') - def test_get_address_with_env_variable(self): - self.assertEqual('https://domain1.com:5000', getAddress()) +import unittest +from dapr.ext.workflow.util import getAddress +from unittest.mock import patch + +from dapr.conf import settings + + +class DaprWorkflowUtilTest(unittest.TestCase): + def test_get_address_default(self): + expected = f'{settings.DAPR_RUNTIME_HOST}:{settings.DAPR_GRPC_PORT}' + self.assertEqual(expected, getAddress()) + + def test_get_address_with_constructor_arguments(self): + self.assertEqual('test.com:5000', getAddress('test.com', '5000')) + + def test_get_address_with_partial_constructor_arguments(self): + expected = f'{settings.DAPR_RUNTIME_HOST}:5000' + self.assertEqual(expected, getAddress(port='5000')) + + expected = f'test.com:{settings.DAPR_GRPC_PORT}' + self.assertEqual(expected, getAddress(host='test.com')) + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') + def test_get_address_with_constructor_arguments_and_env_variable(self): + self.assertEqual('test.com:5000', getAddress('test.com', '5000')) + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') + def test_get_address_with_env_variable(self): + self.assertEqual('https://domain1.com:5000', getAddress()) diff --git a/ext/flask_dapr/LICENSE b/ext/flask_dapr/LICENSE index be033a7fd..da1ba6b93 100644 --- a/ext/flask_dapr/LICENSE +++ b/ext/flask_dapr/LICENSE @@ -1,203 +1,203 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2021 The Dapr Authors. - - and others that have contributed code to the public domain. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Dapr Authors. + + and others that have contributed code to the public domain. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and limitations under the License. \ No newline at end of file diff --git a/ext/flask_dapr/README.rst b/ext/flask_dapr/README.rst index 015025eb6..866a59601 100644 --- a/ext/flask_dapr/README.rst +++ b/ext/flask_dapr/README.rst @@ -1,39 +1,39 @@ -Dapr flask extension -==================== - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/flask-dapr.svg - :target: https://pypi.org/project/flask-dapr/ - -This flask extension is used to: -- run the actor service -- subscribe to PubSub events - -Installation ------------- - -:: - - pip install flask-dapr - -PubSub Events -------------- - -```python -from flask import Flask, request -from flask_dapr import DaprApp - -app = Flask('myapp') -dapr_app = DaprApp(app) -@dapr_app.subscribe(pubsub='pubsub', topic='some_topic', route='/some_endpoint') -def my_event_handler(): - # request.data contains pubsub event - pass -``` - -References ----------- - -* `Dapr `_ -* `Dapr Python-SDK `_ +Dapr flask extension +==================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/flask-dapr.svg + :target: https://pypi.org/project/flask-dapr/ + +This flask extension is used to: +- run the actor service +- subscribe to PubSub events + +Installation +------------ + +:: + + pip install flask-dapr + +PubSub Events +------------- + +```python +from flask import Flask, request +from flask_dapr import DaprApp + +app = Flask('myapp') +dapr_app = DaprApp(app) +@dapr_app.subscribe(pubsub='pubsub', topic='some_topic', route='/some_endpoint') +def my_event_handler(): + # request.data contains pubsub event + pass +``` + +References +---------- + +* `Dapr `_ +* `Dapr Python-SDK `_ diff --git a/ext/flask_dapr/flask_dapr/__init__.py b/ext/flask_dapr/flask_dapr/__init__.py index e43df65c9..f8eb4db07 100644 --- a/ext/flask_dapr/flask_dapr/__init__.py +++ b/ext/flask_dapr/flask_dapr/__init__.py @@ -1,19 +1,19 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from .actor import DaprActor -from .app import DaprApp - -__all__ = ['DaprActor', 'DaprApp'] +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from .actor import DaprActor +from .app import DaprApp + +__all__ = ['DaprActor', 'DaprApp'] diff --git a/ext/flask_dapr/flask_dapr/actor.py b/ext/flask_dapr/flask_dapr/actor.py index b717de15c..200c07aae 100644 --- a/ext/flask_dapr/flask_dapr/actor.py +++ b/ext/flask_dapr/flask_dapr/actor.py @@ -1,158 +1,158 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import asyncio -from typing import Any, Optional, Type - -from flask import jsonify, make_response, request - -from dapr.actor import Actor, ActorRuntime -from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN -from dapr.serializers import DefaultJSONSerializer - -DEFAULT_CONTENT_TYPE = 'application/json; utf-8' -DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id' - - -class DaprActor(object): - def __init__(self, app=None): - self._app = app - self._dapr_serializer = DefaultJSONSerializer() - - if app is not None: - self.init_routes(app) - - def init_routes(self, app): - app.add_url_rule('/healthz', None, self._healthz_handler, methods=['GET']) - app.add_url_rule('/dapr/config', None, self._config_handler, methods=['GET']) - app.add_url_rule( - '/actors//', - None, - self._deactivation_handler, - methods=['DELETE'], - ) - app.add_url_rule( - '/actors///method/', - None, - self._method_handler, - methods=['PUT'], - ) - app.add_url_rule( - '/actors///method/timer/', - None, - self._timer_handler, - methods=['PUT'], - ) - app.add_url_rule( - '/actors///method/remind/', - None, - self._reminder_handler, - methods=['PUT'], - ) - - def teardown(self, exception): - self._app.logger.debug('actor service is shutting down.') - - def register_actor(self, actor: Type[Actor], **kwargs) -> None: - asyncio.run(ActorRuntime.register_actor(actor, **kwargs)) - self._app.logger.debug(f'registered actor: {actor.__class__.__name__}') - - def _healthz_handler(self): - return wrap_response(200, 'ok') - - def _config_handler(self): - serialized = self._dapr_serializer.serialize(ActorRuntime.get_actor_config()) - return wrap_response(200, serialized) - - def _deactivation_handler(self, actor_type_name, actor_id): - try: - asyncio.run(ActorRuntime.deactivate(actor_type_name, actor_id)) - except DaprInternalError as ex: - return wrap_response(500, ex.as_dict()) - except Exception as ex: - return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) - - msg = f'deactivated actor: {actor_type_name}.{actor_id}' - self._app.logger.debug(msg) - return wrap_response(200, msg) - - def _method_handler(self, actor_type_name, actor_id, method_name): - try: - # Read raw bytes from request stream - req_body = request.stream.read() - reentrancy_id = request.headers.get(DAPR_REENTRANCY_ID_HEADER) - result = asyncio.run( - ActorRuntime.dispatch( - actor_type_name, actor_id, method_name, req_body, reentrancy_id - ) - ) - except DaprInternalError as ex: - return wrap_response(500, ex.as_dict()) - except Exception as ex: - return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) - - msg = f'called method. actor: {actor_type_name}.{actor_id}, method: {method_name}' - self._app.logger.debug(msg) - return wrap_response(200, result) - - def _timer_handler(self, actor_type_name, actor_id, timer_name): - try: - # Read raw bytes from request stream - req_body = request.stream.read() - asyncio.run(ActorRuntime.fire_timer(actor_type_name, actor_id, timer_name, req_body)) - except DaprInternalError as ex: - return wrap_response(500, ex.as_dict()) - except Exception as ex: - return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) - - msg = f'called timer. actor: {actor_type_name}.{actor_id}, timer: {timer_name}' - self._app.logger.debug(msg) - return wrap_response(200, msg) - - def _reminder_handler(self, actor_type_name, actor_id, reminder_name): - try: - # Read raw bytes from request stream - req_body = request.stream.read() - asyncio.run( - ActorRuntime.fire_reminder(actor_type_name, actor_id, reminder_name, req_body) - ) - except DaprInternalError as ex: - return wrap_response(500, ex.as_dict()) - except Exception as ex: - return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) - - msg = f'called reminder. actor: {actor_type_name}.{actor_id}, reminder: {reminder_name}' - self._app.logger.debug(msg) - return wrap_response(200, msg) - - -# wrap_response wraps dapr errors to flask response -def wrap_response( - status: int, msg: Any, error_code: Optional[str] = None, content_type: Optional[str] = None -): - resp = None - if isinstance(msg, str): - response_obj = { - 'message': msg, - } - if not (status >= 200 and status < 300) and error_code: - response_obj['errorCode'] = error_code - resp = make_response(jsonify(response_obj), status) - elif isinstance(msg, bytes): - resp = make_response(msg, status) - else: - resp = make_response(jsonify(msg), status) - resp.headers['Content-type'] = content_type or DEFAULT_CONTENT_TYPE - return resp +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import asyncio +from typing import Any, Optional, Type + +from flask import jsonify, make_response, request + +from dapr.actor import Actor, ActorRuntime +from dapr.clients.exceptions import DaprInternalError, ERROR_CODE_UNKNOWN +from dapr.serializers import DefaultJSONSerializer + +DEFAULT_CONTENT_TYPE = 'application/json; utf-8' +DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id' + + +class DaprActor(object): + def __init__(self, app=None): + self._app = app + self._dapr_serializer = DefaultJSONSerializer() + + if app is not None: + self.init_routes(app) + + def init_routes(self, app): + app.add_url_rule('/healthz', None, self._healthz_handler, methods=['GET']) + app.add_url_rule('/dapr/config', None, self._config_handler, methods=['GET']) + app.add_url_rule( + '/actors//', + None, + self._deactivation_handler, + methods=['DELETE'], + ) + app.add_url_rule( + '/actors///method/', + None, + self._method_handler, + methods=['PUT'], + ) + app.add_url_rule( + '/actors///method/timer/', + None, + self._timer_handler, + methods=['PUT'], + ) + app.add_url_rule( + '/actors///method/remind/', + None, + self._reminder_handler, + methods=['PUT'], + ) + + def teardown(self, exception): + self._app.logger.debug('actor service is shutting down.') + + def register_actor(self, actor: Type[Actor], **kwargs) -> None: + asyncio.run(ActorRuntime.register_actor(actor, **kwargs)) + self._app.logger.debug(f'registered actor: {actor.__class__.__name__}') + + def _healthz_handler(self): + return wrap_response(200, 'ok') + + def _config_handler(self): + serialized = self._dapr_serializer.serialize(ActorRuntime.get_actor_config()) + return wrap_response(200, serialized) + + def _deactivation_handler(self, actor_type_name, actor_id): + try: + asyncio.run(ActorRuntime.deactivate(actor_type_name, actor_id)) + except DaprInternalError as ex: + return wrap_response(500, ex.as_dict()) + except Exception as ex: + return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) + + msg = f'deactivated actor: {actor_type_name}.{actor_id}' + self._app.logger.debug(msg) + return wrap_response(200, msg) + + def _method_handler(self, actor_type_name, actor_id, method_name): + try: + # Read raw bytes from request stream + req_body = request.stream.read() + reentrancy_id = request.headers.get(DAPR_REENTRANCY_ID_HEADER) + result = asyncio.run( + ActorRuntime.dispatch( + actor_type_name, actor_id, method_name, req_body, reentrancy_id + ) + ) + except DaprInternalError as ex: + return wrap_response(500, ex.as_dict()) + except Exception as ex: + return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) + + msg = f'called method. actor: {actor_type_name}.{actor_id}, method: {method_name}' + self._app.logger.debug(msg) + return wrap_response(200, result) + + def _timer_handler(self, actor_type_name, actor_id, timer_name): + try: + # Read raw bytes from request stream + req_body = request.stream.read() + asyncio.run(ActorRuntime.fire_timer(actor_type_name, actor_id, timer_name, req_body)) + except DaprInternalError as ex: + return wrap_response(500, ex.as_dict()) + except Exception as ex: + return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) + + msg = f'called timer. actor: {actor_type_name}.{actor_id}, timer: {timer_name}' + self._app.logger.debug(msg) + return wrap_response(200, msg) + + def _reminder_handler(self, actor_type_name, actor_id, reminder_name): + try: + # Read raw bytes from request stream + req_body = request.stream.read() + asyncio.run( + ActorRuntime.fire_reminder(actor_type_name, actor_id, reminder_name, req_body) + ) + except DaprInternalError as ex: + return wrap_response(500, ex.as_dict()) + except Exception as ex: + return wrap_response(500, repr(ex), ERROR_CODE_UNKNOWN) + + msg = f'called reminder. actor: {actor_type_name}.{actor_id}, reminder: {reminder_name}' + self._app.logger.debug(msg) + return wrap_response(200, msg) + + +# wrap_response wraps dapr errors to flask response +def wrap_response( + status: int, msg: Any, error_code: Optional[str] = None, content_type: Optional[str] = None +): + resp = None + if isinstance(msg, str): + response_obj = { + 'message': msg, + } + if not (status >= 200 and status < 300) and error_code: + response_obj['errorCode'] = error_code + resp = make_response(jsonify(response_obj), status) + elif isinstance(msg, bytes): + resp = make_response(msg, status) + else: + resp = make_response(jsonify(msg), status) + resp.headers['Content-type'] = content_type or DEFAULT_CONTENT_TYPE + return resp diff --git a/ext/flask_dapr/flask_dapr/app.py b/ext/flask_dapr/flask_dapr/app.py index c8d5def92..53796e035 100644 --- a/ext/flask_dapr/flask_dapr/app.py +++ b/ext/flask_dapr/flask_dapr/app.py @@ -1,98 +1,98 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Dict, List, Optional -from flask import Flask, jsonify - - -class DaprApp: - """ - Wraps a regular Flask app instance to enhance it with Dapr specific functionality. - - Args: - app_instance: The Flask instance to wrap. - """ - - def __init__(self, app_instance: Flask): - self._app = app_instance - self._subscriptions: List[Dict[str, object]] = [] - - self._app.add_url_rule( - '/dapr/subscribe', '/dapr/subscribe', self._get_subscriptions, methods=['GET'] - ) - - def subscribe( - self, - pubsub: str, - topic: str, - metadata: Optional[Dict[str, str]] = {}, - route: Optional[str] = None, - dead_letter_topic: Optional[str] = None, - ): - """ - Subscribes to a topic on a pub/sub component. - - Subscriptions made through this method will show up when you GET /dapr/subscribe. - - Example: - The following sample demonstrates how to use the subscribe method to register an - event handler for the application on a pub/sub component named `pubsub`. - - >> app = Flask('myapp') - >> dapr_app = DaprApp(app) - >> @dapr_app.subscribe(pubsub='pubsub', topic='some_topic', route='/some_endpoint') - >> def my_event_handler(): - >> # To get the event data import the flask request object: - >> # from flask import request - >> # request.data contains the event data - >> pass - - Args: - pubsub: The name of the pub/sub component. - topic: The name of the topic. - metadata: The metadata for the subscription. - route: - The HTTP route to register for the event subscription. By default we'll - generate one that matches the pattern /events/{pubsub}/{topic}. You can - override this with your own route. - dead_letter_topic: The name of the dead letter topic to use for the subscription. - - Returns: - The decorator for the function. - """ - - def decorator(func): - event_handler_route = f'/events/{pubsub}/{topic}' if route is None else route - - self._app.add_url_rule(event_handler_route, event_handler_route, func, methods=['POST']) - - self._subscriptions.append( - { - 'pubsubname': pubsub, - 'topic': topic, - 'route': event_handler_route, - 'metadata': metadata, - **( - {'deadLetterTopic': dead_letter_topic} - if dead_letter_topic is not None - else {} - ), - } - ) - - return decorator - - def _get_subscriptions(self): - return jsonify(self._subscriptions) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Dict, List, Optional +from flask import Flask, jsonify + + +class DaprApp: + """ + Wraps a regular Flask app instance to enhance it with Dapr specific functionality. + + Args: + app_instance: The Flask instance to wrap. + """ + + def __init__(self, app_instance: Flask): + self._app = app_instance + self._subscriptions: List[Dict[str, object]] = [] + + self._app.add_url_rule( + '/dapr/subscribe', '/dapr/subscribe', self._get_subscriptions, methods=['GET'] + ) + + def subscribe( + self, + pubsub: str, + topic: str, + metadata: Optional[Dict[str, str]] = {}, + route: Optional[str] = None, + dead_letter_topic: Optional[str] = None, + ): + """ + Subscribes to a topic on a pub/sub component. + + Subscriptions made through this method will show up when you GET /dapr/subscribe. + + Example: + The following sample demonstrates how to use the subscribe method to register an + event handler for the application on a pub/sub component named `pubsub`. + + >> app = Flask('myapp') + >> dapr_app = DaprApp(app) + >> @dapr_app.subscribe(pubsub='pubsub', topic='some_topic', route='/some_endpoint') + >> def my_event_handler(): + >> # To get the event data import the flask request object: + >> # from flask import request + >> # request.data contains the event data + >> pass + + Args: + pubsub: The name of the pub/sub component. + topic: The name of the topic. + metadata: The metadata for the subscription. + route: + The HTTP route to register for the event subscription. By default we'll + generate one that matches the pattern /events/{pubsub}/{topic}. You can + override this with your own route. + dead_letter_topic: The name of the dead letter topic to use for the subscription. + + Returns: + The decorator for the function. + """ + + def decorator(func): + event_handler_route = f'/events/{pubsub}/{topic}' if route is None else route + + self._app.add_url_rule(event_handler_route, event_handler_route, func, methods=['POST']) + + self._subscriptions.append( + { + 'pubsubname': pubsub, + 'topic': topic, + 'route': event_handler_route, + 'metadata': metadata, + **( + {'deadLetterTopic': dead_letter_topic} + if dead_letter_topic is not None + else {} + ), + } + ) + + return decorator + + def _get_subscriptions(self): + return jsonify(self._subscriptions) diff --git a/ext/flask_dapr/flask_dapr/version.py b/ext/flask_dapr/flask_dapr/version.py index 287c4a57c..cc71c630a 100644 --- a/ext/flask_dapr/flask_dapr/version.py +++ b/ext/flask_dapr/flask_dapr/version.py @@ -1,16 +1,16 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -__version__ = '1.14.0rc1.dev' +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +__version__ = '1.14.0rc1.dev' diff --git a/ext/flask_dapr/setup.cfg b/ext/flask_dapr/setup.cfg index 4cccbd36f..0f912c3f4 100644 --- a/ext/flask_dapr/setup.cfg +++ b/ext/flask_dapr/setup.cfg @@ -1,29 +1,29 @@ -[metadata] -url = https://dapr.io/ -author = Dapr Authors -author_email = daprweb@microsoft.com -license = Apache -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 -project_urls = - Documentation = https://github.com/dapr/docs - Source = https://github.com/dapr/python-sdk - -[options] -python_requires = >=3.8 -packages = find: -include_package_data = true -zip_safe = false -install_requires = - Flask >= 1.1 - dapr-dev >= 1.13.0rc1.dev +[metadata] +url = https://dapr.io/ +author = Dapr Authors +author_email = daprweb@microsoft.com +license = Apache +license_file = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 +project_urls = + Documentation = https://github.com/dapr/docs + Source = https://github.com/dapr/python-sdk + +[options] +python_requires = >=3.8 +packages = find: +include_package_data = true +zip_safe = false +install_requires = + Flask >= 1.1 + dapr-dev >= 1.13.0rc1.dev diff --git a/ext/flask_dapr/setup.py b/ext/flask_dapr/setup.py index d2aca0577..0431d5097 100644 --- a/ext/flask_dapr/setup.py +++ b/ext/flask_dapr/setup.py @@ -1,64 +1,64 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os - -from setuptools import setup - -# Load version in dapr package. -version_info = {} -with open('flask_dapr/version.py') as fp: - exec(fp.read(), version_info) -__version__ = version_info['__version__'] - - -def is_release(): - return '.dev' not in __version__ - - -name = 'flask-dapr' -version = __version__ -description = 'The official release of Dapr Python SDK Flask Extension.' -long_description = """ -This is the Flask extension for Dapr. - -Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to -build resilient, stateless and stateful microservices that run on the cloud and edge and -embraces the diversity of languages and developer frameworks. - -Dapr codifies the best practices for building microservice applications into open, -independent, building blocks that enable you to build portable applications with the language -and framework of your choice. Each building block is independent and you can use one, some, -or all of them in your application. -""".lstrip() - -# Get build number from GITHUB_RUN_NUMBER environment variable -build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') - -if not is_release(): - name += '-dev' - version = f'{__version__}{build_number}' - description = 'The developmental release for Dapr Python SDK Flask.' - long_description = 'This is the developmental release for Dapr Python SDK Flask.' - -print(f'package name: {name}, version: {version}', flush=True) - - -setup( - name=name, - version=version, - description=description, - long_description=long_description, -) +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from setuptools import setup + +# Load version in dapr package. +version_info = {} +with open('flask_dapr/version.py') as fp: + exec(fp.read(), version_info) +__version__ = version_info['__version__'] + + +def is_release(): + return '.dev' not in __version__ + + +name = 'flask-dapr' +version = __version__ +description = 'The official release of Dapr Python SDK Flask Extension.' +long_description = """ +This is the Flask extension for Dapr. + +Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to +build resilient, stateless and stateful microservices that run on the cloud and edge and +embraces the diversity of languages and developer frameworks. + +Dapr codifies the best practices for building microservice applications into open, +independent, building blocks that enable you to build portable applications with the language +and framework of your choice. Each building block is independent and you can use one, some, +or all of them in your application. +""".lstrip() + +# Get build number from GITHUB_RUN_NUMBER environment variable +build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') + +if not is_release(): + name += '-dev' + version = f'{__version__}{build_number}' + description = 'The developmental release for Dapr Python SDK Flask.' + long_description = 'This is the developmental release for Dapr Python SDK Flask.' + +print(f'package name: {name}, version: {version}', flush=True) + + +setup( + name=name, + version=version, + description=description, + long_description=long_description, +) diff --git a/ext/flask_dapr/tests/test_app.py b/ext/flask_dapr/tests/test_app.py index 7ddfa14fe..d8562920c 100644 --- a/ext/flask_dapr/tests/test_app.py +++ b/ext/flask_dapr/tests/test_app.py @@ -1,117 +1,117 @@ -import json -import unittest - -from flask import Flask -from flask_dapr import DaprApp - - -class DaprAppTest(unittest.TestCase): - def setUp(self): - self.app = Flask('test_app') - self.app.testing = True - self.dapr_app = DaprApp(self.app) - self.client = self.app.test_client() - - def test_subscribe_subscription_registered(self): - @self.dapr_app.subscribe(pubsub='pubsub', topic='test') - def event_handler(): - return 'default route' - - self.assertEqual(len(self.dapr_app._subscriptions), 1) - - self.assertIn('/dapr/subscribe', [r.rule for r in self.app.url_map.iter_rules()]) - self.assertIn('/events/pubsub/test', [r.rule for r in self.app.url_map.iter_rules()]) - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [ - { - 'pubsubname': 'pubsub', - 'topic': 'test', - 'route': '/events/pubsub/test', - 'metadata': {}, - } - ], - json.loads(response.data), - ) - - response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data.decode('utf-8'), 'default route') - - def test_subscribe_with_route_subscription_registered_with_custom_route(self): - @self.dapr_app.subscribe(pubsub='pubsub', topic='test', route='/do-something') - def event_handler(): - return 'custom route' - - self.assertEqual(len(self.dapr_app._subscriptions), 1) - - self.assertIn('/dapr/subscribe', [r.rule for r in self.app.url_map.iter_rules()]) - self.assertIn('/do-something', [r.rule for r in self.app.url_map.iter_rules()]) - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [{'pubsubname': 'pubsub', 'topic': 'test', 'route': '/do-something', 'metadata': {}}], - json.loads(response.data), - ) - - response = self.client.post('/do-something', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data.decode('utf-8'), 'custom route') - - def test_subscribe_metadata(self): - handler_metadata = {'rawPayload': 'true'} - - @self.dapr_app.subscribe(pubsub='pubsub', topic='test', metadata=handler_metadata) - def event_handler(): - return 'custom metadata' - - self.assertEqual((self.dapr_app._subscriptions[0]['metadata']['rawPayload']), 'true') - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [ - { - 'pubsubname': 'pubsub', - 'topic': 'test', - 'route': '/events/pubsub/test', - 'metadata': {'rawPayload': 'true'}, - } - ], - json.loads(response.data), - ) - - response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data.decode('utf-8'), 'custom metadata') - - def test_subscribe_dead_letter(self): - dead_letter_topic = 'dead-test' - - @self.dapr_app.subscribe(pubsub='pubsub', topic='test', dead_letter_topic=dead_letter_topic) - def event_handler(): - return 'dead letter test' - - self.assertEqual((self.dapr_app._subscriptions[0]['deadLetterTopic']), dead_letter_topic) - - response = self.client.get('/dapr/subscribe') - self.assertEqual( - [ - { - 'pubsubname': 'pubsub', - 'topic': 'test', - 'route': '/events/pubsub/test', - 'metadata': {}, - 'deadLetterTopic': dead_letter_topic, - } - ], - json.loads(response.data), - ) - - response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) - self.assertEqual(response.status_code, 200) - self.assertEqual(response.data.decode('utf-8'), 'dead letter test') - - -if __name__ == '__main__': - unittest.main() +import json +import unittest + +from flask import Flask +from flask_dapr import DaprApp + + +class DaprAppTest(unittest.TestCase): + def setUp(self): + self.app = Flask('test_app') + self.app.testing = True + self.dapr_app = DaprApp(self.app) + self.client = self.app.test_client() + + def test_subscribe_subscription_registered(self): + @self.dapr_app.subscribe(pubsub='pubsub', topic='test') + def event_handler(): + return 'default route' + + self.assertEqual(len(self.dapr_app._subscriptions), 1) + + self.assertIn('/dapr/subscribe', [r.rule for r in self.app.url_map.iter_rules()]) + self.assertIn('/events/pubsub/test', [r.rule for r in self.app.url_map.iter_rules()]) + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [ + { + 'pubsubname': 'pubsub', + 'topic': 'test', + 'route': '/events/pubsub/test', + 'metadata': {}, + } + ], + json.loads(response.data), + ) + + response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data.decode('utf-8'), 'default route') + + def test_subscribe_with_route_subscription_registered_with_custom_route(self): + @self.dapr_app.subscribe(pubsub='pubsub', topic='test', route='/do-something') + def event_handler(): + return 'custom route' + + self.assertEqual(len(self.dapr_app._subscriptions), 1) + + self.assertIn('/dapr/subscribe', [r.rule for r in self.app.url_map.iter_rules()]) + self.assertIn('/do-something', [r.rule for r in self.app.url_map.iter_rules()]) + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [{'pubsubname': 'pubsub', 'topic': 'test', 'route': '/do-something', 'metadata': {}}], + json.loads(response.data), + ) + + response = self.client.post('/do-something', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data.decode('utf-8'), 'custom route') + + def test_subscribe_metadata(self): + handler_metadata = {'rawPayload': 'true'} + + @self.dapr_app.subscribe(pubsub='pubsub', topic='test', metadata=handler_metadata) + def event_handler(): + return 'custom metadata' + + self.assertEqual((self.dapr_app._subscriptions[0]['metadata']['rawPayload']), 'true') + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [ + { + 'pubsubname': 'pubsub', + 'topic': 'test', + 'route': '/events/pubsub/test', + 'metadata': {'rawPayload': 'true'}, + } + ], + json.loads(response.data), + ) + + response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data.decode('utf-8'), 'custom metadata') + + def test_subscribe_dead_letter(self): + dead_letter_topic = 'dead-test' + + @self.dapr_app.subscribe(pubsub='pubsub', topic='test', dead_letter_topic=dead_letter_topic) + def event_handler(): + return 'dead letter test' + + self.assertEqual((self.dapr_app._subscriptions[0]['deadLetterTopic']), dead_letter_topic) + + response = self.client.get('/dapr/subscribe') + self.assertEqual( + [ + { + 'pubsubname': 'pubsub', + 'topic': 'test', + 'route': '/events/pubsub/test', + 'metadata': {}, + 'deadLetterTopic': dead_letter_topic, + } + ], + json.loads(response.data), + ) + + response = self.client.post('/events/pubsub/test', json={'body': 'new message'}) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data.decode('utf-8'), 'dead letter test') + + +if __name__ == '__main__': + unittest.main() diff --git a/mypy.ini b/mypy.ini index 9ae893adb..b1b273e95 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,21 +1,21 @@ -[mypy] -python_version = 3.8 -warn_unused_configs = True -warn_redundant_casts = True -show_error_codes = True -check_untyped_defs = True -install_types = True -non_interactive = True - -files = - dapr/actor/**/*.py, - dapr/clients/**/*.py, - dapr/conf/**/*.py, - dapr/serializers/**/*.py, - ext/dapr-ext-grpc/dapr/**/*.py, - ext/dapr-ext-fastapi/dapr/**/*.py, - ext/flask_dapr/flask_dapr/*.py, - examples/demo_actor/**/*.py - -[mypy-dapr.proto.*] -ignore_errors = True +[mypy] +python_version = 3.8 +warn_unused_configs = True +warn_redundant_casts = True +show_error_codes = True +check_untyped_defs = True +install_types = True +non_interactive = True + +files = + dapr/actor/**/*.py, + dapr/clients/**/*.py, + dapr/conf/**/*.py, + dapr/serializers/**/*.py, + ext/dapr-ext-grpc/dapr/**/*.py, + ext/dapr-ext-fastapi/dapr/**/*.py, + ext/flask_dapr/flask_dapr/*.py, + examples/demo_actor/**/*.py + +[mypy-dapr.proto.*] +ignore_errors = True diff --git a/pyproject.toml b/pyproject.toml index 2b8ddf72e..c9d1a4821 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,21 @@ -[tool.ruff] -target-version = "py38" -line-length = 100 -fix = true -extend-exclude = [".github", "dapr/proto"] -[tool.ruff.lint] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear - "UP", # pyupgrade -] -ignore = [ - # Undefined name {name} - "F821", -] -[tool.ruff.format] -quote-style = 'single' +[tool.ruff] +target-version = "py38" +line-length = 100 +fix = true +extend-exclude = [".github", "dapr/proto"] +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear + "UP", # pyupgrade +] +ignore = [ + # Undefined name {name} + "F821", +] +[tool.ruff.format] +quote-style = 'single' diff --git a/setup.cfg b/setup.cfg index 87c00f369..af140e113 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,68 +1,68 @@ -[metadata] -url = https://dapr.io/ -author = Dapr Authors -author_email = daprweb@microsoft.com -license = Apache -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.11 - Programming Language :: Python :: 3.12 -project_urls = - Documentation = https://github.com/dapr/docs - Source = https://github.com/dapr/python-sdk - -[options] -python_requires = >=3.8 -packages = find_namespace: -include_package_data = True -zip_safe = False -install_requires = - protobuf >= 4.22 - grpcio >= 1.37.0 - grpcio-status>=1.37.0 - aiohttp >= 3.9.0b0 - python-dateutil >= 2.8.1 - typing-extensions>=4.4.0 - -[options.packages.find] -include = - dapr - dapr.* -exclude = - ext - examples - tests - -[options.package_data] -dapr.actor = - py.typed -dapr.clients = - py.typed -dapr.conf = - py.typed -dapr.proto = - py.typed -dapr.serializers = - py.typed - -[flake8] -exclude = - .venv, - venv, - .env, - build, - dist, - .git, - .tox, - dapr/proto, - examples -ignore = F821, E501, W503, E203 -max-line-length = 100 +[metadata] +url = https://dapr.io/ +author = Dapr Authors +author_email = daprweb@microsoft.com +license = Apache +license_file = LICENSE +classifiers = + Development Status :: 5 - Production/Stable + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 +project_urls = + Documentation = https://github.com/dapr/docs + Source = https://github.com/dapr/python-sdk + +[options] +python_requires = >=3.8 +packages = find_namespace: +include_package_data = True +zip_safe = False +install_requires = + protobuf >= 4.22 + grpcio >= 1.37.0 + grpcio-status>=1.37.0 + aiohttp >= 3.9.0b0 + python-dateutil >= 2.8.1 + typing-extensions>=4.4.0 + +[options.packages.find] +include = + dapr + dapr.* +exclude = + ext + examples + tests + +[options.package_data] +dapr.actor = + py.typed +dapr.clients = + py.typed +dapr.conf = + py.typed +dapr.proto = + py.typed +dapr.serializers = + py.typed + +[flake8] +exclude = + .venv, + venv, + .env, + build, + dist, + .git, + .tox, + dapr/proto, + examples +ignore = F821, E501, W503, E203 +max-line-length = 100 diff --git a/setup.py b/setup.py index 75dce736f..33c8360fb 100644 --- a/setup.py +++ b/setup.py @@ -1,62 +1,62 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import os - -from setuptools import setup - -# Load version in dapr package. -version_info = {} -with open('dapr/version/version.py') as fp: - exec(fp.read(), version_info) -__version__ = version_info['__version__'] - - -def is_release(): - return '.dev' not in __version__ - - -name = 'dapr' -version = __version__ -description = 'The official release of Dapr Python SDK.' -long_description = """ -Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to -build resilient, stateless and stateful microservices that run on the cloud and edge and -embraces the diversity of languages and developer frameworks. - -Dapr codifies the best practices for building microservice applications into open, -independent, building blocks that enable you to build portable applications with the language -and framework of your choice. Each building block is independent and you can use one, some, -or all of them in your application. -""".lstrip() - -# Get build number from GITHUB_RUN_NUMBER environment variable -build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') - -if not is_release(): - name += '-dev' - version = f'{__version__}{build_number}' - description = 'The developmental release for Dapr Python SDK.' - long_description = 'This is the developmental release for Dapr Python SDK.' - -print(f'package name: {name}, version: {version}', flush=True) - - -setup( - name=name, - version=version, - description=description, - long_description=long_description, -) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os + +from setuptools import setup + +# Load version in dapr package. +version_info = {} +with open('dapr/version/version.py') as fp: + exec(fp.read(), version_info) +__version__ = version_info['__version__'] + + +def is_release(): + return '.dev' not in __version__ + + +name = 'dapr' +version = __version__ +description = 'The official release of Dapr Python SDK.' +long_description = """ +Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to +build resilient, stateless and stateful microservices that run on the cloud and edge and +embraces the diversity of languages and developer frameworks. + +Dapr codifies the best practices for building microservice applications into open, +independent, building blocks that enable you to build portable applications with the language +and framework of your choice. Each building block is independent and you can use one, some, +or all of them in your application. +""".lstrip() + +# Get build number from GITHUB_RUN_NUMBER environment variable +build_number = os.environ.get('GITHUB_RUN_NUMBER', '0') + +if not is_release(): + name += '-dev' + version = f'{__version__}{build_number}' + description = 'The developmental release for Dapr Python SDK.' + long_description = 'This is the developmental release for Dapr Python SDK.' + +print(f'package name: {name}, version: {version}', flush=True) + + +setup( + name=name, + version=version, + description=description, + long_description=long_description, +) diff --git a/tests/actor/__init__.py b/tests/actor/__init__.py index c9c4314fb..26b94b07d 100644 --- a/tests/actor/__init__.py +++ b/tests/actor/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/tests/actor/fake_actor_classes.py b/tests/actor/fake_actor_classes.py index 50fe63fcf..4c8b8a372 100644 --- a/tests/actor/fake_actor_classes.py +++ b/tests/actor/fake_actor_classes.py @@ -1,219 +1,219 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -from dapr.serializers.json import DefaultJSONSerializer -import asyncio - -from datetime import timedelta -from typing import Optional - -from dapr.actor.runtime.actor import Actor -from dapr.actor.runtime.remindable import Remindable -from dapr.actor.actor_interface import ActorInterface, actormethod - -from dapr.actor.runtime.reentrancy_context import reentrancy_ctx - - -# Fake Simple Actor Class for testing -class FakeSimpleActorInterface(ActorInterface): - @actormethod(name='ActorMethod') - async def actor_method(self, arg: int) -> dict: - ... - - -class FakeSimpleActor(Actor, FakeSimpleActorInterface): - def __init__(self, ctx, actor_id): - super(FakeSimpleActor, self).__init__(ctx, actor_id) - - async def actor_method(self, arg: int) -> dict: - return {'name': 'actor_method'} - - async def non_actor_method(self, arg0: int, arg1: str, arg2: float) -> str: - pass - - -class FakeSimpleReminderActor(Actor, FakeSimpleActorInterface, Remindable): - def __init__(self, ctx, actor_id): - super(FakeSimpleReminderActor, self).__init__(ctx, actor_id) - - async def actor_method(self, arg: int) -> dict: - return {'name': 'actor_method'} - - async def non_actor_method(self, arg0: int, arg1: str, arg2: float) -> str: - pass - - async def receive_reminder( - self, - name: str, - state: bytes, - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta], - ) -> None: - pass - - -class FakeSimpleTimerActor(Actor, FakeSimpleActorInterface): - def __init__(self, ctx, actor_id): - super(FakeSimpleTimerActor, self).__init__(ctx, actor_id) - self.timer_called = False - - async def actor_method(self, arg: int) -> dict: - return {'name': 'actor_method'} - - async def timer_callback(self, obj) -> None: - self.timer_called = True - - async def receive_reminder( - self, - name: str, - state: bytes, - due_time: timedelta, - period: timedelta, - ttl: Optional[timedelta], - ) -> None: - pass - - -class FakeActorCls1Interface(ActorInterface): - # Fake Actor Class deriving multiple ActorInterfaces - @actormethod(name='ActorCls1Method') - async def actor_cls1_method(self, arg): - ... - - @actormethod(name='ActorCls1Method1') - async def actor_cls1_method1(self, arg): - ... - - @actormethod(name='ActorCls1Method2') - async def actor_cls1_method2(self, arg): - ... - - -class FakeActorCls2Interface(ActorInterface): - @actormethod(name='ActorCls2Method') - async def actor_cls2_method(self, arg): - ... - - @actormethod(name='ActionMethod') - async def action(self, data: object) -> str: - ... - - @actormethod(name='ActionMethodWithoutArg') - async def action_no_arg(self) -> str: - ... - - -class ReentrantActorInterface(ActorInterface): - @actormethod(name='ReentrantMethod') - async def reentrant_method(self, data: object) -> str: - ... - - @actormethod(name='ReentrantMethodWithPassthrough') - async def reentrant_pass_through_method(self, arg): - ... - - -class FakeMultiInterfacesActor( - Actor, FakeActorCls1Interface, FakeActorCls2Interface, ReentrantActorInterface -): - def __init__(self, ctx, actor_id): - super(FakeMultiInterfacesActor, self).__init__(ctx, actor_id) - self.activated = False - self.deactivated = False - self.id = actor_id - - async def actor_cls1_method(self, arg): - pass - - async def actor_cls1_method1(self, arg): - pass - - async def actor_cls1_method2(self, arg): - pass - - async def actor_cls2_method(self, arg): - pass - - async def action(self, data: object) -> str: - self.action_data = data - return self.action_data['message'] - - async def action_no_arg(self) -> str: - self.action_data = {'message': 'no_arg'} - return self.action_data['message'] - - async def _on_activate(self): - self.activated = True - self.deactivated = False - - async def _on_deactivate(self): - self.activated = False - self.deactivated = True - - async def reentrant_method(self, data: object) -> str: - self.action_data = data - return self.action_data['message'] - - async def reentrant_pass_through_method(self, arg): - pass - - -class FakeReentrantActor(Actor, FakeActorCls1Interface, ReentrantActorInterface): - def __init__(self, ctx, actor_id): - super(FakeReentrantActor, self).__init__(ctx, actor_id) - - async def reentrant_method(self, data: object) -> str: - return reentrancy_ctx.get() - - async def reentrant_pass_through_method(self, arg): - from dapr.actor.client import proxy - - await proxy.DaprActorHttpClient(DefaultJSONSerializer()).invoke_method( - FakeSlowReentrantActor.__name__, 'test-id', 'ReentrantMethod' - ) - - async def actor_cls1_method(self, arg): - pass - - async def actor_cls1_method1(self, arg): - pass - - async def actor_cls1_method2(self, arg): - pass - - -class FakeSlowReentrantActor(Actor, FakeActorCls2Interface, ReentrantActorInterface): - def __init__(self, ctx, actor_id): - super(FakeSlowReentrantActor, self).__init__(ctx, actor_id) - - async def reentrant_method(self, data: object) -> str: - await asyncio.sleep(1) - return reentrancy_ctx.get() - - async def reentrant_pass_through_method(self, arg): - from dapr.actor.client import proxy - - await proxy.DaprActorHttpClient(DefaultJSONSerializer()).invoke_method( - FakeReentrantActor.__name__, 'test-id', 'ReentrantMethod' - ) - - async def actor_cls2_method(self, arg): - pass - - async def action_no_arg(self) -> str: - pass - - async def action(self, data: object) -> str: - pass +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from dapr.serializers.json import DefaultJSONSerializer +import asyncio + +from datetime import timedelta +from typing import Optional + +from dapr.actor.runtime.actor import Actor +from dapr.actor.runtime.remindable import Remindable +from dapr.actor.actor_interface import ActorInterface, actormethod + +from dapr.actor.runtime.reentrancy_context import reentrancy_ctx + + +# Fake Simple Actor Class for testing +class FakeSimpleActorInterface(ActorInterface): + @actormethod(name='ActorMethod') + async def actor_method(self, arg: int) -> dict: + ... + + +class FakeSimpleActor(Actor, FakeSimpleActorInterface): + def __init__(self, ctx, actor_id): + super(FakeSimpleActor, self).__init__(ctx, actor_id) + + async def actor_method(self, arg: int) -> dict: + return {'name': 'actor_method'} + + async def non_actor_method(self, arg0: int, arg1: str, arg2: float) -> str: + pass + + +class FakeSimpleReminderActor(Actor, FakeSimpleActorInterface, Remindable): + def __init__(self, ctx, actor_id): + super(FakeSimpleReminderActor, self).__init__(ctx, actor_id) + + async def actor_method(self, arg: int) -> dict: + return {'name': 'actor_method'} + + async def non_actor_method(self, arg0: int, arg1: str, arg2: float) -> str: + pass + + async def receive_reminder( + self, + name: str, + state: bytes, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta], + ) -> None: + pass + + +class FakeSimpleTimerActor(Actor, FakeSimpleActorInterface): + def __init__(self, ctx, actor_id): + super(FakeSimpleTimerActor, self).__init__(ctx, actor_id) + self.timer_called = False + + async def actor_method(self, arg: int) -> dict: + return {'name': 'actor_method'} + + async def timer_callback(self, obj) -> None: + self.timer_called = True + + async def receive_reminder( + self, + name: str, + state: bytes, + due_time: timedelta, + period: timedelta, + ttl: Optional[timedelta], + ) -> None: + pass + + +class FakeActorCls1Interface(ActorInterface): + # Fake Actor Class deriving multiple ActorInterfaces + @actormethod(name='ActorCls1Method') + async def actor_cls1_method(self, arg): + ... + + @actormethod(name='ActorCls1Method1') + async def actor_cls1_method1(self, arg): + ... + + @actormethod(name='ActorCls1Method2') + async def actor_cls1_method2(self, arg): + ... + + +class FakeActorCls2Interface(ActorInterface): + @actormethod(name='ActorCls2Method') + async def actor_cls2_method(self, arg): + ... + + @actormethod(name='ActionMethod') + async def action(self, data: object) -> str: + ... + + @actormethod(name='ActionMethodWithoutArg') + async def action_no_arg(self) -> str: + ... + + +class ReentrantActorInterface(ActorInterface): + @actormethod(name='ReentrantMethod') + async def reentrant_method(self, data: object) -> str: + ... + + @actormethod(name='ReentrantMethodWithPassthrough') + async def reentrant_pass_through_method(self, arg): + ... + + +class FakeMultiInterfacesActor( + Actor, FakeActorCls1Interface, FakeActorCls2Interface, ReentrantActorInterface +): + def __init__(self, ctx, actor_id): + super(FakeMultiInterfacesActor, self).__init__(ctx, actor_id) + self.activated = False + self.deactivated = False + self.id = actor_id + + async def actor_cls1_method(self, arg): + pass + + async def actor_cls1_method1(self, arg): + pass + + async def actor_cls1_method2(self, arg): + pass + + async def actor_cls2_method(self, arg): + pass + + async def action(self, data: object) -> str: + self.action_data = data + return self.action_data['message'] + + async def action_no_arg(self) -> str: + self.action_data = {'message': 'no_arg'} + return self.action_data['message'] + + async def _on_activate(self): + self.activated = True + self.deactivated = False + + async def _on_deactivate(self): + self.activated = False + self.deactivated = True + + async def reentrant_method(self, data: object) -> str: + self.action_data = data + return self.action_data['message'] + + async def reentrant_pass_through_method(self, arg): + pass + + +class FakeReentrantActor(Actor, FakeActorCls1Interface, ReentrantActorInterface): + def __init__(self, ctx, actor_id): + super(FakeReentrantActor, self).__init__(ctx, actor_id) + + async def reentrant_method(self, data: object) -> str: + return reentrancy_ctx.get() + + async def reentrant_pass_through_method(self, arg): + from dapr.actor.client import proxy + + await proxy.DaprActorHttpClient(DefaultJSONSerializer()).invoke_method( + FakeSlowReentrantActor.__name__, 'test-id', 'ReentrantMethod' + ) + + async def actor_cls1_method(self, arg): + pass + + async def actor_cls1_method1(self, arg): + pass + + async def actor_cls1_method2(self, arg): + pass + + +class FakeSlowReentrantActor(Actor, FakeActorCls2Interface, ReentrantActorInterface): + def __init__(self, ctx, actor_id): + super(FakeSlowReentrantActor, self).__init__(ctx, actor_id) + + async def reentrant_method(self, data: object) -> str: + await asyncio.sleep(1) + return reentrancy_ctx.get() + + async def reentrant_pass_through_method(self, arg): + from dapr.actor.client import proxy + + await proxy.DaprActorHttpClient(DefaultJSONSerializer()).invoke_method( + FakeReentrantActor.__name__, 'test-id', 'ReentrantMethod' + ) + + async def actor_cls2_method(self, arg): + pass + + async def action_no_arg(self) -> str: + pass + + async def action(self, data: object) -> str: + pass diff --git a/tests/actor/fake_client.py b/tests/actor/fake_client.py index fa5fe1577..351e9b432 100644 --- a/tests/actor/fake_client.py +++ b/tests/actor/fake_client.py @@ -1,72 +1,72 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from dapr.clients import DaprActorClientBase -from typing import Optional - - -# Fake Dapr Actor Client Base Class for testing -class FakeDaprActorClientBase(DaprActorClientBase): - async def invoke_method( - self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None - ) -> bytes: - ... - - async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: - ... - - async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: - ... - - async def register_reminder( - self, actor_type: str, actor_id: str, name: str, data: bytes - ) -> None: - ... - - async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: - ... - - async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: - ... - - async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: - ... - - -class FakeDaprActorClient(FakeDaprActorClientBase): - async def invoke_method( - self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None - ) -> bytes: - return b'"expected_response"' - - async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: - pass - - async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: - return b'"expected_response"' - - async def register_reminder( - self, actor_type: str, actor_id: str, name: str, data: bytes - ) -> None: - pass - - async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: - pass - - async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: - pass - - async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: - pass +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from dapr.clients import DaprActorClientBase +from typing import Optional + + +# Fake Dapr Actor Client Base Class for testing +class FakeDaprActorClientBase(DaprActorClientBase): + async def invoke_method( + self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None + ) -> bytes: + ... + + async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: + ... + + async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: + ... + + async def register_reminder( + self, actor_type: str, actor_id: str, name: str, data: bytes + ) -> None: + ... + + async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: + ... + + async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: + ... + + async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: + ... + + +class FakeDaprActorClient(FakeDaprActorClientBase): + async def invoke_method( + self, actor_type: str, actor_id: str, method: str, data: Optional[bytes] = None + ) -> bytes: + return b'"expected_response"' + + async def save_state_transactionally(self, actor_type: str, actor_id: str, data: bytes) -> None: + pass + + async def get_state(self, actor_type: str, actor_id: str, name: str) -> bytes: + return b'"expected_response"' + + async def register_reminder( + self, actor_type: str, actor_id: str, name: str, data: bytes + ) -> None: + pass + + async def unregister_reminder(self, actor_type: str, actor_id: str, name: str) -> None: + pass + + async def register_timer(self, actor_type: str, actor_id: str, name: str, data: bytes) -> None: + pass + + async def unregister_timer(self, actor_type: str, actor_id: str, name: str) -> None: + pass diff --git a/tests/actor/test_actor.py b/tests/actor/test_actor.py index d9b602c9d..d3c64f87b 100644 --- a/tests/actor/test_actor.py +++ b/tests/actor/test_actor.py @@ -1,206 +1,206 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from unittest import mock -from datetime import timedelta - -from dapr.actor.id import ActorId -from dapr.actor.runtime.config import ActorRuntimeConfig -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.actor.runtime.runtime import ActorRuntime -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.conf import settings -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import ( - FakeSimpleActor, - FakeSimpleReminderActor, - FakeSimpleTimerActor, - FakeMultiInterfacesActor, -) - -from tests.actor.fake_client import FakeDaprActorClient -from tests.actor.utils import _async_mock, _run -from tests.clients.fake_http_server import FakeHttpServer - - -class ActorTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.server = FakeHttpServer(3500) - cls.server.start() - settings.DAPR_HTTP_PORT = 3500 - - @classmethod - def tearDownClass(cls): - cls.server.shutdown_server() - - def setUp(self): - ActorRuntime._actor_managers = {} - ActorRuntime.set_actor_config(ActorRuntimeConfig()) - self._serializer = DefaultJSONSerializer() - _run(ActorRuntime.register_actor(FakeSimpleActor)) - _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) - - def test_get_registered_actor_types(self): - actor_types = ActorRuntime.get_registered_actor_types() - self.assertTrue(actor_types.index('FakeSimpleActor') >= 0) - self.assertTrue(actor_types.index(FakeMultiInterfacesActor.__name__) >= 0) - - def test_actor_config(self): - config = ActorRuntime.get_actor_config() - - self.assertTrue(config._drain_rebalanced_actors) - self.assertEqual(timedelta(hours=1), config._actor_idle_timeout) - self.assertEqual(timedelta(seconds=30), config._actor_scan_interval) - self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) - self.assertEqual(2, len(config._entities)) - - # apply new config - new_config = ActorRuntimeConfig( - timedelta(hours=3), timedelta(seconds=10), timedelta(minutes=1), False - ) - - ActorRuntime.set_actor_config(new_config) - config = ActorRuntime.get_actor_config() - - self.assertFalse(config._drain_rebalanced_actors) - self.assertEqual(timedelta(hours=3), config._actor_idle_timeout) - self.assertEqual(timedelta(seconds=10), config._actor_scan_interval) - self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) - self.assertEqual(2, len(config._entities)) - - def test_entities_update(self): - # Clean up managers - ActorRuntime._actor_managers = {} - ActorRuntime.set_actor_config(ActorRuntimeConfig()) - - config = ActorRuntime.get_actor_config() - self.assertFalse(FakeSimpleActor.__name__ in config._entities) - - _run(ActorRuntime.register_actor(FakeSimpleActor)) - config = ActorRuntime.get_actor_config() - self.assertTrue(FakeSimpleActor.__name__ in config._entities) - - def test_dispatch(self): - _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) - - request_body = { - 'message': 'hello dapr', - } - - test_request_body = self._serializer.serialize(request_body) - response = _run( - ActorRuntime.dispatch( - FakeMultiInterfacesActor.__name__, 'test-id', 'ActionMethod', test_request_body - ) - ) - - self.assertEqual(b'"hello dapr"', response) - - _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) - - # Ensure test-id is deactivated - with self.assertRaises(ValueError): - _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.register_reminder', - new=_async_mock(return_value=b'"ok"'), - ) - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.unregister_reminder', - new=_async_mock(return_value=b'"ok"'), - ) - def test_register_reminder(self): - test_actor_id = ActorId('test_id') - test_type_info = ActorTypeInformation.create(FakeSimpleReminderActor) - test_client = FakeDaprActorClient - ctx = ActorRuntimeContext(test_type_info, self._serializer, self._serializer, test_client) - test_actor = FakeSimpleReminderActor(ctx, test_actor_id) - - # register reminder - _run( - test_actor.register_reminder( - 'test_reminder', b'reminder_message', timedelta(seconds=1), timedelta(seconds=1) - ) - ) - test_client.register_reminder.mock.assert_called_once() - test_client.register_reminder.mock.assert_called_with( - 'FakeSimpleReminderActor', - 'test_id', - 'test_reminder', - b'{"reminderName":"test_reminder","dueTime":"0h0m1s0ms0\\u03bcs","period":"0h0m1s0ms0\\u03bcs","data":"cmVtaW5kZXJfbWVzc2FnZQ=="}', - ) # noqa E501 - - # unregister reminder - _run(test_actor.unregister_reminder('test_reminder')) - test_client.unregister_reminder.mock.assert_called_once() - test_client.unregister_reminder.mock.assert_called_with( - 'FakeSimpleReminderActor', 'test_id', 'test_reminder' - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.register_timer', - new=_async_mock(return_value=b'"ok"'), - ) - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.unregister_timer', - new=_async_mock(return_value=b'"ok"'), - ) - def test_register_timer(self): - test_actor_id = ActorId('test_id') - test_type_info = ActorTypeInformation.create(FakeSimpleTimerActor) - test_client = FakeDaprActorClient - ctx = ActorRuntimeContext(test_type_info, self._serializer, self._serializer, test_client) - test_actor = FakeSimpleTimerActor(ctx, test_actor_id) - - # register timer - _run( - test_actor.register_timer( - 'test_timer', - test_actor.timer_callback, - 'mydata', - timedelta(seconds=1), - timedelta(seconds=2), - ) - ) - test_client.register_timer.mock.assert_called_once() - test_client.register_timer.mock.assert_called_with( - 'FakeSimpleTimerActor', - 'test_id', - 'test_timer', - b'{"callback":"timer_callback","data":"mydata","dueTime":"0h0m1s0ms0\\u03bcs","period":"0h0m2s0ms0\\u03bcs"}', - ) # noqa E501 - - # unregister timer - _run(test_actor.unregister_timer('test_timer')) - test_client.unregister_timer.mock.assert_called_once() - test_client.unregister_timer.mock.assert_called_with( - 'FakeSimpleTimerActor', 'test_id', 'test_timer' - ) - - # register timer without timer name - _run( - test_actor.register_timer( - None, - test_actor.timer_callback, - 'timer call', - timedelta(seconds=1), - timedelta(seconds=1), - ) - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from unittest import mock +from datetime import timedelta + +from dapr.actor.id import ActorId +from dapr.actor.runtime.config import ActorRuntimeConfig +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.actor.runtime.runtime import ActorRuntime +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.conf import settings +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import ( + FakeSimpleActor, + FakeSimpleReminderActor, + FakeSimpleTimerActor, + FakeMultiInterfacesActor, +) + +from tests.actor.fake_client import FakeDaprActorClient +from tests.actor.utils import _async_mock, _run +from tests.clients.fake_http_server import FakeHttpServer + + +class ActorTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.server = FakeHttpServer(3500) + cls.server.start() + settings.DAPR_HTTP_PORT = 3500 + + @classmethod + def tearDownClass(cls): + cls.server.shutdown_server() + + def setUp(self): + ActorRuntime._actor_managers = {} + ActorRuntime.set_actor_config(ActorRuntimeConfig()) + self._serializer = DefaultJSONSerializer() + _run(ActorRuntime.register_actor(FakeSimpleActor)) + _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) + + def test_get_registered_actor_types(self): + actor_types = ActorRuntime.get_registered_actor_types() + self.assertTrue(actor_types.index('FakeSimpleActor') >= 0) + self.assertTrue(actor_types.index(FakeMultiInterfacesActor.__name__) >= 0) + + def test_actor_config(self): + config = ActorRuntime.get_actor_config() + + self.assertTrue(config._drain_rebalanced_actors) + self.assertEqual(timedelta(hours=1), config._actor_idle_timeout) + self.assertEqual(timedelta(seconds=30), config._actor_scan_interval) + self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) + self.assertEqual(2, len(config._entities)) + + # apply new config + new_config = ActorRuntimeConfig( + timedelta(hours=3), timedelta(seconds=10), timedelta(minutes=1), False + ) + + ActorRuntime.set_actor_config(new_config) + config = ActorRuntime.get_actor_config() + + self.assertFalse(config._drain_rebalanced_actors) + self.assertEqual(timedelta(hours=3), config._actor_idle_timeout) + self.assertEqual(timedelta(seconds=10), config._actor_scan_interval) + self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) + self.assertEqual(2, len(config._entities)) + + def test_entities_update(self): + # Clean up managers + ActorRuntime._actor_managers = {} + ActorRuntime.set_actor_config(ActorRuntimeConfig()) + + config = ActorRuntime.get_actor_config() + self.assertFalse(FakeSimpleActor.__name__ in config._entities) + + _run(ActorRuntime.register_actor(FakeSimpleActor)) + config = ActorRuntime.get_actor_config() + self.assertTrue(FakeSimpleActor.__name__ in config._entities) + + def test_dispatch(self): + _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) + + request_body = { + 'message': 'hello dapr', + } + + test_request_body = self._serializer.serialize(request_body) + response = _run( + ActorRuntime.dispatch( + FakeMultiInterfacesActor.__name__, 'test-id', 'ActionMethod', test_request_body + ) + ) + + self.assertEqual(b'"hello dapr"', response) + + _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) + + # Ensure test-id is deactivated + with self.assertRaises(ValueError): + _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.register_reminder', + new=_async_mock(return_value=b'"ok"'), + ) + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.unregister_reminder', + new=_async_mock(return_value=b'"ok"'), + ) + def test_register_reminder(self): + test_actor_id = ActorId('test_id') + test_type_info = ActorTypeInformation.create(FakeSimpleReminderActor) + test_client = FakeDaprActorClient + ctx = ActorRuntimeContext(test_type_info, self._serializer, self._serializer, test_client) + test_actor = FakeSimpleReminderActor(ctx, test_actor_id) + + # register reminder + _run( + test_actor.register_reminder( + 'test_reminder', b'reminder_message', timedelta(seconds=1), timedelta(seconds=1) + ) + ) + test_client.register_reminder.mock.assert_called_once() + test_client.register_reminder.mock.assert_called_with( + 'FakeSimpleReminderActor', + 'test_id', + 'test_reminder', + b'{"reminderName":"test_reminder","dueTime":"0h0m1s0ms0\\u03bcs","period":"0h0m1s0ms0\\u03bcs","data":"cmVtaW5kZXJfbWVzc2FnZQ=="}', + ) # noqa E501 + + # unregister reminder + _run(test_actor.unregister_reminder('test_reminder')) + test_client.unregister_reminder.mock.assert_called_once() + test_client.unregister_reminder.mock.assert_called_with( + 'FakeSimpleReminderActor', 'test_id', 'test_reminder' + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.register_timer', + new=_async_mock(return_value=b'"ok"'), + ) + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.unregister_timer', + new=_async_mock(return_value=b'"ok"'), + ) + def test_register_timer(self): + test_actor_id = ActorId('test_id') + test_type_info = ActorTypeInformation.create(FakeSimpleTimerActor) + test_client = FakeDaprActorClient + ctx = ActorRuntimeContext(test_type_info, self._serializer, self._serializer, test_client) + test_actor = FakeSimpleTimerActor(ctx, test_actor_id) + + # register timer + _run( + test_actor.register_timer( + 'test_timer', + test_actor.timer_callback, + 'mydata', + timedelta(seconds=1), + timedelta(seconds=2), + ) + ) + test_client.register_timer.mock.assert_called_once() + test_client.register_timer.mock.assert_called_with( + 'FakeSimpleTimerActor', + 'test_id', + 'test_timer', + b'{"callback":"timer_callback","data":"mydata","dueTime":"0h0m1s0ms0\\u03bcs","period":"0h0m2s0ms0\\u03bcs"}', + ) # noqa E501 + + # unregister timer + _run(test_actor.unregister_timer('test_timer')) + test_client.unregister_timer.mock.assert_called_once() + test_client.unregister_timer.mock.assert_called_with( + 'FakeSimpleTimerActor', 'test_id', 'test_timer' + ) + + # register timer without timer name + _run( + test_actor.register_timer( + None, + test_actor.timer_callback, + 'timer call', + timedelta(seconds=1), + timedelta(seconds=1), + ) + ) diff --git a/tests/actor/test_actor_factory.py b/tests/actor/test_actor_factory.py index 0715c33f4..093e4e634 100644 --- a/tests/actor/test_actor_factory.py +++ b/tests/actor/test_actor_factory.py @@ -1,96 +1,96 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.actor import Actor -from dapr.actor.id import ActorId -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.actor.runtime.manager import ActorManager -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import ( - FakeSimpleActorInterface, -) - -from tests.actor.fake_client import FakeDaprActorClient - -from tests.actor.utils import _run - - -class FakeDependency: - def __init__(self, value: str): - self.value = value - - def get_value(self) -> str: - return self.value - - -class FakeSimpleActorWithDependency(Actor, FakeSimpleActorInterface): - def __init__(self, ctx, actor_id, dependency: FakeDependency): - super(FakeSimpleActorWithDependency, self).__init__(ctx, actor_id) - self.dependency = dependency - - async def actor_method(self, arg: int) -> dict: - return {'name': f'{arg}-{self.dependency.get_value()}'} - - async def _on_activate(self): - self.activated = True - self.deactivated = False - - async def _on_deactivate(self): - self.activated = False - self.deactivated = True - - -def an_actor_factory(ctx: 'ActorRuntimeContext', actor_id: ActorId) -> 'Actor': - dependency = FakeDependency('some-value') - return ctx.actor_type_info.implementation_type(ctx, actor_id, dependency) - - -class ActorFactoryTests(unittest.TestCase): - def setUp(self): - self._test_type_info = ActorTypeInformation.create(FakeSimpleActorWithDependency) - self._serializer = DefaultJSONSerializer() - - self._fake_client = FakeDaprActorClient - self._runtime_ctx = ActorRuntimeContext( - self._test_type_info, - self._serializer, - self._serializer, - self._fake_client, - an_actor_factory, - ) - self._manager = ActorManager(self._runtime_ctx) - - def test_activate_actor(self): - """Activate ActorId(1)""" - test_actor_id = ActorId('1') - _run(self._manager.activate_actor(test_actor_id)) - - # assert - self.assertEqual(test_actor_id, self._manager._active_actors[test_actor_id.id].id) - self.assertTrue(self._manager._active_actors[test_actor_id.id].activated) - self.assertFalse(self._manager._active_actors[test_actor_id.id].deactivated) - - def test_dispatch_success(self): - """dispatch ActionMethod""" - test_actor_id = ActorId('dispatch') - _run(self._manager.activate_actor(test_actor_id)) - - test_request_body = b'5' - response = _run(self._manager.dispatch(test_actor_id, 'ActorMethod', test_request_body)) - self.assertEqual(b'{"name":"5-some-value"}', response) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.actor import Actor +from dapr.actor.id import ActorId +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.actor.runtime.manager import ActorManager +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import ( + FakeSimpleActorInterface, +) + +from tests.actor.fake_client import FakeDaprActorClient + +from tests.actor.utils import _run + + +class FakeDependency: + def __init__(self, value: str): + self.value = value + + def get_value(self) -> str: + return self.value + + +class FakeSimpleActorWithDependency(Actor, FakeSimpleActorInterface): + def __init__(self, ctx, actor_id, dependency: FakeDependency): + super(FakeSimpleActorWithDependency, self).__init__(ctx, actor_id) + self.dependency = dependency + + async def actor_method(self, arg: int) -> dict: + return {'name': f'{arg}-{self.dependency.get_value()}'} + + async def _on_activate(self): + self.activated = True + self.deactivated = False + + async def _on_deactivate(self): + self.activated = False + self.deactivated = True + + +def an_actor_factory(ctx: 'ActorRuntimeContext', actor_id: ActorId) -> 'Actor': + dependency = FakeDependency('some-value') + return ctx.actor_type_info.implementation_type(ctx, actor_id, dependency) + + +class ActorFactoryTests(unittest.TestCase): + def setUp(self): + self._test_type_info = ActorTypeInformation.create(FakeSimpleActorWithDependency) + self._serializer = DefaultJSONSerializer() + + self._fake_client = FakeDaprActorClient + self._runtime_ctx = ActorRuntimeContext( + self._test_type_info, + self._serializer, + self._serializer, + self._fake_client, + an_actor_factory, + ) + self._manager = ActorManager(self._runtime_ctx) + + def test_activate_actor(self): + """Activate ActorId(1)""" + test_actor_id = ActorId('1') + _run(self._manager.activate_actor(test_actor_id)) + + # assert + self.assertEqual(test_actor_id, self._manager._active_actors[test_actor_id.id].id) + self.assertTrue(self._manager._active_actors[test_actor_id.id].activated) + self.assertFalse(self._manager._active_actors[test_actor_id.id].deactivated) + + def test_dispatch_success(self): + """dispatch ActionMethod""" + test_actor_id = ActorId('dispatch') + _run(self._manager.activate_actor(test_actor_id)) + + test_request_body = b'5' + response = _run(self._manager.dispatch(test_actor_id, 'ActorMethod', test_request_body)) + self.assertEqual(b'{"name":"5-some-value"}', response) diff --git a/tests/actor/test_actor_id.py b/tests/actor/test_actor_id.py index 7fa341411..dc15eab21 100644 --- a/tests/actor/test_actor_id.py +++ b/tests/actor/test_actor_id.py @@ -1,40 +1,40 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.actor.id import ActorId - - -class ActorIdTests(unittest.TestCase): - def test_create_actor_id(self): - actor_id_1 = ActorId('1') - self.assertEqual('1', actor_id_1.id) - - def test_create_random_id(self): - actor_id_random = ActorId.create_random_id() - self.assertEqual(len('f56d5aec5b3b11ea9121acde48001122'), len(actor_id_random.id)) - - def test_get_hash(self): - actor_test_id = ActorId('testId') - self.assertIsNotNone(actor_test_id.__hash__) - - def test_comparison(self): - actor_id_1 = ActorId('1') - actor_id_1a = ActorId('1') - self.assertTrue(actor_id_1 == actor_id_1a) - - actor_id_2 = ActorId('2') - self.assertFalse(actor_id_1 == actor_id_2) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.actor.id import ActorId + + +class ActorIdTests(unittest.TestCase): + def test_create_actor_id(self): + actor_id_1 = ActorId('1') + self.assertEqual('1', actor_id_1.id) + + def test_create_random_id(self): + actor_id_random = ActorId.create_random_id() + self.assertEqual(len('f56d5aec5b3b11ea9121acde48001122'), len(actor_id_random.id)) + + def test_get_hash(self): + actor_test_id = ActorId('testId') + self.assertIsNotNone(actor_test_id.__hash__) + + def test_comparison(self): + actor_id_1 = ActorId('1') + actor_id_1a = ActorId('1') + self.assertTrue(actor_id_1 == actor_id_1a) + + actor_id_2 = ActorId('2') + self.assertFalse(actor_id_1 == actor_id_2) diff --git a/tests/actor/test_actor_manager.py b/tests/actor/test_actor_manager.py index 6c21abfb7..9e18338e2 100644 --- a/tests/actor/test_actor_manager.py +++ b/tests/actor/test_actor_manager.py @@ -1,167 +1,167 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -from datetime import timedelta -from unittest import mock - -from dapr.actor.id import ActorId -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.actor.runtime.manager import ActorManager -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import ( - FakeMultiInterfacesActor, - FakeSimpleActor, - FakeSimpleReminderActor, - FakeSimpleTimerActor, -) - -from tests.actor.fake_client import FakeDaprActorClient - -from tests.actor.utils import ( - _async_mock, - _run, -) - - -class ActorManagerTests(unittest.TestCase): - def setUp(self): - self._test_type_info = ActorTypeInformation.create(FakeMultiInterfacesActor) - self._serializer = DefaultJSONSerializer() - - self._fake_client = FakeDaprActorClient - self._runtime_ctx = ActorRuntimeContext( - self._test_type_info, self._serializer, self._serializer, self._fake_client - ) - self._manager = ActorManager(self._runtime_ctx) - - def test_activate_actor(self): - """Activate ActorId(1)""" - test_actor_id = ActorId('1') - _run(self._manager.activate_actor(test_actor_id)) - - # assert - self.assertEqual(test_actor_id, self._manager._active_actors[test_actor_id.id].id) - self.assertTrue(self._manager._active_actors[test_actor_id.id].activated) - self.assertFalse(self._manager._active_actors[test_actor_id.id].deactivated) - - def test_deactivate_actor(self): - """Activate ActorId('2') and deactivate it""" - test_actor_id = ActorId('2') - _run(self._manager.activate_actor(test_actor_id)) - - # assert - self.assertEqual(test_actor_id, self._manager._active_actors[test_actor_id.id].id) - self.assertTrue(self._manager._active_actors[test_actor_id.id].activated) - self.assertFalse(self._manager._active_actors[test_actor_id.id].deactivated) - - _run(self._manager.deactivate_actor(test_actor_id)) - self.assertIsNone(self._manager._active_actors.get(test_actor_id.id)) - - def test_dispatch_success(self): - """dispatch ActionMethod""" - test_actor_id = ActorId('dispatch') - _run(self._manager.activate_actor(test_actor_id)) - - request_body = { - 'message': 'hello dapr', - } - - test_request_body = self._serializer.serialize(request_body) - response = _run(self._manager.dispatch(test_actor_id, 'ActionMethod', test_request_body)) - self.assertEqual(b'"hello dapr"', response) - - -class ActorManagerReminderTests(unittest.TestCase): - def setUp(self): - self._serializer = DefaultJSONSerializer() - self._fake_client = FakeDaprActorClient - - self._test_reminder_req = self._serializer.serialize( - { - 'name': 'test_reminder', - 'dueTime': timedelta(seconds=1), - 'period': timedelta(seconds=1), - 'ttl': timedelta(seconds=1), - 'data': 'cmVtaW5kZXJfc3RhdGU=', - } - ) - - def test_fire_reminder_for_non_reminderable(self): - test_type_info = ActorTypeInformation.create(FakeSimpleActor) - ctx = ActorRuntimeContext( - test_type_info, self._serializer, self._serializer, self._fake_client - ) - manager = ActorManager(ctx) - with self.assertRaises(ValueError): - _run(manager.fire_reminder(ActorId('testid'), 'test_reminder', self._test_reminder_req)) - - def test_fire_reminder_success(self): - test_actor_id = ActorId('testid') - test_type_info = ActorTypeInformation.create(FakeSimpleReminderActor) - ctx = ActorRuntimeContext( - test_type_info, self._serializer, self._serializer, self._fake_client - ) - manager = ActorManager(ctx) - _run(manager.activate_actor(test_actor_id)) - _run(manager.fire_reminder(test_actor_id, 'test_reminder', self._test_reminder_req)) - - -class ActorManagerTimerTests(unittest.TestCase): - def setUp(self): - self._serializer = DefaultJSONSerializer() - - self._fake_client = FakeDaprActorClient - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', - new=_async_mock(return_value=b'"expected_response"'), - ) - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.register_timer', new=_async_mock()) - def test_fire_timer_success(self): - test_actor_id = ActorId('testid') - test_type_info = ActorTypeInformation.create(FakeSimpleTimerActor) - ctx = ActorRuntimeContext( - test_type_info, self._serializer, self._serializer, self._fake_client - ) - manager = ActorManager(ctx) - - _run(manager.activate_actor(test_actor_id)) - actor = manager._active_actors.get(test_actor_id.id, None) - - # Setup timer - _run( - actor.register_timer( - 'test_timer', - actor.timer_callback, - 'timer call', - timedelta(seconds=1), - timedelta(seconds=1), - timedelta(seconds=1), - ) - ) - - # Fire timer - _run( - manager.fire_timer( - test_actor_id, - 'test_timer', - '{ "callback": "timer_callback", "data": "timer call" }'.encode('UTF8'), - ) - ) - - self.assertTrue(actor.timer_called) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +from datetime import timedelta +from unittest import mock + +from dapr.actor.id import ActorId +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.actor.runtime.manager import ActorManager +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import ( + FakeMultiInterfacesActor, + FakeSimpleActor, + FakeSimpleReminderActor, + FakeSimpleTimerActor, +) + +from tests.actor.fake_client import FakeDaprActorClient + +from tests.actor.utils import ( + _async_mock, + _run, +) + + +class ActorManagerTests(unittest.TestCase): + def setUp(self): + self._test_type_info = ActorTypeInformation.create(FakeMultiInterfacesActor) + self._serializer = DefaultJSONSerializer() + + self._fake_client = FakeDaprActorClient + self._runtime_ctx = ActorRuntimeContext( + self._test_type_info, self._serializer, self._serializer, self._fake_client + ) + self._manager = ActorManager(self._runtime_ctx) + + def test_activate_actor(self): + """Activate ActorId(1)""" + test_actor_id = ActorId('1') + _run(self._manager.activate_actor(test_actor_id)) + + # assert + self.assertEqual(test_actor_id, self._manager._active_actors[test_actor_id.id].id) + self.assertTrue(self._manager._active_actors[test_actor_id.id].activated) + self.assertFalse(self._manager._active_actors[test_actor_id.id].deactivated) + + def test_deactivate_actor(self): + """Activate ActorId('2') and deactivate it""" + test_actor_id = ActorId('2') + _run(self._manager.activate_actor(test_actor_id)) + + # assert + self.assertEqual(test_actor_id, self._manager._active_actors[test_actor_id.id].id) + self.assertTrue(self._manager._active_actors[test_actor_id.id].activated) + self.assertFalse(self._manager._active_actors[test_actor_id.id].deactivated) + + _run(self._manager.deactivate_actor(test_actor_id)) + self.assertIsNone(self._manager._active_actors.get(test_actor_id.id)) + + def test_dispatch_success(self): + """dispatch ActionMethod""" + test_actor_id = ActorId('dispatch') + _run(self._manager.activate_actor(test_actor_id)) + + request_body = { + 'message': 'hello dapr', + } + + test_request_body = self._serializer.serialize(request_body) + response = _run(self._manager.dispatch(test_actor_id, 'ActionMethod', test_request_body)) + self.assertEqual(b'"hello dapr"', response) + + +class ActorManagerReminderTests(unittest.TestCase): + def setUp(self): + self._serializer = DefaultJSONSerializer() + self._fake_client = FakeDaprActorClient + + self._test_reminder_req = self._serializer.serialize( + { + 'name': 'test_reminder', + 'dueTime': timedelta(seconds=1), + 'period': timedelta(seconds=1), + 'ttl': timedelta(seconds=1), + 'data': 'cmVtaW5kZXJfc3RhdGU=', + } + ) + + def test_fire_reminder_for_non_reminderable(self): + test_type_info = ActorTypeInformation.create(FakeSimpleActor) + ctx = ActorRuntimeContext( + test_type_info, self._serializer, self._serializer, self._fake_client + ) + manager = ActorManager(ctx) + with self.assertRaises(ValueError): + _run(manager.fire_reminder(ActorId('testid'), 'test_reminder', self._test_reminder_req)) + + def test_fire_reminder_success(self): + test_actor_id = ActorId('testid') + test_type_info = ActorTypeInformation.create(FakeSimpleReminderActor) + ctx = ActorRuntimeContext( + test_type_info, self._serializer, self._serializer, self._fake_client + ) + manager = ActorManager(ctx) + _run(manager.activate_actor(test_actor_id)) + _run(manager.fire_reminder(test_actor_id, 'test_reminder', self._test_reminder_req)) + + +class ActorManagerTimerTests(unittest.TestCase): + def setUp(self): + self._serializer = DefaultJSONSerializer() + + self._fake_client = FakeDaprActorClient + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', + new=_async_mock(return_value=b'"expected_response"'), + ) + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.register_timer', new=_async_mock()) + def test_fire_timer_success(self): + test_actor_id = ActorId('testid') + test_type_info = ActorTypeInformation.create(FakeSimpleTimerActor) + ctx = ActorRuntimeContext( + test_type_info, self._serializer, self._serializer, self._fake_client + ) + manager = ActorManager(ctx) + + _run(manager.activate_actor(test_actor_id)) + actor = manager._active_actors.get(test_actor_id.id, None) + + # Setup timer + _run( + actor.register_timer( + 'test_timer', + actor.timer_callback, + 'timer call', + timedelta(seconds=1), + timedelta(seconds=1), + timedelta(seconds=1), + ) + ) + + # Fire timer + _run( + manager.fire_timer( + test_actor_id, + 'test_timer', + '{ "callback": "timer_callback", "data": "timer call" }'.encode('UTF8'), + ) + ) + + self.assertTrue(actor.timer_called) diff --git a/tests/actor/test_actor_reentrancy.py b/tests/actor/test_actor_reentrancy.py index 834273f41..9bbef4d67 100644 --- a/tests/actor/test_actor_reentrancy.py +++ b/tests/actor/test_actor_reentrancy.py @@ -1,277 +1,277 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -import asyncio - -from unittest import mock - -from dapr.actor.runtime.runtime import ActorRuntime -from dapr.actor.runtime.config import ActorRuntimeConfig, ActorReentrancyConfig -from dapr.conf import settings -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import ( - FakeReentrantActor, - FakeMultiInterfacesActor, - FakeSlowReentrantActor, -) - -from tests.actor.utils import _run -from tests.clients.fake_http_server import FakeHttpServer - - -class ActorRuntimeTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.server = FakeHttpServer(3500) - cls.server.start() - settings.DAPR_HTTP_PORT = 3500 - - @classmethod - def tearDownClass(cls): - cls.server.shutdown_server() - - def setUp(self): - ActorRuntime._actor_managers = {} - ActorRuntime.set_actor_config( - ActorRuntimeConfig(reentrancy=ActorReentrancyConfig(enabled=True)) - ) - self._serializer = DefaultJSONSerializer() - _run(ActorRuntime.register_actor(FakeReentrantActor)) - _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) - _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) - - def test_reentrant_dispatch(self): - _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) - - request_body = { - 'message': 'hello dapr', - } - - reentrancy_id = '0faa4c8b-f53a-4dff-9a9d-c50205035085' - - test_request_body = self._serializer.serialize(request_body) - response = _run( - ActorRuntime.dispatch( - FakeMultiInterfacesActor.__name__, - 'test-id', - 'ReentrantMethod', - test_request_body, - reentrancy_id=reentrancy_id, - ) - ) - - self.assertEqual(b'"hello dapr"', response) - - _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) - - # Ensure test-id is deactivated - with self.assertRaises(ValueError): - _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) - - def test_interleaved_reentrant_actor_dispatch(self): - _run(ActorRuntime.register_actor(FakeReentrantActor)) - _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) - - request_body = self._serializer.serialize( - { - 'message': 'Normal', - } - ) - - normal_reentrancy_id = 'f6319f23-dc0a-4880-90d9-87b23c19c20a' - slow_reentrancy_id = 'b1653a2f-fe54-4514-8197-98b52d156454' - - async def dispatchReentrantCall(actorName: str, method: str, reentrancy_id: str): - return await ActorRuntime.dispatch( - actorName, 'test-id', method, request_body, reentrancy_id=reentrancy_id - ) - - async def run_parallel_actors(): - slow = dispatchReentrantCall( - FakeSlowReentrantActor.__name__, 'ReentrantMethod', slow_reentrancy_id - ) - normal = dispatchReentrantCall( - FakeReentrantActor.__name__, 'ReentrantMethod', normal_reentrancy_id - ) - - res = await asyncio.gather(slow, normal) - self.slow_res = res[0] - self.normal_res = res[1] - - _run(run_parallel_actors()) - - self.assertEqual(self.normal_res, bytes('"' + normal_reentrancy_id + '"', 'utf-8')) - self.assertEqual(self.slow_res, bytes('"' + slow_reentrancy_id + '"', 'utf-8')) - - _run(ActorRuntime.deactivate(FakeSlowReentrantActor.__name__, 'test-id')) - _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) - - # Ensure test-id is deactivated - with self.assertRaises(ValueError): - _run(ActorRuntime.deactivate(FakeSlowReentrantActor.__name__, 'test-id')) - _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) - - def test_reentrancy_header_passthrough(self): - _run(ActorRuntime.register_actor(FakeReentrantActor)) - _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) - - request_body = self._serializer.serialize( - { - 'message': 'Normal', - } - ) - - async def expected_return_value(*args, **kwargs): - return ['expected', 'None'] - - reentrancy_id = 'f6319f23-dc0a-4880-90d9-87b23c19c20a' - actor = FakeSlowReentrantActor.__name__ - method = 'ReentrantMethod' - - with mock.patch('dapr.clients.http.client.DaprHttpClient.send_bytes') as mocked: - mocked.side_effect = expected_return_value - _run( - ActorRuntime.dispatch( - FakeReentrantActor.__name__, - 'test-id', - 'ReentrantMethodWithPassthrough', - request_body, - reentrancy_id=reentrancy_id, - ) - ) - - mocked.assert_called_with( - method='POST', - url=f'http://127.0.0.1:3500/v1.0/actors/{actor}/test-id/method/{method}', - data=None, - headers={'Dapr-Reentrancy-Id': reentrancy_id}, - ) - - _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) - - # Ensure test-id is deactivated - with self.assertRaises(ValueError): - _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) - - def test_header_passthrough_reentrancy_disabled(self): - config = ActorRuntimeConfig(reentrancy=None) - ActorRuntime.set_actor_config(config) - _run(ActorRuntime.register_actor(FakeReentrantActor)) - _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) - - request_body = self._serializer.serialize( - { - 'message': 'Normal', - } - ) - - async def expected_return_value(*args, **kwargs): - return ['expected', 'None'] - - reentrancy_id = None # the runtime would not pass this header - actor = FakeSlowReentrantActor.__name__ - method = 'ReentrantMethod' - - with mock.patch('dapr.clients.http.client.DaprHttpClient.send_bytes') as mocked: - mocked.side_effect = expected_return_value - _run( - ActorRuntime.dispatch( - FakeReentrantActor.__name__, - 'test-id', - 'ReentrantMethodWithPassthrough', - request_body, - reentrancy_id=reentrancy_id, - ) - ) - - mocked.assert_called_with( - method='POST', - url=f'http://127.0.0.1:3500/v1.0/actors/{actor}/test-id/method/{method}', - data=None, - headers={}, - ) - - _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) - - # Ensure test-id is deactivated - with self.assertRaises(ValueError): - _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) - - def test_parse_incoming_reentrancy_header_flask(self): - from ext.flask_dapr import flask_dapr - from flask import Flask - - app = Flask(f'{FakeReentrantActor.__name__}Service') - flask_dapr.DaprActor(app) - - reentrancy_id = 'b1653a2f-fe54-4514-8197-98b52d156454' - actor_type_name = FakeReentrantActor.__name__ - actor_id = 'test-id' - method_name = 'ReentrantMethod' - - request_body = self._serializer.serialize( - { - 'message': 'Normal', - } - ) - - relativeUrl = f'/actors/{actor_type_name}/{actor_id}/method/{method_name}' - - with mock.patch('dapr.actor.runtime.runtime.ActorRuntime.dispatch') as mocked: - client = app.test_client() - mocked.return_value = None - client.put( - relativeUrl, - headers={flask_dapr.actor.DAPR_REENTRANCY_ID_HEADER: reentrancy_id}, - data=request_body, - ) - mocked.assert_called_with( - actor_type_name, actor_id, method_name, request_body, reentrancy_id - ) - - def test_parse_incoming_reentrancy_header_fastapi(self): - from fastapi import FastAPI - from fastapi.testclient import TestClient - from dapr.ext import fastapi - - app = FastAPI(title=f'{FakeReentrantActor.__name__}Service') - fastapi.DaprActor(app) - - reentrancy_id = 'b1653a2f-fe54-4514-8197-98b52d156454' - actor_type_name = FakeReentrantActor.__name__ - actor_id = 'test-id' - method_name = 'ReentrantMethod' - - request_body = self._serializer.serialize( - { - 'message': 'Normal', - } - ) - - relativeUrl = f'/actors/{actor_type_name}/{actor_id}/method/{method_name}' - - with mock.patch('dapr.actor.runtime.runtime.ActorRuntime.dispatch') as mocked: - client = TestClient(app) - mocked.return_value = None - client.put( - relativeUrl, - headers={fastapi.actor.DAPR_REENTRANCY_ID_HEADER: reentrancy_id}, - data=request_body, - ) - mocked.assert_called_with( - actor_type_name, actor_id, method_name, request_body, reentrancy_id - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +import asyncio + +from unittest import mock + +from dapr.actor.runtime.runtime import ActorRuntime +from dapr.actor.runtime.config import ActorRuntimeConfig, ActorReentrancyConfig +from dapr.conf import settings +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import ( + FakeReentrantActor, + FakeMultiInterfacesActor, + FakeSlowReentrantActor, +) + +from tests.actor.utils import _run +from tests.clients.fake_http_server import FakeHttpServer + + +class ActorRuntimeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.server = FakeHttpServer(3500) + cls.server.start() + settings.DAPR_HTTP_PORT = 3500 + + @classmethod + def tearDownClass(cls): + cls.server.shutdown_server() + + def setUp(self): + ActorRuntime._actor_managers = {} + ActorRuntime.set_actor_config( + ActorRuntimeConfig(reentrancy=ActorReentrancyConfig(enabled=True)) + ) + self._serializer = DefaultJSONSerializer() + _run(ActorRuntime.register_actor(FakeReentrantActor)) + _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) + _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) + + def test_reentrant_dispatch(self): + _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) + + request_body = { + 'message': 'hello dapr', + } + + reentrancy_id = '0faa4c8b-f53a-4dff-9a9d-c50205035085' + + test_request_body = self._serializer.serialize(request_body) + response = _run( + ActorRuntime.dispatch( + FakeMultiInterfacesActor.__name__, + 'test-id', + 'ReentrantMethod', + test_request_body, + reentrancy_id=reentrancy_id, + ) + ) + + self.assertEqual(b'"hello dapr"', response) + + _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) + + # Ensure test-id is deactivated + with self.assertRaises(ValueError): + _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) + + def test_interleaved_reentrant_actor_dispatch(self): + _run(ActorRuntime.register_actor(FakeReentrantActor)) + _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) + + request_body = self._serializer.serialize( + { + 'message': 'Normal', + } + ) + + normal_reentrancy_id = 'f6319f23-dc0a-4880-90d9-87b23c19c20a' + slow_reentrancy_id = 'b1653a2f-fe54-4514-8197-98b52d156454' + + async def dispatchReentrantCall(actorName: str, method: str, reentrancy_id: str): + return await ActorRuntime.dispatch( + actorName, 'test-id', method, request_body, reentrancy_id=reentrancy_id + ) + + async def run_parallel_actors(): + slow = dispatchReentrantCall( + FakeSlowReentrantActor.__name__, 'ReentrantMethod', slow_reentrancy_id + ) + normal = dispatchReentrantCall( + FakeReentrantActor.__name__, 'ReentrantMethod', normal_reentrancy_id + ) + + res = await asyncio.gather(slow, normal) + self.slow_res = res[0] + self.normal_res = res[1] + + _run(run_parallel_actors()) + + self.assertEqual(self.normal_res, bytes('"' + normal_reentrancy_id + '"', 'utf-8')) + self.assertEqual(self.slow_res, bytes('"' + slow_reentrancy_id + '"', 'utf-8')) + + _run(ActorRuntime.deactivate(FakeSlowReentrantActor.__name__, 'test-id')) + _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) + + # Ensure test-id is deactivated + with self.assertRaises(ValueError): + _run(ActorRuntime.deactivate(FakeSlowReentrantActor.__name__, 'test-id')) + _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) + + def test_reentrancy_header_passthrough(self): + _run(ActorRuntime.register_actor(FakeReentrantActor)) + _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) + + request_body = self._serializer.serialize( + { + 'message': 'Normal', + } + ) + + async def expected_return_value(*args, **kwargs): + return ['expected', 'None'] + + reentrancy_id = 'f6319f23-dc0a-4880-90d9-87b23c19c20a' + actor = FakeSlowReentrantActor.__name__ + method = 'ReentrantMethod' + + with mock.patch('dapr.clients.http.client.DaprHttpClient.send_bytes') as mocked: + mocked.side_effect = expected_return_value + _run( + ActorRuntime.dispatch( + FakeReentrantActor.__name__, + 'test-id', + 'ReentrantMethodWithPassthrough', + request_body, + reentrancy_id=reentrancy_id, + ) + ) + + mocked.assert_called_with( + method='POST', + url=f'http://127.0.0.1:3500/v1.0/actors/{actor}/test-id/method/{method}', + data=None, + headers={'Dapr-Reentrancy-Id': reentrancy_id}, + ) + + _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) + + # Ensure test-id is deactivated + with self.assertRaises(ValueError): + _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) + + def test_header_passthrough_reentrancy_disabled(self): + config = ActorRuntimeConfig(reentrancy=None) + ActorRuntime.set_actor_config(config) + _run(ActorRuntime.register_actor(FakeReentrantActor)) + _run(ActorRuntime.register_actor(FakeSlowReentrantActor)) + + request_body = self._serializer.serialize( + { + 'message': 'Normal', + } + ) + + async def expected_return_value(*args, **kwargs): + return ['expected', 'None'] + + reentrancy_id = None # the runtime would not pass this header + actor = FakeSlowReentrantActor.__name__ + method = 'ReentrantMethod' + + with mock.patch('dapr.clients.http.client.DaprHttpClient.send_bytes') as mocked: + mocked.side_effect = expected_return_value + _run( + ActorRuntime.dispatch( + FakeReentrantActor.__name__, + 'test-id', + 'ReentrantMethodWithPassthrough', + request_body, + reentrancy_id=reentrancy_id, + ) + ) + + mocked.assert_called_with( + method='POST', + url=f'http://127.0.0.1:3500/v1.0/actors/{actor}/test-id/method/{method}', + data=None, + headers={}, + ) + + _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) + + # Ensure test-id is deactivated + with self.assertRaises(ValueError): + _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id')) + + def test_parse_incoming_reentrancy_header_flask(self): + from ext.flask_dapr import flask_dapr + from flask import Flask + + app = Flask(f'{FakeReentrantActor.__name__}Service') + flask_dapr.DaprActor(app) + + reentrancy_id = 'b1653a2f-fe54-4514-8197-98b52d156454' + actor_type_name = FakeReentrantActor.__name__ + actor_id = 'test-id' + method_name = 'ReentrantMethod' + + request_body = self._serializer.serialize( + { + 'message': 'Normal', + } + ) + + relativeUrl = f'/actors/{actor_type_name}/{actor_id}/method/{method_name}' + + with mock.patch('dapr.actor.runtime.runtime.ActorRuntime.dispatch') as mocked: + client = app.test_client() + mocked.return_value = None + client.put( + relativeUrl, + headers={flask_dapr.actor.DAPR_REENTRANCY_ID_HEADER: reentrancy_id}, + data=request_body, + ) + mocked.assert_called_with( + actor_type_name, actor_id, method_name, request_body, reentrancy_id + ) + + def test_parse_incoming_reentrancy_header_fastapi(self): + from fastapi import FastAPI + from fastapi.testclient import TestClient + from dapr.ext import fastapi + + app = FastAPI(title=f'{FakeReentrantActor.__name__}Service') + fastapi.DaprActor(app) + + reentrancy_id = 'b1653a2f-fe54-4514-8197-98b52d156454' + actor_type_name = FakeReentrantActor.__name__ + actor_id = 'test-id' + method_name = 'ReentrantMethod' + + request_body = self._serializer.serialize( + { + 'message': 'Normal', + } + ) + + relativeUrl = f'/actors/{actor_type_name}/{actor_id}/method/{method_name}' + + with mock.patch('dapr.actor.runtime.runtime.ActorRuntime.dispatch') as mocked: + client = TestClient(app) + mocked.return_value = None + client.put( + relativeUrl, + headers={fastapi.actor.DAPR_REENTRANCY_ID_HEADER: reentrancy_id}, + data=request_body, + ) + mocked.assert_called_with( + actor_type_name, actor_id, method_name, request_body, reentrancy_id + ) diff --git a/tests/actor/test_actor_runtime.py b/tests/actor/test_actor_runtime.py index f17f96cc8..65808031d 100644 --- a/tests/actor/test_actor_runtime.py +++ b/tests/actor/test_actor_runtime.py @@ -1,141 +1,141 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from datetime import timedelta - -from dapr.actor.runtime.runtime import ActorRuntime -from dapr.actor.runtime.config import ActorRuntimeConfig -from dapr.conf import settings -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import ( - FakeSimpleActor, - FakeMultiInterfacesActor, - FakeSimpleTimerActor, -) - -from tests.actor.utils import _run -from tests.clients.fake_http_server import FakeHttpServer - - -class ActorRuntimeTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.server = FakeHttpServer(3500) - cls.server.start() - settings.DAPR_HTTP_PORT = 3500 - - @classmethod - def tearDownClass(cls): - cls.server.shutdown_server() - - def setUp(self): - ActorRuntime._actor_managers = {} - ActorRuntime.set_actor_config(ActorRuntimeConfig()) - self._serializer = DefaultJSONSerializer() - _run(ActorRuntime.register_actor(FakeSimpleActor)) - _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) - _run(ActorRuntime.register_actor(FakeSimpleTimerActor)) - - def test_get_registered_actor_types(self): - actor_types = ActorRuntime.get_registered_actor_types() - self.assertTrue(actor_types.index('FakeSimpleActor') >= 0) - self.assertTrue(actor_types.index(FakeMultiInterfacesActor.__name__) >= 0) - self.assertTrue(actor_types.index(FakeSimpleTimerActor.__name__) >= 0) - - def test_actor_config(self): - config = ActorRuntime.get_actor_config() - - self.assertTrue(config._drain_rebalanced_actors) - self.assertEqual(timedelta(hours=1), config._actor_idle_timeout) - self.assertEqual(timedelta(seconds=30), config._actor_scan_interval) - self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) - self.assertEqual(3, len(config._entities)) - - # apply new config - new_config = ActorRuntimeConfig( - timedelta(hours=3), timedelta(seconds=10), timedelta(minutes=1), False - ) - - ActorRuntime.set_actor_config(new_config) - config = ActorRuntime.get_actor_config() - - self.assertFalse(config._drain_rebalanced_actors) - self.assertEqual(timedelta(hours=3), config._actor_idle_timeout) - self.assertEqual(timedelta(seconds=10), config._actor_scan_interval) - self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) - self.assertEqual(3, len(config._entities)) - - def test_entities_update(self): - # Clean up managers - ActorRuntime._actor_managers = {} - ActorRuntime.set_actor_config(ActorRuntimeConfig()) - - config = ActorRuntime.get_actor_config() - self.assertFalse(FakeSimpleActor.__name__ in config._entities) - - _run(ActorRuntime.register_actor(FakeSimpleActor)) - config = ActorRuntime.get_actor_config() - self.assertTrue(FakeSimpleActor.__name__ in config._entities) - - def test_dispatch(self): - _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) - - request_body = { - 'message': 'hello dapr', - } - - test_request_body = self._serializer.serialize(request_body) - response = _run( - ActorRuntime.dispatch( - FakeMultiInterfacesActor.__name__, 'test-id', 'ActionMethod', test_request_body - ) - ) - - self.assertEqual(b'"hello dapr"', response) - - _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) - - # Ensure test-id is deactivated - with self.assertRaises(ValueError): - _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) - - def test_fire_timer_success(self): - # Fire timer - _run( - ActorRuntime.fire_timer( - FakeSimpleTimerActor.__name__, - 'test-id', - 'test_timer', - '{ "callback": "timer_callback", "data": "timer call" }'.encode('UTF8'), - ) - ) - - manager = ActorRuntime._actor_managers[FakeSimpleTimerActor.__name__] - actor = manager._active_actors['test-id'] - self.assertTrue(actor.timer_called) - - def test_fire_timer_unregistered(self): - with self.assertRaises(ValueError): - _run( - ActorRuntime.fire_timer( - 'UnknownType', - 'test-id', - 'test_timer', - '{ "callback": "timer_callback", "data": "timer call" }'.encode('UTF8'), - ) - ) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from datetime import timedelta + +from dapr.actor.runtime.runtime import ActorRuntime +from dapr.actor.runtime.config import ActorRuntimeConfig +from dapr.conf import settings +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import ( + FakeSimpleActor, + FakeMultiInterfacesActor, + FakeSimpleTimerActor, +) + +from tests.actor.utils import _run +from tests.clients.fake_http_server import FakeHttpServer + + +class ActorRuntimeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.server = FakeHttpServer(3500) + cls.server.start() + settings.DAPR_HTTP_PORT = 3500 + + @classmethod + def tearDownClass(cls): + cls.server.shutdown_server() + + def setUp(self): + ActorRuntime._actor_managers = {} + ActorRuntime.set_actor_config(ActorRuntimeConfig()) + self._serializer = DefaultJSONSerializer() + _run(ActorRuntime.register_actor(FakeSimpleActor)) + _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) + _run(ActorRuntime.register_actor(FakeSimpleTimerActor)) + + def test_get_registered_actor_types(self): + actor_types = ActorRuntime.get_registered_actor_types() + self.assertTrue(actor_types.index('FakeSimpleActor') >= 0) + self.assertTrue(actor_types.index(FakeMultiInterfacesActor.__name__) >= 0) + self.assertTrue(actor_types.index(FakeSimpleTimerActor.__name__) >= 0) + + def test_actor_config(self): + config = ActorRuntime.get_actor_config() + + self.assertTrue(config._drain_rebalanced_actors) + self.assertEqual(timedelta(hours=1), config._actor_idle_timeout) + self.assertEqual(timedelta(seconds=30), config._actor_scan_interval) + self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) + self.assertEqual(3, len(config._entities)) + + # apply new config + new_config = ActorRuntimeConfig( + timedelta(hours=3), timedelta(seconds=10), timedelta(minutes=1), False + ) + + ActorRuntime.set_actor_config(new_config) + config = ActorRuntime.get_actor_config() + + self.assertFalse(config._drain_rebalanced_actors) + self.assertEqual(timedelta(hours=3), config._actor_idle_timeout) + self.assertEqual(timedelta(seconds=10), config._actor_scan_interval) + self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout) + self.assertEqual(3, len(config._entities)) + + def test_entities_update(self): + # Clean up managers + ActorRuntime._actor_managers = {} + ActorRuntime.set_actor_config(ActorRuntimeConfig()) + + config = ActorRuntime.get_actor_config() + self.assertFalse(FakeSimpleActor.__name__ in config._entities) + + _run(ActorRuntime.register_actor(FakeSimpleActor)) + config = ActorRuntime.get_actor_config() + self.assertTrue(FakeSimpleActor.__name__ in config._entities) + + def test_dispatch(self): + _run(ActorRuntime.register_actor(FakeMultiInterfacesActor)) + + request_body = { + 'message': 'hello dapr', + } + + test_request_body = self._serializer.serialize(request_body) + response = _run( + ActorRuntime.dispatch( + FakeMultiInterfacesActor.__name__, 'test-id', 'ActionMethod', test_request_body + ) + ) + + self.assertEqual(b'"hello dapr"', response) + + _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) + + # Ensure test-id is deactivated + with self.assertRaises(ValueError): + _run(ActorRuntime.deactivate(FakeMultiInterfacesActor.__name__, 'test-id')) + + def test_fire_timer_success(self): + # Fire timer + _run( + ActorRuntime.fire_timer( + FakeSimpleTimerActor.__name__, + 'test-id', + 'test_timer', + '{ "callback": "timer_callback", "data": "timer call" }'.encode('UTF8'), + ) + ) + + manager = ActorRuntime._actor_managers[FakeSimpleTimerActor.__name__] + actor = manager._active_actors['test-id'] + self.assertTrue(actor.timer_called) + + def test_fire_timer_unregistered(self): + with self.assertRaises(ValueError): + _run( + ActorRuntime.fire_timer( + 'UnknownType', + 'test-id', + 'test_timer', + '{ "callback": "timer_callback", "data": "timer call" }'.encode('UTF8'), + ) + ) diff --git a/tests/actor/test_actor_runtime_config.py b/tests/actor/test_actor_runtime_config.py index 7bbd8cefc..fc65d107f 100644 --- a/tests/actor/test_actor_runtime_config.py +++ b/tests/actor/test_actor_runtime_config.py @@ -1,175 +1,175 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from datetime import timedelta -from dapr.actor.runtime.config import ActorRuntimeConfig, ActorReentrancyConfig, ActorTypeConfig - - -class ActorTypeConfigTests(unittest.TestCase): - def test_default_config(self): - config = ActorTypeConfig('testactor') - self.assertEqual(config._actor_idle_timeout, None) - self.assertEqual(config._actor_scan_interval, None) - self.assertEqual(config._drain_ongoing_call_timeout, None) - self.assertEqual(config._drain_rebalanced_actors, None) - self.assertEqual(config._reentrancy, None) - self.assertEqual(config.as_dict()['entities'], ['testactor']) - keys = config.as_dict().keys() - self.assertNotIn('reentrancy', keys) - self.assertNotIn('remindersStoragePartitions', keys) - self.assertNotIn('actorIdleTimeout', keys) - self.assertNotIn('actorScanInterval', keys) - self.assertNotIn('drainOngoingCallTimeout', keys) - self.assertNotIn('drainRebalancedActors', keys) - - def test_complete_config(self): - config = ActorTypeConfig( - 'testactor', - actor_idle_timeout=timedelta(seconds=3600), - actor_scan_interval=timedelta(seconds=30), - drain_ongoing_call_timeout=timedelta(seconds=60), - drain_rebalanced_actors=False, - reentrancy=ActorReentrancyConfig(enabled=True), - reminders_storage_partitions=10, - ) - self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(config._drain_rebalanced_actors, False) - self.assertEqual(config._reentrancy._enabled, True) - self.assertEqual(config._reentrancy._maxStackDepth, 32) - d = config.as_dict() - self.assertEqual(d['entities'], ['testactor']) - self.assertEqual(d['reentrancy']['enabled'], True) - self.assertEqual(d['reentrancy']['maxStackDepth'], 32) - self.assertEqual(d['remindersStoragePartitions'], 10) - self.assertEqual(d['actorIdleTimeout'], timedelta(seconds=3600)) - self.assertEqual(d['actorScanInterval'], timedelta(seconds=30)) - self.assertEqual(d['drainOngoingCallTimeout'], timedelta(seconds=60)) - self.assertEqual(d['drainRebalancedActors'], False) - - -class ActorRuntimeConfigTests(unittest.TestCase): - def test_default_config(self): - config = ActorRuntimeConfig() - - self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(config._drain_rebalanced_actors, True) - self.assertEqual(config._reentrancy, None) - self.assertEqual(config._entities, set()) - self.assertEqual(config._entitiesConfig, []) - self.assertNotIn('reentrancy', config.as_dict().keys()) - self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) - self.assertEqual(config.as_dict()['entitiesConfig'], []) - - def test_default_config_with_reentrancy(self): - reentrancyConfig = ActorReentrancyConfig(enabled=True) - config = ActorRuntimeConfig(reentrancy=reentrancyConfig) - - self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(config._drain_rebalanced_actors, True) - self.assertEqual(config._reentrancy, reentrancyConfig) - self.assertEqual(config._entities, set()) - self.assertEqual(config._entitiesConfig, []) - self.assertEqual(config.as_dict()['reentrancy'], reentrancyConfig.as_dict()) - self.assertEqual(config.as_dict()['reentrancy']['enabled'], True) - self.assertEqual(config.as_dict()['reentrancy']['maxStackDepth'], 32) - self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) - - def test_config_with_actor_type_config(self): - typeConfig1 = ActorTypeConfig( - 'testactor1', - actor_scan_interval=timedelta(seconds=10), - reentrancy=ActorReentrancyConfig(enabled=True), - ) - typeConfig2 = ActorTypeConfig( - 'testactor2', - drain_ongoing_call_timeout=timedelta(seconds=60), - reminders_storage_partitions=10, - ) - config = ActorRuntimeConfig(actor_type_configs=[typeConfig1, typeConfig2]) - - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - - d = config.as_dict() - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(d['entitiesConfig'][0]['entities'], ['testactor1']) - self.assertEqual(d['entitiesConfig'][0]['actorScanInterval'], timedelta(seconds=10)) - self.assertEqual(d['entitiesConfig'][0]['reentrancy']['enabled'], True) - self.assertEqual(d['entitiesConfig'][0]['reentrancy']['maxStackDepth'], 32) - self.assertEqual(d['entitiesConfig'][1]['entities'], ['testactor2']) - self.assertEqual(d['entitiesConfig'][1]['drainOngoingCallTimeout'], timedelta(seconds=60)) - self.assertEqual(d['entitiesConfig'][1]['remindersStoragePartitions'], 10) - self.assertNotIn('reentrancy', d['entitiesConfig'][1]) - self.assertNotIn('actorScanInterval', d['entitiesConfig'][1]) - self.assertNotIn('draingOngoingCallTimeout', d['entitiesConfig'][0]) - self.assertNotIn('remindersStoragePartitions', d['entitiesConfig'][0]) - self.assertEqual(sorted(d['entities']), ['testactor1', 'testactor2']) - - def test_update_entities(self): - config = ActorRuntimeConfig() - config.update_entities(['actortype1']) - - self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(config._drain_rebalanced_actors, True) - self.assertEqual(config._entities, {'actortype1'}) - self.assertEqual(config._entitiesConfig, []) - self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) - - def test_update_entities_two_types(self): - config = ActorRuntimeConfig() - config.update_entities(['actortype1', 'actortype1']) - self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(config._drain_rebalanced_actors, True) - self.assertEqual(config._entities, {'actortype1', 'actortype1'}) - self.assertEqual(config._entitiesConfig, []) - self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) - - def test_update_actor_type_config(self): - config = ActorRuntimeConfig() - config.update_entities(['actortype1']) - config.update_actor_type_configs( - [ActorTypeConfig('updatetype1', actor_scan_interval=timedelta(seconds=5))] - ) - - d = config.as_dict() - self.assertEqual(sorted(d['entities']), ['actortype1', 'updatetype1']) - self.assertEqual(d['entitiesConfig'][0]['actorScanInterval'], timedelta(seconds=5)) - self.assertEqual(d['entitiesConfig'][0]['entities'], ['updatetype1']) - self.assertEqual(d['actorScanInterval'], timedelta(seconds=30)) - - def test_set_reminders_storage_partitions(self): - config = ActorRuntimeConfig(reminders_storage_partitions=12) - self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) - self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) - self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) - self.assertEqual(config._drain_rebalanced_actors, True) - self.assertNotIn('reentrancy', config.as_dict().keys()) - self.assertEqual(config._reminders_storage_partitions, 12) - self.assertEqual(config.as_dict()['remindersStoragePartitions'], 12) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from datetime import timedelta +from dapr.actor.runtime.config import ActorRuntimeConfig, ActorReentrancyConfig, ActorTypeConfig + + +class ActorTypeConfigTests(unittest.TestCase): + def test_default_config(self): + config = ActorTypeConfig('testactor') + self.assertEqual(config._actor_idle_timeout, None) + self.assertEqual(config._actor_scan_interval, None) + self.assertEqual(config._drain_ongoing_call_timeout, None) + self.assertEqual(config._drain_rebalanced_actors, None) + self.assertEqual(config._reentrancy, None) + self.assertEqual(config.as_dict()['entities'], ['testactor']) + keys = config.as_dict().keys() + self.assertNotIn('reentrancy', keys) + self.assertNotIn('remindersStoragePartitions', keys) + self.assertNotIn('actorIdleTimeout', keys) + self.assertNotIn('actorScanInterval', keys) + self.assertNotIn('drainOngoingCallTimeout', keys) + self.assertNotIn('drainRebalancedActors', keys) + + def test_complete_config(self): + config = ActorTypeConfig( + 'testactor', + actor_idle_timeout=timedelta(seconds=3600), + actor_scan_interval=timedelta(seconds=30), + drain_ongoing_call_timeout=timedelta(seconds=60), + drain_rebalanced_actors=False, + reentrancy=ActorReentrancyConfig(enabled=True), + reminders_storage_partitions=10, + ) + self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(config._drain_rebalanced_actors, False) + self.assertEqual(config._reentrancy._enabled, True) + self.assertEqual(config._reentrancy._maxStackDepth, 32) + d = config.as_dict() + self.assertEqual(d['entities'], ['testactor']) + self.assertEqual(d['reentrancy']['enabled'], True) + self.assertEqual(d['reentrancy']['maxStackDepth'], 32) + self.assertEqual(d['remindersStoragePartitions'], 10) + self.assertEqual(d['actorIdleTimeout'], timedelta(seconds=3600)) + self.assertEqual(d['actorScanInterval'], timedelta(seconds=30)) + self.assertEqual(d['drainOngoingCallTimeout'], timedelta(seconds=60)) + self.assertEqual(d['drainRebalancedActors'], False) + + +class ActorRuntimeConfigTests(unittest.TestCase): + def test_default_config(self): + config = ActorRuntimeConfig() + + self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(config._drain_rebalanced_actors, True) + self.assertEqual(config._reentrancy, None) + self.assertEqual(config._entities, set()) + self.assertEqual(config._entitiesConfig, []) + self.assertNotIn('reentrancy', config.as_dict().keys()) + self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) + self.assertEqual(config.as_dict()['entitiesConfig'], []) + + def test_default_config_with_reentrancy(self): + reentrancyConfig = ActorReentrancyConfig(enabled=True) + config = ActorRuntimeConfig(reentrancy=reentrancyConfig) + + self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(config._drain_rebalanced_actors, True) + self.assertEqual(config._reentrancy, reentrancyConfig) + self.assertEqual(config._entities, set()) + self.assertEqual(config._entitiesConfig, []) + self.assertEqual(config.as_dict()['reentrancy'], reentrancyConfig.as_dict()) + self.assertEqual(config.as_dict()['reentrancy']['enabled'], True) + self.assertEqual(config.as_dict()['reentrancy']['maxStackDepth'], 32) + self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) + + def test_config_with_actor_type_config(self): + typeConfig1 = ActorTypeConfig( + 'testactor1', + actor_scan_interval=timedelta(seconds=10), + reentrancy=ActorReentrancyConfig(enabled=True), + ) + typeConfig2 = ActorTypeConfig( + 'testactor2', + drain_ongoing_call_timeout=timedelta(seconds=60), + reminders_storage_partitions=10, + ) + config = ActorRuntimeConfig(actor_type_configs=[typeConfig1, typeConfig2]) + + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + + d = config.as_dict() + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(d['entitiesConfig'][0]['entities'], ['testactor1']) + self.assertEqual(d['entitiesConfig'][0]['actorScanInterval'], timedelta(seconds=10)) + self.assertEqual(d['entitiesConfig'][0]['reentrancy']['enabled'], True) + self.assertEqual(d['entitiesConfig'][0]['reentrancy']['maxStackDepth'], 32) + self.assertEqual(d['entitiesConfig'][1]['entities'], ['testactor2']) + self.assertEqual(d['entitiesConfig'][1]['drainOngoingCallTimeout'], timedelta(seconds=60)) + self.assertEqual(d['entitiesConfig'][1]['remindersStoragePartitions'], 10) + self.assertNotIn('reentrancy', d['entitiesConfig'][1]) + self.assertNotIn('actorScanInterval', d['entitiesConfig'][1]) + self.assertNotIn('draingOngoingCallTimeout', d['entitiesConfig'][0]) + self.assertNotIn('remindersStoragePartitions', d['entitiesConfig'][0]) + self.assertEqual(sorted(d['entities']), ['testactor1', 'testactor2']) + + def test_update_entities(self): + config = ActorRuntimeConfig() + config.update_entities(['actortype1']) + + self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(config._drain_rebalanced_actors, True) + self.assertEqual(config._entities, {'actortype1'}) + self.assertEqual(config._entitiesConfig, []) + self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) + + def test_update_entities_two_types(self): + config = ActorRuntimeConfig() + config.update_entities(['actortype1', 'actortype1']) + self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(config._drain_rebalanced_actors, True) + self.assertEqual(config._entities, {'actortype1', 'actortype1'}) + self.assertEqual(config._entitiesConfig, []) + self.assertNotIn('remindersStoragePartitions', config.as_dict().keys()) + + def test_update_actor_type_config(self): + config = ActorRuntimeConfig() + config.update_entities(['actortype1']) + config.update_actor_type_configs( + [ActorTypeConfig('updatetype1', actor_scan_interval=timedelta(seconds=5))] + ) + + d = config.as_dict() + self.assertEqual(sorted(d['entities']), ['actortype1', 'updatetype1']) + self.assertEqual(d['entitiesConfig'][0]['actorScanInterval'], timedelta(seconds=5)) + self.assertEqual(d['entitiesConfig'][0]['entities'], ['updatetype1']) + self.assertEqual(d['actorScanInterval'], timedelta(seconds=30)) + + def test_set_reminders_storage_partitions(self): + config = ActorRuntimeConfig(reminders_storage_partitions=12) + self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600)) + self.assertEqual(config._actor_scan_interval, timedelta(seconds=30)) + self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60)) + self.assertEqual(config._drain_rebalanced_actors, True) + self.assertNotIn('reentrancy', config.as_dict().keys()) + self.assertEqual(config._reminders_storage_partitions, 12) + self.assertEqual(config.as_dict()['remindersStoragePartitions'], 12) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/actor/test_client_proxy.py b/tests/actor/test_client_proxy.py index fe667d629..173729dee 100644 --- a/tests/actor/test_client_proxy.py +++ b/tests/actor/test_client_proxy.py @@ -1,103 +1,103 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import unittest - -from unittest import mock - - -from dapr.actor.id import ActorId -from dapr.actor.client.proxy import ActorProxy -from dapr.serializers import DefaultJSONSerializer -from tests.actor.fake_actor_classes import ( - FakeMultiInterfacesActor, - FakeActorCls2Interface, -) - - -from tests.actor.fake_client import FakeDaprActorClient - -from tests.actor.utils import _async_mock, _run - - -class FakeActoryProxyFactory: - def __init__(self, fake_client): - # TODO: support serializer for state store later - self._dapr_client = fake_client - - def create(self, actor_interface, actor_type, actor_id) -> ActorProxy: - return ActorProxy( - self._dapr_client, actor_interface, actor_type, actor_id, DefaultJSONSerializer() - ) - - -class ActorProxyTests(unittest.TestCase): - def setUp(self): - # Create mock client - self._fake_client = FakeDaprActorClient - self._fake_factory = FakeActoryProxyFactory(self._fake_client) - self._proxy = ActorProxy.create( - FakeMultiInterfacesActor.__name__, - ActorId('fake-id'), - FakeActorCls2Interface, - self._fake_factory, - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', - new=_async_mock(return_value=b'"expected_response"'), - ) - def test_invoke(self): - response = _run(self._proxy.invoke_method('ActionMethod', b'arg0')) - self.assertEqual(b'"expected_response"', response) - self._fake_client.invoke_method.mock.assert_called_once_with( - FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethod', b'arg0' - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', - new=_async_mock(return_value=b'"expected_response"'), - ) - def test_invoke_no_arg(self): - response = _run(self._proxy.invoke_method('ActionMethodWithoutArg')) - self.assertEqual(b'"expected_response"', response) - self._fake_client.invoke_method.mock.assert_called_once_with( - FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethodWithoutArg', None - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', - new=_async_mock(return_value=b'"expected_response"'), - ) - def test_invoke_with_static_typing(self): - response = _run(self._proxy.ActionMethod(b'arg0')) - self.assertEqual('expected_response', response) - self._fake_client.invoke_method.mock.assert_called_once_with( - FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethod', b'arg0' - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', - new=_async_mock(return_value=b'"expected_response"'), - ) - def test_invoke_with_static_typing_no_arg(self): - response = _run(self._proxy.ActionMethodWithoutArg()) - self.assertEqual('expected_response', response) - self._fake_client.invoke_method.mock.assert_called_once_with( - FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethodWithoutArg', None - ) - - def test_raise_exception_non_existing_method(self): - with self.assertRaises(AttributeError): - _run(self._proxy.non_existing()) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import unittest + +from unittest import mock + + +from dapr.actor.id import ActorId +from dapr.actor.client.proxy import ActorProxy +from dapr.serializers import DefaultJSONSerializer +from tests.actor.fake_actor_classes import ( + FakeMultiInterfacesActor, + FakeActorCls2Interface, +) + + +from tests.actor.fake_client import FakeDaprActorClient + +from tests.actor.utils import _async_mock, _run + + +class FakeActoryProxyFactory: + def __init__(self, fake_client): + # TODO: support serializer for state store later + self._dapr_client = fake_client + + def create(self, actor_interface, actor_type, actor_id) -> ActorProxy: + return ActorProxy( + self._dapr_client, actor_interface, actor_type, actor_id, DefaultJSONSerializer() + ) + + +class ActorProxyTests(unittest.TestCase): + def setUp(self): + # Create mock client + self._fake_client = FakeDaprActorClient + self._fake_factory = FakeActoryProxyFactory(self._fake_client) + self._proxy = ActorProxy.create( + FakeMultiInterfacesActor.__name__, + ActorId('fake-id'), + FakeActorCls2Interface, + self._fake_factory, + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', + new=_async_mock(return_value=b'"expected_response"'), + ) + def test_invoke(self): + response = _run(self._proxy.invoke_method('ActionMethod', b'arg0')) + self.assertEqual(b'"expected_response"', response) + self._fake_client.invoke_method.mock.assert_called_once_with( + FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethod', b'arg0' + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', + new=_async_mock(return_value=b'"expected_response"'), + ) + def test_invoke_no_arg(self): + response = _run(self._proxy.invoke_method('ActionMethodWithoutArg')) + self.assertEqual(b'"expected_response"', response) + self._fake_client.invoke_method.mock.assert_called_once_with( + FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethodWithoutArg', None + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', + new=_async_mock(return_value=b'"expected_response"'), + ) + def test_invoke_with_static_typing(self): + response = _run(self._proxy.ActionMethod(b'arg0')) + self.assertEqual('expected_response', response) + self._fake_client.invoke_method.mock.assert_called_once_with( + FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethod', b'arg0' + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.invoke_method', + new=_async_mock(return_value=b'"expected_response"'), + ) + def test_invoke_with_static_typing_no_arg(self): + response = _run(self._proxy.ActionMethodWithoutArg()) + self.assertEqual('expected_response', response) + self._fake_client.invoke_method.mock.assert_called_once_with( + FakeMultiInterfacesActor.__name__, 'fake-id', 'ActionMethodWithoutArg', None + ) + + def test_raise_exception_non_existing_method(self): + with self.assertRaises(AttributeError): + _run(self._proxy.non_existing()) diff --git a/tests/actor/test_method_dispatcher.py b/tests/actor/test_method_dispatcher.py index 94f48a7b6..3eaeeb476 100644 --- a/tests/actor/test_method_dispatcher.py +++ b/tests/actor/test_method_dispatcher.py @@ -1,56 +1,56 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.actor.runtime.method_dispatcher import ActorMethodDispatcher -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import FakeSimpleActor -from tests.actor.fake_client import FakeDaprActorClient -from tests.actor.utils import _run - - -class ActorMethodDispatcherTests(unittest.TestCase): - def setUp(self): - self._testActorTypeInfo = ActorTypeInformation.create(FakeSimpleActor) - self._serializer = DefaultJSONSerializer() - self._fake_client = FakeDaprActorClient - self._fake_runtime_ctx = ActorRuntimeContext( - self._testActorTypeInfo, self._serializer, self._serializer, self._fake_client - ) - - def test_get_arg_names(self): - dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) - arg_names = dispatcher.get_arg_names('ActorMethod') - self.assertEqual(['arg'], arg_names) - - def test_get_arg_types(self): - dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) - arg_names = dispatcher.get_arg_types('ActorMethod') - self.assertEqual([int], arg_names) - - def test_get_return_type(self): - dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) - arg_names = dispatcher.get_return_type('ActorMethod') - self.assertEqual(dict, arg_names) - - def test_dispatch(self): - dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) - actorInstance = FakeSimpleActor(self._fake_runtime_ctx, None) - result = _run(dispatcher.dispatch(actorInstance, 'ActorMethod', 10)) - self.assertEqual({'name': 'actor_method'}, result) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.actor.runtime.method_dispatcher import ActorMethodDispatcher +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import FakeSimpleActor +from tests.actor.fake_client import FakeDaprActorClient +from tests.actor.utils import _run + + +class ActorMethodDispatcherTests(unittest.TestCase): + def setUp(self): + self._testActorTypeInfo = ActorTypeInformation.create(FakeSimpleActor) + self._serializer = DefaultJSONSerializer() + self._fake_client = FakeDaprActorClient + self._fake_runtime_ctx = ActorRuntimeContext( + self._testActorTypeInfo, self._serializer, self._serializer, self._fake_client + ) + + def test_get_arg_names(self): + dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) + arg_names = dispatcher.get_arg_names('ActorMethod') + self.assertEqual(['arg'], arg_names) + + def test_get_arg_types(self): + dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) + arg_names = dispatcher.get_arg_types('ActorMethod') + self.assertEqual([int], arg_names) + + def test_get_return_type(self): + dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) + arg_names = dispatcher.get_return_type('ActorMethod') + self.assertEqual(dict, arg_names) + + def test_dispatch(self): + dispatcher = ActorMethodDispatcher(self._testActorTypeInfo) + actorInstance = FakeSimpleActor(self._fake_runtime_ctx, None) + result = _run(dispatcher.dispatch(actorInstance, 'ActorMethod', 10)) + self.assertEqual({'name': 'actor_method'}, result) diff --git a/tests/actor/test_mock_actor.py b/tests/actor/test_mock_actor.py new file mode 100644 index 000000000..66d3c7215 --- /dev/null +++ b/tests/actor/test_mock_actor.py @@ -0,0 +1,264 @@ +import datetime +import unittest +from typing import Optional + +from dapr.actor import Actor, ActorInterface, Remindable, actormethod +from dapr.actor.runtime.mock_actor import create_mock_actor +from dapr.actor.runtime.state_change import StateChangeKind + + +class MockTestActorInterface(ActorInterface): + @actormethod(name='GetData') + async def get_data(self) -> object: ... + + @actormethod(name='SetData') + async def set_data(self, data: object) -> None: ... + + @actormethod(name='ClearData') + async def clear_data(self) -> None: ... + + @actormethod(name='TestData') + async def test_data(self) -> int: ... + + @actormethod(name='AddDataNoSave') + async def add_data_no_save(self, data: object) -> None: ... + + @actormethod(name='RemoveDataNoSave') + async def remove_data_no_save(self) -> None: ... + + @actormethod(name='SaveState') + async def save_state(self) -> None: ... + + @actormethod(name='ToggleReminder') + async def toggle_reminder(self, name: str, enabled: bool) -> None: ... + + @actormethod(name='ToggleTimer') + async def toggle_timer(self, name: str, enabled: bool) -> None: ... + + +class MockTestActor(Actor, MockTestActorInterface, Remindable): + def __init__(self, ctx, actor_id): + super().__init__(ctx, actor_id) + + async def _on_activate(self) -> None: + await self._state_manager.set_state('state', {'test': 5}) + await self._state_manager.save_state() + + async def get_data(self) -> object: + _, val = await self._state_manager.try_get_state('state') + return val + + async def set_data(self, data) -> None: + await self._state_manager.set_state('state', data) + await self._state_manager.save_state() + + async def clear_data(self) -> None: + await self._state_manager.remove_state('state') + await self._state_manager.save_state() + + async def test_data(self) -> int: + _, val = await self._state_manager.try_get_state('state') + if val is None: + return 0 + if 'test' not in val: + return 1 + if val['test'] % 2 == 1: + return 2 + elif val['test'] % 2 == 0: + return 3 + return 4 + + async def add_data_no_save(self, data: object) -> None: + await self._state_manager.set_state('state', data) + + async def remove_data_no_save(self) -> None: + await self._state_manager.remove_state('state') + + async def save_state(self) -> None: + await self._state_manager.save_state() + + async def toggle_reminder(self, name: str, enabled: bool) -> None: + if enabled: + await self.register_reminder( + name, + b'reminder_state', + datetime.timedelta(seconds=5), + datetime.timedelta(seconds=10), + datetime.timedelta(seconds=15), + ) + else: + await self.unregister_reminder(name) + + async def toggle_timer(self, name: str, enabled: bool) -> None: + if enabled: + await self.register_timer( + name, + self.timer_callback, + 'timer_state', + datetime.timedelta(seconds=5), + datetime.timedelta(seconds=10), + datetime.timedelta(seconds=15), + ) + else: + await self.unregister_timer(name) + + async def receive_reminder( + self, + name: str, + state: bytes, + due_time: datetime.timedelta, + period: datetime.timedelta, + ttl: Optional[datetime.timedelta] = None, + ) -> None: + await self._state_manager.set_state(name, True) + await self._state_manager.save_state() + + async def timer_callback(self, state) -> None: + print('Timer triggered') + + +class ActorMockActorTests(unittest.IsolatedAsyncioTestCase): + def test_create_actor(self): + mockactor = create_mock_actor(MockTestActor, '1') + self.assertEqual(mockactor.id.id, '1') + + async def test_inistate(self): + mockactor = create_mock_actor(MockTestActor, '1', initstate={'state': 5}) + self.assertTrue('state' in mockactor._state_manager._mock_state) + self.assertEqual(mockactor._state_manager._mock_state['state'], 5) + + async def test_on_activate(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor._on_activate() + self.assertTrue('state' in mockactor._state_manager._mock_state) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 5}) + + async def test_get_data(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor._on_activate() + out1 = await mockactor.get_data() + self.assertEqual(out1, {'test': 5}) + + async def test_get_data_initstate(self): + mockactor = create_mock_actor(MockTestActor, '1', initstate={'state': {'test': 6}}) + out1 = await mockactor.get_data() + self.assertEqual(out1, {'test': 6}) + + async def test_set_data(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor._on_activate() + self.assertTrue('state' in mockactor._state_manager._mock_state) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 5}) + await mockactor.set_data({'test': 10}) + self.assertTrue('state' in mockactor._state_manager._mock_state) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 10}) + out1 = await mockactor.get_data() + self.assertEqual(out1, {'test': 10}) + + async def test_clear_data(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor._on_activate() + self.assertTrue('state' in mockactor._state_manager._mock_state) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 5}) + await mockactor.clear_data() + self.assertFalse('state' in mockactor._state_manager._mock_state) + self.assertIsNone(mockactor._state_manager._mock_state.get('state')) + out1 = await mockactor.get_data() + self.assertIsNone(out1) + + async def test_toggle_reminder(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor._on_activate() + self.assertEqual(len(mockactor._state_manager._mock_reminders), 0) + await mockactor.toggle_reminder('test', True) + self.assertEqual(len(mockactor._state_manager._mock_reminders), 1) + self.assertTrue('test' in mockactor._state_manager._mock_reminders) + reminderstate = mockactor._state_manager._mock_reminders['test'] + self.assertTrue(reminderstate.reminder_name, 'test') + await mockactor.toggle_reminder('test', False) + self.assertEqual(len(mockactor._state_manager._mock_reminders), 0) + + async def test_toggle_timer(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor._on_activate() + self.assertEqual(len(mockactor._state_manager._mock_timers), 0) + await mockactor.toggle_timer('test', True) + self.assertEqual(len(mockactor._state_manager._mock_timers), 1) + self.assertTrue('test' in mockactor._state_manager._mock_timers) + timerstate = mockactor._state_manager._mock_timers['test'] + self.assertTrue(timerstate.timer_name, 'test') + await mockactor.toggle_timer('test', False) + self.assertEqual(len(mockactor._state_manager._mock_timers), 0) + + async def test_activate_reminder(self): + mockactor = create_mock_actor(MockTestActor, '1') + await mockactor.receive_reminder( + 'test', + b'test1', + datetime.timedelta(days=1), + datetime.timedelta(days=1), + datetime.timedelta(days=1), + ) + self.assertEqual(mockactor._state_manager._mock_state['test'], True) + + async def test_test_data(self): + mockactor = create_mock_actor(MockTestActor, '1') + result = await mockactor.test_data() + self.assertEqual(result, 0) + await mockactor.set_data('lol') + result = await mockactor.test_data() + self.assertEqual(result, 1) + await mockactor.set_data({'test': 'lol'}) + with self.assertRaises(TypeError): + await mockactor.test_data() + await mockactor.set_data({'test': 1}) + result = await mockactor.test_data() + self.assertEqual(result, 2) + await mockactor.set_data({'test': 2}) + result = await mockactor.test_data() + self.assertEqual(result, 3) + + async def test_state_change_tracker(self): + mockactor = create_mock_actor(MockTestActor, '1') + self.assertEqual(len(mockactor._state_manager._default_state_change_tracker), 0) + await mockactor._on_activate() + self.assertEqual(len(mockactor._state_manager._default_state_change_tracker), 1) + self.assertTrue('state' in mockactor._state_manager._default_state_change_tracker) + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].change_kind, + StateChangeKind.none, + ) + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].value, {'test': 5} + ) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 5}) + await mockactor.remove_data_no_save() + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].change_kind, + StateChangeKind.remove, + ) + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].value, {'test': 5} + ) + self.assertTrue('state' not in mockactor._state_manager._mock_state) + await mockactor.save_state() + self.assertEqual(len(mockactor._state_manager._default_state_change_tracker), 0) + self.assertTrue('state' not in mockactor._state_manager._mock_state) + await mockactor.add_data_no_save({'test': 6}) + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].change_kind, + StateChangeKind.add, + ) + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].value, {'test': 6} + ) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 6}) + await mockactor.save_state() + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].change_kind, + StateChangeKind.none, + ) + self.assertEqual( + mockactor._state_manager._default_state_change_tracker['state'].value, {'test': 6} + ) + self.assertEqual(mockactor._state_manager._mock_state['state'], {'test': 6}) diff --git a/tests/actor/test_reminder_data.py b/tests/actor/test_reminder_data.py index e142217c9..45d62dc39 100644 --- a/tests/actor/test_reminder_data.py +++ b/tests/actor/test_reminder_data.py @@ -1,82 +1,82 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -from datetime import timedelta - -from dapr.actor.runtime._reminder_data import ActorReminderData - - -class ActorReminderTests(unittest.TestCase): - def test_invalid_state(self): - with self.assertRaises(ValueError): - ActorReminderData( - 'test_reminder', - 123, # int type - timedelta(seconds=1), - timedelta(seconds=2), - timedelta(seconds=3), - ) - ActorReminderData( - 'test_reminder', - 'reminder_state', # string type - timedelta(seconds=2), - timedelta(seconds=1), - timedelta(seconds=3), - ) - - def test_valid_state(self): - # bytes type state data - reminder = ActorReminderData( - 'test_reminder', - b'reminder_state', - timedelta(seconds=1), - timedelta(seconds=2), - timedelta(seconds=3), - ) - self.assertEqual(b'reminder_state', reminder.state) - - def test_as_dict(self): - reminder = ActorReminderData( - 'test_reminder', - b'reminder_state', - timedelta(seconds=1), - timedelta(seconds=2), - timedelta(seconds=3), - ) - expected = { - 'reminderName': 'test_reminder', - 'dueTime': timedelta(seconds=1), - 'period': timedelta(seconds=2), - 'ttl': timedelta(seconds=3), - 'data': 'cmVtaW5kZXJfc3RhdGU=', - } - self.assertDictEqual(expected, reminder.as_dict()) - - def test_from_dict(self): - reminder = ActorReminderData.from_dict( - 'test_reminder', - { - 'dueTime': timedelta(seconds=1), - 'period': timedelta(seconds=2), - 'ttl': timedelta(seconds=3), - 'data': 'cmVtaW5kZXJfc3RhdGU=', - }, - ) - self.assertEqual('test_reminder', reminder.reminder_name) - self.assertEqual(timedelta(seconds=1), reminder.due_time) - self.assertEqual(timedelta(seconds=2), reminder.period) - self.assertEqual(timedelta(seconds=3), reminder.ttl) - self.assertEqual(b'reminder_state', reminder.state) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +from datetime import timedelta + +from dapr.actor.runtime._reminder_data import ActorReminderData + + +class ActorReminderTests(unittest.TestCase): + def test_invalid_state(self): + with self.assertRaises(ValueError): + ActorReminderData( + 'test_reminder', + 123, # int type + timedelta(seconds=1), + timedelta(seconds=2), + timedelta(seconds=3), + ) + ActorReminderData( + 'test_reminder', + 'reminder_state', # string type + timedelta(seconds=2), + timedelta(seconds=1), + timedelta(seconds=3), + ) + + def test_valid_state(self): + # bytes type state data + reminder = ActorReminderData( + 'test_reminder', + b'reminder_state', + timedelta(seconds=1), + timedelta(seconds=2), + timedelta(seconds=3), + ) + self.assertEqual(b'reminder_state', reminder.state) + + def test_as_dict(self): + reminder = ActorReminderData( + 'test_reminder', + b'reminder_state', + timedelta(seconds=1), + timedelta(seconds=2), + timedelta(seconds=3), + ) + expected = { + 'reminderName': 'test_reminder', + 'dueTime': timedelta(seconds=1), + 'period': timedelta(seconds=2), + 'ttl': timedelta(seconds=3), + 'data': 'cmVtaW5kZXJfc3RhdGU=', + } + self.assertDictEqual(expected, reminder.as_dict()) + + def test_from_dict(self): + reminder = ActorReminderData.from_dict( + 'test_reminder', + { + 'dueTime': timedelta(seconds=1), + 'period': timedelta(seconds=2), + 'ttl': timedelta(seconds=3), + 'data': 'cmVtaW5kZXJfc3RhdGU=', + }, + ) + self.assertEqual('test_reminder', reminder.reminder_name) + self.assertEqual(timedelta(seconds=1), reminder.due_time) + self.assertEqual(timedelta(seconds=2), reminder.period) + self.assertEqual(timedelta(seconds=3), reminder.ttl) + self.assertEqual(b'reminder_state', reminder.state) diff --git a/tests/actor/test_state_manager.py b/tests/actor/test_state_manager.py index 98db0228e..1307cec2d 100644 --- a/tests/actor/test_state_manager.py +++ b/tests/actor/test_state_manager.py @@ -1,450 +1,450 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import base64 -import unittest - -from unittest import mock - -from dapr.actor.id import ActorId -from dapr.actor.runtime.context import ActorRuntimeContext -from dapr.actor.runtime.state_change import StateChangeKind -from dapr.actor.runtime.state_manager import ActorStateManager -from dapr.actor.runtime._type_information import ActorTypeInformation -from dapr.serializers import DefaultJSONSerializer - -from tests.actor.fake_actor_classes import FakeSimpleActor -from tests.actor.fake_client import FakeDaprActorClient - -from tests.actor.utils import _async_mock, _run - - -class ActorStateManagerTests(unittest.TestCase): - def setUp(self): - # Create mock client - self._fake_client = FakeDaprActorClient - - self._test_actor_id = ActorId('1') - self._test_type_info = ActorTypeInformation.create(FakeSimpleActor) - self._serializer = DefaultJSONSerializer() - self._runtime_ctx = ActorRuntimeContext( - self._test_type_info, self._serializer, self._serializer, self._fake_client - ) - self._fake_actor = FakeSimpleActor(self._runtime_ctx, self._test_actor_id) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=base64.b64encode(b'"value1"')), - ) - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.save_state_transactionally', new=_async_mock() - ) - def test_add_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - - # Add first 'state1' - added = _run(state_manager.try_add_state('state1', 'value1')) - self.assertTrue(added) - - state = state_change_tracker['state1'] - self.assertEqual('value1', state.value) - self.assertEqual(StateChangeKind.add, state.change_kind) - - # Add 'state1' again - added = _run(state_manager.try_add_state('state1', 'value1')) - self.assertFalse(added) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_get_state_for_no_state(self): - state_manager = ActorStateManager(self._fake_actor) - has_value, val = _run(state_manager.try_get_state('state1')) - self.assertFalse(has_value) - self.assertIsNone(val) - - # Test if the test value is empty string - self._fake_client.get_state.return_value = '' - has_value, val = _run(state_manager.try_get_state('state1')) - self.assertFalse(has_value) - self.assertIsNone(val) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_get_state_for_existing_value(self): - state_manager = ActorStateManager(self._fake_actor) - has_value, val = _run(state_manager.try_get_state('state1')) - self.assertTrue(has_value) - self.assertEqual('value1', val) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_get_state_for_removed_value(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - removed = _run(state_manager.try_remove_state('state1')) - self.assertTrue(removed) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.remove, state.change_kind) - - has_value, val = _run(state_manager.try_get_state('state1')) - self.assertFalse(has_value) - self.assertIsNone(val) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_set_state_for_new_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state('state1', 'value1')) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value1', state.value) - self.assertEqual(None, state.ttl_in_seconds) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_set_state_for_existing_state_only_in_mem(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state('state1', 'value1')) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value1', state.value) - - _run(state_manager.set_state('state1', 'value2')) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value2', state.value) - self.assertEqual(None, state.ttl_in_seconds) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_set_state_for_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state('state1', 'value2')) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.update, state.change_kind) - self.assertEqual('value2', state.value) - self.assertEqual(None, state.ttl_in_seconds) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_set_state_ttl_for_new_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state_ttl('state1', 'value1', 3600)) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value1', state.value) - self.assertEqual(3600, state.ttl_in_seconds) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_set_state_ttl_for_existing_state_only_in_mem(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state_ttl('state1', 'value1', 3600)) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value1', state.value) - self.assertEqual(3600, state.ttl_in_seconds) - - _run(state_manager.set_state_ttl('state1', 'value2', 7200)) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value2', state.value) - self.assertEqual(7200, state.ttl_in_seconds) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_set_state_ttl_for_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state_ttl('state1', 'value2', 3600)) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.update, state.change_kind) - self.assertEqual('value2', state.value) - self.assertEqual(3600, state.ttl_in_seconds) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_set_state_ttl_lt_0_for_new_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state_ttl('state1', 'value1', -3600)) - self.assertNotIn('state1', state_change_tracker) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_set_state_ttl_lt_0_for_existing_state_only_in_mem(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state_ttl('state1', 'value1', 3600)) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value1', state.value) - self.assertEqual(3600, state.ttl_in_seconds) - - _run(state_manager.set_state_ttl('state1', 'value2', -3600)) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value1', state.value) - self.assertEqual(3600, state.ttl_in_seconds) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_remove_state_for_non_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - removed = _run(state_manager.try_remove_state('state1')) - self.assertFalse(removed) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_remove_state_for_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - removed = _run(state_manager.try_remove_state('state1')) - self.assertTrue(removed) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.remove, state.change_kind) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_remove_state_for_existing_state_in_mem(self): - state_manager = ActorStateManager(self._fake_actor) - _run(state_manager.set_state('state1', 'value1')) - removed = _run(state_manager.try_remove_state('state1')) - self.assertTrue(removed) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_remove_state_twice_for_existing_state_in_mem(self): - state_manager = ActorStateManager(self._fake_actor) - _run(state_manager.set_state('state1', 'value1')) - removed = _run(state_manager.try_remove_state('state1')) - self.assertTrue(removed) - removed = _run(state_manager.try_remove_state('state1')) - self.assertFalse(removed) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_contains_state_for_removed_state(self): - state_manager = ActorStateManager(self._fake_actor) - _run(state_manager.set_state('state1', 'value1')) - - exist = _run(state_manager.contains_state('state1')) - self.assertTrue(exist) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_contains_state_for_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - exist = _run(state_manager.contains_state('state1')) - self.assertTrue(exist) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_get_or_add_state_for_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - val = _run(state_manager.get_or_add_state('state1', 'value2')) - self.assertEqual('value1', val) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_get_or_add_state_for_non_existing_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - val = _run(state_manager.get_or_add_state('state1', 'value2')) - - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - self.assertEqual('value2', val) - - self._fake_client.get_state.mock.assert_called_once_with( - self._test_type_info._name, self._test_actor_id.id, 'state1' - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_get_or_add_state_for_removed_state(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.remove_state('state1')) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.remove, state.change_kind) - - val = _run(state_manager.get_or_add_state('state1', 'value2')) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.update, state.change_kind) - self.assertEqual('value2', val) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_add_or_update_state_for_new_state(self): - """adds state if state does not exist.""" - - def test_update_value(name, value): - return f'{name}-{value}' - - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) - self.assertEqual('value1', val) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.add, state.change_kind) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_add_or_update_state_for_state_in_storage(self): - """updates state value using update_value_factory if state is - in the storage.""" - - def test_update_value(name, value): - return f'{name}-{value}' - - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) - self.assertEqual('state1-value1', val) - state = state_change_tracker['state1'] - self.assertEqual(StateChangeKind.update, state.change_kind) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_add_or_update_state_for_removed_state(self): - """add state value if state was removed.""" - - def test_update_value(name, value): - return f'{name}-{value}' - - state_manager = ActorStateManager(self._fake_actor) - _run(state_manager.remove_state('state1')) - - val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) - self.assertEqual('value1', val) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value1"'), - ) - def test_add_or_update_state_for_none_state_key(self): - """update state value for StateChangeKind.none state""" - - def test_update_value(name, value): - return f'{name}-{value}' - - state_manager = ActorStateManager(self._fake_actor) - has_value, val = _run(state_manager.try_get_state('state1')) - self.assertTrue(has_value) - self.assertEqual('value1', val) - - val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) - self.assertEqual('state1-value1', val) - - def test_add_or_update_state_without_update_value_factory(self): - """tries to add or update state without update_value_factory""" - state_manager = ActorStateManager(self._fake_actor) - with self.assertRaises(AttributeError): - _run(state_manager.add_or_update_state('state1', 'value1', None)) - - @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) - def test_get_state_names(self): - state_manager = ActorStateManager(self._fake_actor) - _run(state_manager.set_state('state1', 'value1')) - _run(state_manager.set_state('state2', 'value2')) - _run(state_manager.set_state('state3', 'value3')) - names = _run(state_manager.get_state_names()) - self.assertEqual(['state1', 'state2', 'state3'], names) - - self._fake_client.get_state.mock.assert_any_call( - self._test_type_info._name, self._test_actor_id.id, 'state1' - ) - self._fake_client.get_state.mock.assert_any_call( - self._test_type_info._name, self._test_actor_id.id, 'state2' - ) - self._fake_client.get_state.mock.assert_any_call( - self._test_type_info._name, self._test_actor_id.id, 'state3' - ) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value0"'), - ) - def test_clear_cache(self): - state_manager = ActorStateManager(self._fake_actor) - state_change_tracker = state_manager._get_contextual_state_tracker() - _run(state_manager.set_state('state1', 'value1')) - _run(state_manager.set_state('state2', 'value2')) - _run(state_manager.set_state('state3', 'value3')) - _run(state_manager.clear_cache()) - - self.assertEqual(0, len(state_change_tracker)) - - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.get_state', - new=_async_mock(return_value=b'"value3"'), - ) - @mock.patch( - 'tests.actor.fake_client.FakeDaprActorClient.save_state_transactionally', new=_async_mock() - ) - def test_save_state(self): - state_manager = ActorStateManager(self._fake_actor) - # set states which are StateChangeKind.add - _run(state_manager.set_state('state1', 'value1')) - _run(state_manager.set_state('state2', 'value2')) - - has_value, val = _run(state_manager.try_get_state('state3')) - self.assertTrue(has_value) - self.assertEqual('value3', val) - # set state which is StateChangeKind.remove - _run(state_manager.remove_state('state4')) - # set state which is StateChangeKind.update - _run(state_manager.set_state('state5', 'value5')) - _run(state_manager.set_state('state5', 'new_value5')) - # set state with ttl >= 0 - _run(state_manager.set_state_ttl('state6', 'value6', 3600)) - _run(state_manager.set_state_ttl('state7', 'value7', 0)) - # set state with ttl < 0 - _run(state_manager.set_state_ttl('state8', 'value8', -3600)) - - expected = b'[{"operation":"upsert","request":{"key":"state1","value":"value1"}},{"operation":"upsert","request":{"key":"state2","value":"value2"}},{"operation":"delete","request":{"key":"state4"}},{"operation":"upsert","request":{"key":"state5","value":"new_value5"}},{"operation":"upsert","request":{"key":"state6","value":"value6","metadata":{"ttlInSeconds":"3600"}}},{"operation":"upsert","request":{"key":"state7","value":"value7","metadata":{"ttlInSeconds":"0"}}}]' # noqa: E501 - - # Save the state - async def mock_save_state(actor_type, actor_id, data): - self.assertEqual(expected, data) - - self._fake_client.save_state_transactionally = mock_save_state - _run(state_manager.save_state()) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import base64 +import unittest + +from unittest import mock + +from dapr.actor.id import ActorId +from dapr.actor.runtime.context import ActorRuntimeContext +from dapr.actor.runtime.state_change import StateChangeKind +from dapr.actor.runtime.state_manager import ActorStateManager +from dapr.actor.runtime._type_information import ActorTypeInformation +from dapr.serializers import DefaultJSONSerializer + +from tests.actor.fake_actor_classes import FakeSimpleActor +from tests.actor.fake_client import FakeDaprActorClient + +from tests.actor.utils import _async_mock, _run + + +class ActorStateManagerTests(unittest.TestCase): + def setUp(self): + # Create mock client + self._fake_client = FakeDaprActorClient + + self._test_actor_id = ActorId('1') + self._test_type_info = ActorTypeInformation.create(FakeSimpleActor) + self._serializer = DefaultJSONSerializer() + self._runtime_ctx = ActorRuntimeContext( + self._test_type_info, self._serializer, self._serializer, self._fake_client + ) + self._fake_actor = FakeSimpleActor(self._runtime_ctx, self._test_actor_id) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=base64.b64encode(b'"value1"')), + ) + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.save_state_transactionally', new=_async_mock() + ) + def test_add_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + + # Add first 'state1' + added = _run(state_manager.try_add_state('state1', 'value1')) + self.assertTrue(added) + + state = state_change_tracker['state1'] + self.assertEqual('value1', state.value) + self.assertEqual(StateChangeKind.add, state.change_kind) + + # Add 'state1' again + added = _run(state_manager.try_add_state('state1', 'value1')) + self.assertFalse(added) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_get_state_for_no_state(self): + state_manager = ActorStateManager(self._fake_actor) + has_value, val = _run(state_manager.try_get_state('state1')) + self.assertFalse(has_value) + self.assertIsNone(val) + + # Test if the test value is empty string + self._fake_client.get_state.return_value = '' + has_value, val = _run(state_manager.try_get_state('state1')) + self.assertFalse(has_value) + self.assertIsNone(val) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_get_state_for_existing_value(self): + state_manager = ActorStateManager(self._fake_actor) + has_value, val = _run(state_manager.try_get_state('state1')) + self.assertTrue(has_value) + self.assertEqual('value1', val) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_get_state_for_removed_value(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + removed = _run(state_manager.try_remove_state('state1')) + self.assertTrue(removed) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.remove, state.change_kind) + + has_value, val = _run(state_manager.try_get_state('state1')) + self.assertFalse(has_value) + self.assertIsNone(val) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_set_state_for_new_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state('state1', 'value1')) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value1', state.value) + self.assertEqual(None, state.ttl_in_seconds) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_set_state_for_existing_state_only_in_mem(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state('state1', 'value1')) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value1', state.value) + + _run(state_manager.set_state('state1', 'value2')) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value2', state.value) + self.assertEqual(None, state.ttl_in_seconds) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_set_state_for_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state('state1', 'value2')) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.update, state.change_kind) + self.assertEqual('value2', state.value) + self.assertEqual(None, state.ttl_in_seconds) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_set_state_ttl_for_new_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state_ttl('state1', 'value1', 3600)) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value1', state.value) + self.assertEqual(3600, state.ttl_in_seconds) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_set_state_ttl_for_existing_state_only_in_mem(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state_ttl('state1', 'value1', 3600)) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value1', state.value) + self.assertEqual(3600, state.ttl_in_seconds) + + _run(state_manager.set_state_ttl('state1', 'value2', 7200)) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value2', state.value) + self.assertEqual(7200, state.ttl_in_seconds) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_set_state_ttl_for_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state_ttl('state1', 'value2', 3600)) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.update, state.change_kind) + self.assertEqual('value2', state.value) + self.assertEqual(3600, state.ttl_in_seconds) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_set_state_ttl_lt_0_for_new_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state_ttl('state1', 'value1', -3600)) + self.assertNotIn('state1', state_change_tracker) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_set_state_ttl_lt_0_for_existing_state_only_in_mem(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state_ttl('state1', 'value1', 3600)) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value1', state.value) + self.assertEqual(3600, state.ttl_in_seconds) + + _run(state_manager.set_state_ttl('state1', 'value2', -3600)) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value1', state.value) + self.assertEqual(3600, state.ttl_in_seconds) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_remove_state_for_non_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + removed = _run(state_manager.try_remove_state('state1')) + self.assertFalse(removed) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_remove_state_for_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + removed = _run(state_manager.try_remove_state('state1')) + self.assertTrue(removed) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.remove, state.change_kind) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_remove_state_for_existing_state_in_mem(self): + state_manager = ActorStateManager(self._fake_actor) + _run(state_manager.set_state('state1', 'value1')) + removed = _run(state_manager.try_remove_state('state1')) + self.assertTrue(removed) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_remove_state_twice_for_existing_state_in_mem(self): + state_manager = ActorStateManager(self._fake_actor) + _run(state_manager.set_state('state1', 'value1')) + removed = _run(state_manager.try_remove_state('state1')) + self.assertTrue(removed) + removed = _run(state_manager.try_remove_state('state1')) + self.assertFalse(removed) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_contains_state_for_removed_state(self): + state_manager = ActorStateManager(self._fake_actor) + _run(state_manager.set_state('state1', 'value1')) + + exist = _run(state_manager.contains_state('state1')) + self.assertTrue(exist) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_contains_state_for_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + exist = _run(state_manager.contains_state('state1')) + self.assertTrue(exist) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_get_or_add_state_for_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + val = _run(state_manager.get_or_add_state('state1', 'value2')) + self.assertEqual('value1', val) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_get_or_add_state_for_non_existing_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + val = _run(state_manager.get_or_add_state('state1', 'value2')) + + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + self.assertEqual('value2', val) + + self._fake_client.get_state.mock.assert_called_once_with( + self._test_type_info._name, self._test_actor_id.id, 'state1' + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_get_or_add_state_for_removed_state(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.remove_state('state1')) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.remove, state.change_kind) + + val = _run(state_manager.get_or_add_state('state1', 'value2')) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.update, state.change_kind) + self.assertEqual('value2', val) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_add_or_update_state_for_new_state(self): + """adds state if state does not exist.""" + + def test_update_value(name, value): + return f'{name}-{value}' + + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) + self.assertEqual('value1', val) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.add, state.change_kind) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_add_or_update_state_for_state_in_storage(self): + """updates state value using update_value_factory if state is + in the storage.""" + + def test_update_value(name, value): + return f'{name}-{value}' + + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) + self.assertEqual('state1-value1', val) + state = state_change_tracker['state1'] + self.assertEqual(StateChangeKind.update, state.change_kind) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_add_or_update_state_for_removed_state(self): + """add state value if state was removed.""" + + def test_update_value(name, value): + return f'{name}-{value}' + + state_manager = ActorStateManager(self._fake_actor) + _run(state_manager.remove_state('state1')) + + val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) + self.assertEqual('value1', val) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value1"'), + ) + def test_add_or_update_state_for_none_state_key(self): + """update state value for StateChangeKind.none state""" + + def test_update_value(name, value): + return f'{name}-{value}' + + state_manager = ActorStateManager(self._fake_actor) + has_value, val = _run(state_manager.try_get_state('state1')) + self.assertTrue(has_value) + self.assertEqual('value1', val) + + val = _run(state_manager.add_or_update_state('state1', 'value1', test_update_value)) + self.assertEqual('state1-value1', val) + + def test_add_or_update_state_without_update_value_factory(self): + """tries to add or update state without update_value_factory""" + state_manager = ActorStateManager(self._fake_actor) + with self.assertRaises(AttributeError): + _run(state_manager.add_or_update_state('state1', 'value1', None)) + + @mock.patch('tests.actor.fake_client.FakeDaprActorClient.get_state', new=_async_mock()) + def test_get_state_names(self): + state_manager = ActorStateManager(self._fake_actor) + _run(state_manager.set_state('state1', 'value1')) + _run(state_manager.set_state('state2', 'value2')) + _run(state_manager.set_state('state3', 'value3')) + names = _run(state_manager.get_state_names()) + self.assertEqual(['state1', 'state2', 'state3'], names) + + self._fake_client.get_state.mock.assert_any_call( + self._test_type_info._name, self._test_actor_id.id, 'state1' + ) + self._fake_client.get_state.mock.assert_any_call( + self._test_type_info._name, self._test_actor_id.id, 'state2' + ) + self._fake_client.get_state.mock.assert_any_call( + self._test_type_info._name, self._test_actor_id.id, 'state3' + ) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value0"'), + ) + def test_clear_cache(self): + state_manager = ActorStateManager(self._fake_actor) + state_change_tracker = state_manager._get_contextual_state_tracker() + _run(state_manager.set_state('state1', 'value1')) + _run(state_manager.set_state('state2', 'value2')) + _run(state_manager.set_state('state3', 'value3')) + _run(state_manager.clear_cache()) + + self.assertEqual(0, len(state_change_tracker)) + + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.get_state', + new=_async_mock(return_value=b'"value3"'), + ) + @mock.patch( + 'tests.actor.fake_client.FakeDaprActorClient.save_state_transactionally', new=_async_mock() + ) + def test_save_state(self): + state_manager = ActorStateManager(self._fake_actor) + # set states which are StateChangeKind.add + _run(state_manager.set_state('state1', 'value1')) + _run(state_manager.set_state('state2', 'value2')) + + has_value, val = _run(state_manager.try_get_state('state3')) + self.assertTrue(has_value) + self.assertEqual('value3', val) + # set state which is StateChangeKind.remove + _run(state_manager.remove_state('state4')) + # set state which is StateChangeKind.update + _run(state_manager.set_state('state5', 'value5')) + _run(state_manager.set_state('state5', 'new_value5')) + # set state with ttl >= 0 + _run(state_manager.set_state_ttl('state6', 'value6', 3600)) + _run(state_manager.set_state_ttl('state7', 'value7', 0)) + # set state with ttl < 0 + _run(state_manager.set_state_ttl('state8', 'value8', -3600)) + + expected = b'[{"operation":"upsert","request":{"key":"state1","value":"value1"}},{"operation":"upsert","request":{"key":"state2","value":"value2"}},{"operation":"delete","request":{"key":"state4"}},{"operation":"upsert","request":{"key":"state5","value":"new_value5"}},{"operation":"upsert","request":{"key":"state6","value":"value6","metadata":{"ttlInSeconds":"3600"}}},{"operation":"upsert","request":{"key":"state7","value":"value7","metadata":{"ttlInSeconds":"0"}}}]' # noqa: E501 + + # Save the state + async def mock_save_state(actor_type, actor_id, data): + self.assertEqual(expected, data) + + self._fake_client.save_state_transactionally = mock_save_state + _run(state_manager.save_state()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/actor/test_timer_data.py b/tests/actor/test_timer_data.py index ba410cecd..b5a15c166 100644 --- a/tests/actor/test_timer_data.py +++ b/tests/actor/test_timer_data.py @@ -1,62 +1,62 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -from typing import Any -import unittest -from datetime import timedelta - -from dapr.actor.runtime._timer_data import ActorTimerData - - -class ActorTimerDataTests(unittest.TestCase): - def test_timer_data(self): - def my_callback(input: Any): - print(input) - - timer = ActorTimerData( - 'timer_name', - my_callback, - 'called', - timedelta(seconds=2), - timedelta(seconds=1), - timedelta(seconds=3), - ) - self.assertEqual('timer_name', timer.timer_name) - self.assertEqual('my_callback', timer.callback) - self.assertEqual('called', timer.state) - self.assertEqual(timedelta(seconds=2), timer.due_time) - self.assertEqual(timedelta(seconds=1), timer.period) - self.assertEqual(timedelta(seconds=3), timer.ttl) - - def test_as_dict(self): - def my_callback(input: Any): - print(input) - - timer = ActorTimerData( - 'timer_name', - my_callback, - 'called', - timedelta(seconds=1), - timedelta(seconds=1), - timedelta(seconds=1), - ) - expected = { - 'callback': 'my_callback', - 'data': 'called', - 'dueTime': timedelta(seconds=1), - 'period': timedelta(seconds=1), - 'ttl': timedelta(seconds=1), - } - self.assertDictEqual(expected, timer.as_dict()) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from typing import Any +import unittest +from datetime import timedelta + +from dapr.actor.runtime._timer_data import ActorTimerData + + +class ActorTimerDataTests(unittest.TestCase): + def test_timer_data(self): + def my_callback(input: Any): + print(input) + + timer = ActorTimerData( + 'timer_name', + my_callback, + 'called', + timedelta(seconds=2), + timedelta(seconds=1), + timedelta(seconds=3), + ) + self.assertEqual('timer_name', timer.timer_name) + self.assertEqual('my_callback', timer.callback) + self.assertEqual('called', timer.state) + self.assertEqual(timedelta(seconds=2), timer.due_time) + self.assertEqual(timedelta(seconds=1), timer.period) + self.assertEqual(timedelta(seconds=3), timer.ttl) + + def test_as_dict(self): + def my_callback(input: Any): + print(input) + + timer = ActorTimerData( + 'timer_name', + my_callback, + 'called', + timedelta(seconds=1), + timedelta(seconds=1), + timedelta(seconds=1), + ) + expected = { + 'callback': 'my_callback', + 'data': 'called', + 'dueTime': timedelta(seconds=1), + 'period': timedelta(seconds=1), + 'ttl': timedelta(seconds=1), + } + self.assertDictEqual(expected, timer.as_dict()) diff --git a/tests/actor/test_type_information.py b/tests/actor/test_type_information.py index 1532e3956..c77683c64 100644 --- a/tests/actor/test_type_information.py +++ b/tests/actor/test_type_information.py @@ -1,47 +1,47 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.actor.runtime._type_information import ActorTypeInformation -from tests.actor.fake_actor_classes import ( - FakeSimpleActor, - FakeMultiInterfacesActor, - FakeActorCls1Interface, - FakeActorCls2Interface, - ReentrantActorInterface, -) - - -class ActorTypeInformationTests(unittest.TestCase): - def setUp(self): - pass - - def test_actor_type_name(self): - type_info = ActorTypeInformation.create(FakeSimpleActor) - self.assertEqual(FakeSimpleActor.__name__, type_info.type_name) - - def test_implementation_type_returns_correct_type(self): - type_info = ActorTypeInformation.create(FakeSimpleActor) - self.assertEqual(FakeSimpleActor, type_info.implementation_type) - - def test_actor_interfaces_returns_actor_classes(self): - type_info = ActorTypeInformation.create(FakeMultiInterfacesActor) - - self.assertEqual(FakeMultiInterfacesActor.__name__, type_info.type_name) - self.assertEqual(3, len(type_info.actor_interfaces)) - self.assertTrue(type_info.actor_interfaces.index(FakeActorCls1Interface) >= 0) - self.assertTrue(type_info.actor_interfaces.index(FakeActorCls2Interface) >= 0) - self.assertTrue(type_info.actor_interfaces.index(ReentrantActorInterface) >= 0) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.actor.runtime._type_information import ActorTypeInformation +from tests.actor.fake_actor_classes import ( + FakeSimpleActor, + FakeMultiInterfacesActor, + FakeActorCls1Interface, + FakeActorCls2Interface, + ReentrantActorInterface, +) + + +class ActorTypeInformationTests(unittest.TestCase): + def setUp(self): + pass + + def test_actor_type_name(self): + type_info = ActorTypeInformation.create(FakeSimpleActor) + self.assertEqual(FakeSimpleActor.__name__, type_info.type_name) + + def test_implementation_type_returns_correct_type(self): + type_info = ActorTypeInformation.create(FakeSimpleActor) + self.assertEqual(FakeSimpleActor, type_info.implementation_type) + + def test_actor_interfaces_returns_actor_classes(self): + type_info = ActorTypeInformation.create(FakeMultiInterfacesActor) + + self.assertEqual(FakeMultiInterfacesActor.__name__, type_info.type_name) + self.assertEqual(3, len(type_info.actor_interfaces)) + self.assertTrue(type_info.actor_interfaces.index(FakeActorCls1Interface) >= 0) + self.assertTrue(type_info.actor_interfaces.index(FakeActorCls2Interface) >= 0) + self.assertTrue(type_info.actor_interfaces.index(ReentrantActorInterface) >= 0) diff --git a/tests/actor/test_type_utils.py b/tests/actor/test_type_utils.py index f8b2eee2a..d496fec49 100644 --- a/tests/actor/test_type_utils.py +++ b/tests/actor/test_type_utils.py @@ -1,83 +1,83 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.actor.actor_interface import ActorInterface -from dapr.actor.runtime._type_utils import ( - get_class_method_args, - get_method_arg_types, - get_method_return_types, - is_dapr_actor, - get_actor_interfaces, - get_dispatchable_attrs, -) - -from tests.actor.fake_actor_classes import ( - FakeSimpleActor, - FakeMultiInterfacesActor, - FakeActorCls1Interface, - FakeActorCls2Interface, -) - - -class TypeUtilsTests(unittest.TestCase): - def test_get_class_method_args(self): - args = get_class_method_args(FakeSimpleActor.actor_method) - self.assertEqual(args, ['arg']) - - def test_get_method_arg_types(self): - arg_types = get_method_arg_types(FakeSimpleActor.non_actor_method) - self.assertEqual(arg_types, [type(int(30)), type(str('102')), type(float(10.0))]) - - def test_get_return_types(self): - rtn_type = get_method_return_types(FakeSimpleActor.actor_method) - self.assertEqual(dict, rtn_type) - - rtn_type = get_method_return_types(FakeSimpleActor.non_actor_method) - self.assertEqual(str, rtn_type) - - def test_is_dapr_actor_true(self): - self.assertTrue(is_dapr_actor(FakeSimpleActor)) - - def test_is_dapr_actor_false(self): - # Non-actor class - class TestNonActorClass(ActorInterface): - def test_method(self, arg: int) -> object: - pass - - self.assertFalse(is_dapr_actor(TestNonActorClass)) - - def test_get_actor_interface(self): - actor_interfaces = get_actor_interfaces(FakeMultiInterfacesActor) - - self.assertEqual(FakeActorCls1Interface, actor_interfaces[0]) - self.assertEqual(FakeActorCls2Interface, actor_interfaces[1]) - - def test_get_dispatchable_attrs(self): - dispatchable_attrs = get_dispatchable_attrs(FakeMultiInterfacesActor) - expected_dispatchable_attrs = [ - 'ActorCls1Method', - 'ActorCls1Method1', - 'ActorCls1Method2', - 'ActorCls2Method', - ] - - method_cnt = 0 - for method in expected_dispatchable_attrs: - if dispatchable_attrs.get(method) is not None: - method_cnt = method_cnt + 1 - - self.assertEqual(len(expected_dispatchable_attrs), method_cnt) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.actor.actor_interface import ActorInterface +from dapr.actor.runtime._type_utils import ( + get_class_method_args, + get_method_arg_types, + get_method_return_types, + is_dapr_actor, + get_actor_interfaces, + get_dispatchable_attrs, +) + +from tests.actor.fake_actor_classes import ( + FakeSimpleActor, + FakeMultiInterfacesActor, + FakeActorCls1Interface, + FakeActorCls2Interface, +) + + +class TypeUtilsTests(unittest.TestCase): + def test_get_class_method_args(self): + args = get_class_method_args(FakeSimpleActor.actor_method) + self.assertEqual(args, ['arg']) + + def test_get_method_arg_types(self): + arg_types = get_method_arg_types(FakeSimpleActor.non_actor_method) + self.assertEqual(arg_types, [type(int(30)), type(str('102')), type(float(10.0))]) + + def test_get_return_types(self): + rtn_type = get_method_return_types(FakeSimpleActor.actor_method) + self.assertEqual(dict, rtn_type) + + rtn_type = get_method_return_types(FakeSimpleActor.non_actor_method) + self.assertEqual(str, rtn_type) + + def test_is_dapr_actor_true(self): + self.assertTrue(is_dapr_actor(FakeSimpleActor)) + + def test_is_dapr_actor_false(self): + # Non-actor class + class TestNonActorClass(ActorInterface): + def test_method(self, arg: int) -> object: + pass + + self.assertFalse(is_dapr_actor(TestNonActorClass)) + + def test_get_actor_interface(self): + actor_interfaces = get_actor_interfaces(FakeMultiInterfacesActor) + + self.assertEqual(FakeActorCls1Interface, actor_interfaces[0]) + self.assertEqual(FakeActorCls2Interface, actor_interfaces[1]) + + def test_get_dispatchable_attrs(self): + dispatchable_attrs = get_dispatchable_attrs(FakeMultiInterfacesActor) + expected_dispatchable_attrs = [ + 'ActorCls1Method', + 'ActorCls1Method1', + 'ActorCls1Method2', + 'ActorCls2Method', + ] + + method_cnt = 0 + for method in expected_dispatchable_attrs: + if dispatchable_attrs.get(method) is not None: + method_cnt = method_cnt + 1 + + self.assertEqual(len(expected_dispatchable_attrs), method_cnt) diff --git a/tests/actor/utils.py b/tests/actor/utils.py index 3918109c8..7e0d4e750 100644 --- a/tests/actor/utils.py +++ b/tests/actor/utils.py @@ -1,21 +1,21 @@ -import asyncio -from unittest import mock - - -def _async_mock(*args, **kwargs): - m = mock.MagicMock(*args, **kwargs) - - async def mock_coro(*args, **kwargs): - return m(*args, **kwargs) - - mock_coro.mock = m - return mock_coro - - -def _run(coro): - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - return loop.run_until_complete(coro) +import asyncio +from unittest import mock + + +def _async_mock(*args, **kwargs): + m = mock.MagicMock(*args, **kwargs) + + async def mock_coro(*args, **kwargs): + return m(*args, **kwargs) + + mock_coro.mock = m + return mock_coro + + +def _run(coro): + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + return loop.run_until_complete(coro) diff --git a/tests/clients/__init__.py b/tests/clients/__init__.py index c9c4314fb..26b94b07d 100644 --- a/tests/clients/__init__.py +++ b/tests/clients/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/tests/clients/certs.py b/tests/clients/certs.py index a30b25312..d4353b4d7 100644 --- a/tests/clients/certs.py +++ b/tests/clients/certs.py @@ -1,83 +1,83 @@ -import os -import ssl -import grpc - -from OpenSSL import crypto - - -class Certs: - server_type = 'grpc' - - @classmethod - def create_certificates(cls): - # create a key pair - k = crypto.PKey() - k.generate_key(crypto.TYPE_RSA, 4096) - - # create a self-signed cert - cert = crypto.X509() - cert.get_subject().organizationName = 'Dapr' - cert.get_subject().commonName = 'localhost' - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(24 * 60 * 60) - cert.set_issuer(cert.get_subject()) - cert.set_pubkey(k) - - if cls.server_type == 'http': - cert.add_extensions([crypto.X509Extension(b'subjectAltName', False, b'DNS:localhost')]) - - cert.sign(k, 'sha512') - - with open(cls.get_cert_path(), 'wt') as f_cert: - f_cert.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')) - - with open(cls.get_pk_path(), 'wt') as f_key: - f_key.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode('utf-8')) - - @classmethod - def get_pk_path(cls): - return os.path.join(os.path.dirname(__file__), '{}_private.key').format(cls.server_type) - - @classmethod - def get_cert_path(cls): - return os.path.join(os.path.dirname(__file__), '{}_selfsigned.pem').format(cls.server_type) - - @classmethod - def delete_certificates(cls): - pk = cls.get_pk_path() - if os.path.exists(pk): - os.remove(pk) - - cert = cls.get_cert_path() - if os.path.exists(cert): - os.remove(cert) - - -class GrpcCerts(Certs): - server_type = 'grpc' - - -class HttpCerts(Certs): - server_type = 'http' - - -def replacement_get_credentials_func(a): - """ - Used temporarily, so we can trust self-signed certificates in unit tests - until they get their own environment variable - """ - with open(GrpcCerts.get_cert_path(), 'rb') as f: - creds = grpc.ssl_channel_credentials(f.read()) - return creds - - -def replacement_get_health_context(): - """ - This method is used (overwritten) from tests - to return context for self-signed certificates - """ - context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - - return context +import os +import ssl +import grpc + +from OpenSSL import crypto + + +class Certs: + server_type = 'grpc' + + @classmethod + def create_certificates(cls): + # create a key pair + k = crypto.PKey() + k.generate_key(crypto.TYPE_RSA, 4096) + + # create a self-signed cert + cert = crypto.X509() + cert.get_subject().organizationName = 'Dapr' + cert.get_subject().commonName = 'localhost' + cert.gmtime_adj_notBefore(0) + cert.gmtime_adj_notAfter(24 * 60 * 60) + cert.set_issuer(cert.get_subject()) + cert.set_pubkey(k) + + if cls.server_type == 'http': + cert.add_extensions([crypto.X509Extension(b'subjectAltName', False, b'DNS:localhost')]) + + cert.sign(k, 'sha512') + + with open(cls.get_cert_path(), 'wt') as f_cert: + f_cert.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8')) + + with open(cls.get_pk_path(), 'wt') as f_key: + f_key.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k).decode('utf-8')) + + @classmethod + def get_pk_path(cls): + return os.path.join(os.path.dirname(__file__), '{}_private.key').format(cls.server_type) + + @classmethod + def get_cert_path(cls): + return os.path.join(os.path.dirname(__file__), '{}_selfsigned.pem').format(cls.server_type) + + @classmethod + def delete_certificates(cls): + pk = cls.get_pk_path() + if os.path.exists(pk): + os.remove(pk) + + cert = cls.get_cert_path() + if os.path.exists(cert): + os.remove(cert) + + +class GrpcCerts(Certs): + server_type = 'grpc' + + +class HttpCerts(Certs): + server_type = 'http' + + +def replacement_get_credentials_func(a): + """ + Used temporarily, so we can trust self-signed certificates in unit tests + until they get their own environment variable + """ + with open(GrpcCerts.get_cert_path(), 'rb') as f: + creds = grpc.ssl_channel_credentials(f.read()) + return creds + + +def replacement_get_health_context(): + """ + This method is used (overwritten) from tests + to return context for self-signed certificates + """ + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + return context diff --git a/tests/clients/fake_dapr_server.py b/tests/clients/fake_dapr_server.py index 9ae39aa1c..22a7f663e 100644 --- a/tests/clients/fake_dapr_server.py +++ b/tests/clients/fake_dapr_server.py @@ -1,533 +1,533 @@ -import grpc -import json - -from concurrent import futures -from google.protobuf.any_pb2 import Any as GrpcAny -from google.protobuf import empty_pb2, struct_pb2 -from google.rpc import status_pb2, code_pb2 -from grpc_status import rpc_status - -from dapr.clients.grpc._helpers import to_bytes -from dapr.proto import api_service_v1, common_v1, api_v1, appcallback_v1 -from dapr.proto.common.v1.common_pb2 import ConfigurationItem -from dapr.clients.grpc._response import WorkflowRuntimeStatus -from dapr.proto.runtime.v1.dapr_pb2 import ( - ActiveActorsCount, - GetMetadataResponse, - QueryStateItem, - RegisteredComponents, - SetMetadataRequest, - TryLockRequest, - TryLockResponse, - UnlockRequest, - UnlockResponse, - StartWorkflowRequest, - StartWorkflowResponse, - GetWorkflowRequest, - GetWorkflowResponse, - PauseWorkflowRequest, - ResumeWorkflowRequest, - TerminateWorkflowRequest, - PurgeWorkflowRequest, - RaiseEventWorkflowRequest, - EncryptRequest, - EncryptResponse, - DecryptRequest, - DecryptResponse, -) -from typing import Dict - -from tests.clients.certs import GrpcCerts -from tests.clients.fake_http_server import FakeHttpServer - - -class FakeDaprSidecar(api_service_v1.DaprServicer): - def __init__(self, grpc_port: int = 50001, http_port: int = 8080): - self.grpc_port = grpc_port - self.http_port = http_port - self._grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) - self._http_server = FakeHttpServer(self.http_port) # Needed for the healthcheck endpoint - api_service_v1.add_DaprServicer_to_server(self, self._grpc_server) - self.store = {} - self.shutdown_received = False - self.locks_to_owner = {} # (store_name, resource_id) -> lock_owner - self.workflow_status = {} - self.workflow_options: Dict[str, str] = {} - self.metadata: Dict[str, str] = {} - self._next_exception = None - - def start(self): - self._grpc_server.add_insecure_port(f'[::]:{self.grpc_port}') - self._grpc_server.start() - self._http_server.start() - - def start_secure(self): - GrpcCerts.create_certificates() - - private_key_file = open(GrpcCerts.get_pk_path(), 'rb') - private_key_content = private_key_file.read() - private_key_file.close() - - certificate_chain_file = open(GrpcCerts.get_cert_path(), 'rb') - certificate_chain_content = certificate_chain_file.read() - certificate_chain_file.close() - - credentials = grpc.ssl_server_credentials( - [(private_key_content, certificate_chain_content)] - ) - - self._grpc_server.add_secure_port(f'[::]:{self.grpc_port}', credentials) - self._grpc_server.start() - - self._http_server.start_secure() - - def stop(self): - self._http_server.shutdown_server() - self._grpc_server.stop(None) - - def stop_secure(self): - self._http_server.shutdown_server() - self._grpc_server.stop(None) - GrpcCerts.delete_certificates() - - def raise_exception_on_next_call(self, exception): - """ - Raise an exception on the next call to the server. - Useful for testing error handling. - @param exception: - """ - self._next_exception = exception - - def check_for_exception(self, context): - """ - Check if an exception was raised on the last call to the server. - Useful for testing error handling. - @return: The raised exception, or None if no exception was raised. - """ - - exception = self._next_exception - self._next_exception = None - - if exception is None: - return None - - context.abort_with_status(rpc_status.to_status(exception)) - - def InvokeService(self, request, context) -> common_v1.InvokeResponse: - headers = () - trailers = () - - for k, v in context.invocation_metadata(): - headers = headers + (('h' + k, v),) - trailers = trailers + (('t' + k, v),) - - resp = GrpcAny() - content_type = '' - - if request.message.method == 'bytes': - resp.value = request.message.data.value - content_type = request.message.content_type - else: - resp = request.message.data - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - - return common_v1.InvokeResponse(data=resp, content_type=content_type) - - def InvokeBinding(self, request, context) -> api_v1.InvokeBindingResponse: - headers = () - trailers = () - - for k, v in request.metadata.items(): - headers = headers + (('h' + k, v),) - trailers = trailers + (('t' + k, v),) - - resp_data = b'INVALID' - metadata = {} - - if request.operation == 'create': - resp_data = request.data - metadata = request.metadata - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - - return api_v1.InvokeBindingResponse(data=resp_data, metadata=metadata) - - def PublishEvent(self, request, context): - self.check_for_exception(context) - - headers = () - trailers = () - if request.topic: - headers = headers + (('htopic', request.topic),) - trailers = trailers + (('ttopic', request.topic),) - if request.data: - headers = headers + (('hdata', request.data),) - trailers = trailers + (('hdata', request.data),) - if request.data_content_type: - headers = headers + (('data_content_type', request.data_content_type),) - trailers = trailers + (('data_content_type', request.data_content_type),) - if request.metadata['rawPayload']: - headers = headers + (('metadata_raw_payload', request.metadata['rawPayload']),) - if request.metadata['ttlInSeconds']: - headers = headers + (('metadata_ttl_in_seconds', request.metadata['ttlInSeconds']),) - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - return empty_pb2.Empty() - - def SubscribeTopicEventsAlpha1(self, request_iterator, context): - for request in request_iterator: - if request.HasField('initial_request'): - yield api_v1.SubscribeTopicEventsResponseAlpha1( - initial_response=api_v1.SubscribeTopicEventsResponseInitialAlpha1() - ) - break - - extensions = struct_pb2.Struct() - extensions.update({'field1': 'value1', 'field2': 42, 'field3': True}) - - msg1 = appcallback_v1.TopicEventRequest( - id='111', - topic='TOPIC_A', - data=b'hello2', - source='app1', - data_content_type='text/plain', - type='com.example.type2', - pubsub_name='pubsub', - spec_version='1.0', - extensions=extensions, - ) - yield api_v1.SubscribeTopicEventsResponseAlpha1(event_message=msg1) - - for request in request_iterator: - if request.HasField('event_processed'): - break - - msg2 = appcallback_v1.TopicEventRequest( - id='222', - topic='TOPIC_A', - data=b'{"a": 1}', - source='app1', - data_content_type='application/json', - type='com.example.type2', - pubsub_name='pubsub', - spec_version='1.0', - extensions=extensions, - ) - yield api_v1.SubscribeTopicEventsResponseAlpha1(event_message=msg2) - - for request in request_iterator: - if request.HasField('event_processed'): - break - - # On the third message simulate a disconnection - context.abort_with_status( - rpc_status.to_status( - status_pb2.Status(code=code_pb2.UNAVAILABLE, message='Simulated disconnection') - ) - ) - - def SaveState(self, request, context): - self.check_for_exception(context) - - headers = () - trailers = () - for state in request.states: - data = state.value - if state.metadata['capitalize']: - data = to_bytes(data.decode('utf-8').capitalize()) - if state.HasField('etag'): - self.store[state.key] = (data, state.etag.value) - else: - self.store[state.key] = (data, 'ETAG_WAS_NONE') - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - return empty_pb2.Empty() - - def ExecuteStateTransaction(self, request, context): - self.check_for_exception(context) - - headers = () - trailers = () - for operation in request.operations: - if operation.operationType == 'delete': - del self.store[operation.request.key] - else: - etag = 'ETAG_WAS_NONE' - if operation.request.HasField('etag'): - etag = operation.request.etag.value - self.store[operation.request.key] = (operation.request.value, etag) - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - return empty_pb2.Empty() - - def GetState(self, request, context): - self.check_for_exception(context) - - key = request.key - if key not in self.store: - return empty_pb2.Empty() - else: - data, etag = self.store[key] - if request.metadata['upper']: - data = to_bytes(data.decode('utf-8').upper()) - return api_v1.GetStateResponse(data=data, etag=etag) - - def GetBulkState(self, request, context): - self.check_for_exception(context) - - items = [] - for key in request.keys: - req = api_v1.GetStateRequest(store_name=request.store_name, key=key) - res = self.GetState(req, context) - data = res.data - etag = res.etag - if request.metadata['upper']: - data = to_bytes(data.decode('utf-8').upper()) - items.append(api_v1.BulkStateItem(key=key, etag=etag, data=data)) - return api_v1.GetBulkStateResponse(items=items) - - def DeleteState(self, request, context): - self.check_for_exception(context) - - headers = () - trailers = () - key = request.key - if key in self.store: - del self.store[key] - else: - if request.metadata['must_delete']: - raise ValueError('delete failed') - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - return empty_pb2.Empty() - - def GetSecret(self, request, context) -> api_v1.GetSecretResponse: - headers = () - trailers = () - - key = request.key - - headers = headers + (('keyh', key),) - trailers = trailers + (('keyt', key),) - - resp = {key: 'val'} - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - - return api_v1.GetSecretResponse(data=resp) - - def GetBulkSecret(self, request, context) -> api_v1.GetBulkSecretResponse: - headers = () - trailers = () - - headers = headers + (('keyh', 'bulk'),) - trailers = trailers + (('keyt', 'bulk'),) - - resp = {'keya': api_v1.SecretResponse(secrets={'keyb': 'val'})} - - context.send_initial_metadata(headers) - context.set_trailing_metadata(trailers) - - return api_v1.GetBulkSecretResponse(data=resp) - - def GetConfiguration(self, request, context): - items = dict() - for key in request.keys: - items[str(key)] = ConfigurationItem(value='value', version='1.5.0') - return api_v1.GetConfigurationResponse(items=items) - - def SubscribeConfiguration(self, request, context): - items = [] - for key in request.keys: - item = {'key': key, 'value': 'value', 'version': '1.5.0', 'metadata': {}} - items.append(item) - response = {items: items} - responses = [] - responses.append(response) - return api_v1.SubscribeConfigurationResponse(responses=responses) - - def UnsubscribeConfiguration(self, request, context): - return api_v1.UnsubscribeConfigurationResponse(ok=True) - - def QueryStateAlpha1(self, request, context): - self.check_for_exception(context) - - items = [ - QueryStateItem(key=str(key), data=bytes('value of ' + str(key), 'UTF-8')) - for key in range(1, 11) - ] - query = json.loads(request.query) - - tokenIndex = 1 - if 'page' in query: - if 'token' in query['page']: - # For testing purposes, we return a token that is the same as the key - tokenIndex = int(query['page']['token']) - items = items[tokenIndex - 1 :] - if 'limit' in query['page']: - limit = int(query['page']['limit']) - if len(items) > limit: - items = items[:limit] - tokenIndex = tokenIndex + len(items) - - return api_v1.QueryStateResponse(results=items, token=str(tokenIndex)) - - def TryLockAlpha1(self, request: TryLockRequest, context): - lock_id = (request.store_name, request.resource_id) - - if lock_id not in self.locks_to_owner: - self.locks_to_owner[lock_id] = request.lock_owner - return TryLockResponse(success=True) - else: - # Lock already acquired - return TryLockResponse(success=False) - - def UnlockAlpha1(self, request: UnlockRequest, context): - lock_id = (request.store_name, request.resource_id) - - if lock_id not in self.locks_to_owner: - return UnlockResponse(status=UnlockResponse.Status.LOCK_DOES_NOT_EXIST) - elif self.locks_to_owner[lock_id] == request.lock_owner: - del self.locks_to_owner[lock_id] - return UnlockResponse(status=UnlockResponse.Status.SUCCESS) - else: - return UnlockResponse(status=UnlockResponse.Status.LOCK_BELONGS_TO_OTHERS) - - def EncryptAlpha1(self, requests: EncryptRequest, context): - for req in requests: - # mock encrypt operation by uppercasing the data - req.payload.data = req.payload.data.upper() - yield EncryptResponse(payload=req.payload) - - def DecryptAlpha1(self, requests: DecryptRequest, context): - for req in requests: - # mock decrypt operation by lowercasing the data - req.payload.data = req.payload.data.lower() - yield DecryptResponse(payload=req.payload) - - def StartWorkflowBeta1(self, request: StartWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id not in self.workflow_status: - self.workflow_status[instance_id] = WorkflowRuntimeStatus.RUNNING - return StartWorkflowResponse(instance_id=instance_id) - else: - # workflow already running - raise Exception('Unable to start insance of the workflow') - - def GetWorkflowBeta1(self, request: GetWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id in self.workflow_status: - status = str(self.workflow_status[instance_id])[len('WorkflowRuntimeStatus.') :] - return GetWorkflowResponse( - instance_id=instance_id, - workflow_name='example', - created_at=None, - last_updated_at=None, - runtime_status=status, - properties=self.workflow_options, - ) - else: - # workflow non-existent - raise Exception('Workflow instance does not exist') - - def PauseWorkflowBeta1(self, request: PauseWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id in self.workflow_status: - self.workflow_status[instance_id] = WorkflowRuntimeStatus.SUSPENDED - return empty_pb2.Empty() - else: - # workflow non-existent - raise Exception('Workflow instance could not be paused') - - def ResumeWorkflowBeta1(self, request: ResumeWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id in self.workflow_status: - self.workflow_status[instance_id] = WorkflowRuntimeStatus.RUNNING - return empty_pb2.Empty() - else: - # workflow non-existent - raise Exception('Workflow instance could not be resumed') - - def TerminateWorkflowBeta1(self, request: TerminateWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id in self.workflow_status: - self.workflow_status[instance_id] = WorkflowRuntimeStatus.TERMINATED - return empty_pb2.Empty() - else: - # workflow non-existent - raise Exception('Workflow instance could not be terminated') - - def PurgeWorkflowBeta1(self, request: PurgeWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id in self.workflow_status: - del self.workflow_status[instance_id] - return empty_pb2.Empty() - else: - # workflow non-existent - raise Exception('Workflow instance could not be purged') - - def RaiseEventWorkflowBeta1(self, request: RaiseEventWorkflowRequest, context): - instance_id = request.instance_id - - if instance_id in self.workflow_status: - self.workflow_options[instance_id] = request.event_data - return empty_pb2.Empty() - else: - raise Exception('Unable to raise event on workflow instance') - - def GetMetadata(self, request, context): - self.check_for_exception(context) - - return GetMetadataResponse( - id='myapp', - active_actors_count=[ - ActiveActorsCount( - type='Nichelle Nichols', - count=1, - ), - ], - registered_components=[ - RegisteredComponents( - name='lockstore', - type='lock.redis', - version='', - # Missing capabilities definition, - ), - RegisteredComponents( - name='pubsub', type='pubsub.redis', version='v1', capabilities=[] - ), - RegisteredComponents( - name='statestore', - type='state.redis', - version='v1', - capabilities=[ - 'ETAG', - 'TRANSACTIONAL', - 'ACTOR', - ], - ), - ], - extended_metadata=self.metadata, - ) - - def SetMetadata(self, request: SetMetadataRequest, context): - self.metadata[request.key] = request.value - return empty_pb2.Empty() - - def Shutdown(self, request, context): - self.shutdown_received = True - return empty_pb2.Empty() +import grpc +import json + +from concurrent import futures +from google.protobuf.any_pb2 import Any as GrpcAny +from google.protobuf import empty_pb2, struct_pb2 +from google.rpc import status_pb2, code_pb2 +from grpc_status import rpc_status + +from dapr.clients.grpc._helpers import to_bytes +from dapr.proto import api_service_v1, common_v1, api_v1, appcallback_v1 +from dapr.proto.common.v1.common_pb2 import ConfigurationItem +from dapr.clients.grpc._response import WorkflowRuntimeStatus +from dapr.proto.runtime.v1.dapr_pb2 import ( + ActiveActorsCount, + GetMetadataResponse, + QueryStateItem, + RegisteredComponents, + SetMetadataRequest, + TryLockRequest, + TryLockResponse, + UnlockRequest, + UnlockResponse, + StartWorkflowRequest, + StartWorkflowResponse, + GetWorkflowRequest, + GetWorkflowResponse, + PauseWorkflowRequest, + ResumeWorkflowRequest, + TerminateWorkflowRequest, + PurgeWorkflowRequest, + RaiseEventWorkflowRequest, + EncryptRequest, + EncryptResponse, + DecryptRequest, + DecryptResponse, +) +from typing import Dict + +from tests.clients.certs import GrpcCerts +from tests.clients.fake_http_server import FakeHttpServer + + +class FakeDaprSidecar(api_service_v1.DaprServicer): + def __init__(self, grpc_port: int = 50001, http_port: int = 8080): + self.grpc_port = grpc_port + self.http_port = http_port + self._grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) + self._http_server = FakeHttpServer(self.http_port) # Needed for the healthcheck endpoint + api_service_v1.add_DaprServicer_to_server(self, self._grpc_server) + self.store = {} + self.shutdown_received = False + self.locks_to_owner = {} # (store_name, resource_id) -> lock_owner + self.workflow_status = {} + self.workflow_options: Dict[str, str] = {} + self.metadata: Dict[str, str] = {} + self._next_exception = None + + def start(self): + self._grpc_server.add_insecure_port(f'[::]:{self.grpc_port}') + self._grpc_server.start() + self._http_server.start() + + def start_secure(self): + GrpcCerts.create_certificates() + + private_key_file = open(GrpcCerts.get_pk_path(), 'rb') + private_key_content = private_key_file.read() + private_key_file.close() + + certificate_chain_file = open(GrpcCerts.get_cert_path(), 'rb') + certificate_chain_content = certificate_chain_file.read() + certificate_chain_file.close() + + credentials = grpc.ssl_server_credentials( + [(private_key_content, certificate_chain_content)] + ) + + self._grpc_server.add_secure_port(f'[::]:{self.grpc_port}', credentials) + self._grpc_server.start() + + self._http_server.start_secure() + + def stop(self): + self._http_server.shutdown_server() + self._grpc_server.stop(None) + + def stop_secure(self): + self._http_server.shutdown_server() + self._grpc_server.stop(None) + GrpcCerts.delete_certificates() + + def raise_exception_on_next_call(self, exception): + """ + Raise an exception on the next call to the server. + Useful for testing error handling. + @param exception: + """ + self._next_exception = exception + + def check_for_exception(self, context): + """ + Check if an exception was raised on the last call to the server. + Useful for testing error handling. + @return: The raised exception, or None if no exception was raised. + """ + + exception = self._next_exception + self._next_exception = None + + if exception is None: + return None + + context.abort_with_status(rpc_status.to_status(exception)) + + def InvokeService(self, request, context) -> common_v1.InvokeResponse: + headers = () + trailers = () + + for k, v in context.invocation_metadata(): + headers = headers + (('h' + k, v),) + trailers = trailers + (('t' + k, v),) + + resp = GrpcAny() + content_type = '' + + if request.message.method == 'bytes': + resp.value = request.message.data.value + content_type = request.message.content_type + else: + resp = request.message.data + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + + return common_v1.InvokeResponse(data=resp, content_type=content_type) + + def InvokeBinding(self, request, context) -> api_v1.InvokeBindingResponse: + headers = () + trailers = () + + for k, v in request.metadata.items(): + headers = headers + (('h' + k, v),) + trailers = trailers + (('t' + k, v),) + + resp_data = b'INVALID' + metadata = {} + + if request.operation == 'create': + resp_data = request.data + metadata = request.metadata + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + + return api_v1.InvokeBindingResponse(data=resp_data, metadata=metadata) + + def PublishEvent(self, request, context): + self.check_for_exception(context) + + headers = () + trailers = () + if request.topic: + headers = headers + (('htopic', request.topic),) + trailers = trailers + (('ttopic', request.topic),) + if request.data: + headers = headers + (('hdata', request.data),) + trailers = trailers + (('hdata', request.data),) + if request.data_content_type: + headers = headers + (('data_content_type', request.data_content_type),) + trailers = trailers + (('data_content_type', request.data_content_type),) + if request.metadata['rawPayload']: + headers = headers + (('metadata_raw_payload', request.metadata['rawPayload']),) + if request.metadata['ttlInSeconds']: + headers = headers + (('metadata_ttl_in_seconds', request.metadata['ttlInSeconds']),) + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + return empty_pb2.Empty() + + def SubscribeTopicEventsAlpha1(self, request_iterator, context): + for request in request_iterator: + if request.HasField('initial_request'): + yield api_v1.SubscribeTopicEventsResponseAlpha1( + initial_response=api_v1.SubscribeTopicEventsResponseInitialAlpha1() + ) + break + + extensions = struct_pb2.Struct() + extensions.update({'field1': 'value1', 'field2': 42, 'field3': True}) + + msg1 = appcallback_v1.TopicEventRequest( + id='111', + topic='TOPIC_A', + data=b'hello2', + source='app1', + data_content_type='text/plain', + type='com.example.type2', + pubsub_name='pubsub', + spec_version='1.0', + extensions=extensions, + ) + yield api_v1.SubscribeTopicEventsResponseAlpha1(event_message=msg1) + + for request in request_iterator: + if request.HasField('event_processed'): + break + + msg2 = appcallback_v1.TopicEventRequest( + id='222', + topic='TOPIC_A', + data=b'{"a": 1}', + source='app1', + data_content_type='application/json', + type='com.example.type2', + pubsub_name='pubsub', + spec_version='1.0', + extensions=extensions, + ) + yield api_v1.SubscribeTopicEventsResponseAlpha1(event_message=msg2) + + for request in request_iterator: + if request.HasField('event_processed'): + break + + # On the third message simulate a disconnection + context.abort_with_status( + rpc_status.to_status( + status_pb2.Status(code=code_pb2.UNAVAILABLE, message='Simulated disconnection') + ) + ) + + def SaveState(self, request, context): + self.check_for_exception(context) + + headers = () + trailers = () + for state in request.states: + data = state.value + if state.metadata['capitalize']: + data = to_bytes(data.decode('utf-8').capitalize()) + if state.HasField('etag'): + self.store[state.key] = (data, state.etag.value) + else: + self.store[state.key] = (data, 'ETAG_WAS_NONE') + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + return empty_pb2.Empty() + + def ExecuteStateTransaction(self, request, context): + self.check_for_exception(context) + + headers = () + trailers = () + for operation in request.operations: + if operation.operationType == 'delete': + del self.store[operation.request.key] + else: + etag = 'ETAG_WAS_NONE' + if operation.request.HasField('etag'): + etag = operation.request.etag.value + self.store[operation.request.key] = (operation.request.value, etag) + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + return empty_pb2.Empty() + + def GetState(self, request, context): + self.check_for_exception(context) + + key = request.key + if key not in self.store: + return empty_pb2.Empty() + else: + data, etag = self.store[key] + if request.metadata['upper']: + data = to_bytes(data.decode('utf-8').upper()) + return api_v1.GetStateResponse(data=data, etag=etag) + + def GetBulkState(self, request, context): + self.check_for_exception(context) + + items = [] + for key in request.keys: + req = api_v1.GetStateRequest(store_name=request.store_name, key=key) + res = self.GetState(req, context) + data = res.data + etag = res.etag + if request.metadata['upper']: + data = to_bytes(data.decode('utf-8').upper()) + items.append(api_v1.BulkStateItem(key=key, etag=etag, data=data)) + return api_v1.GetBulkStateResponse(items=items) + + def DeleteState(self, request, context): + self.check_for_exception(context) + + headers = () + trailers = () + key = request.key + if key in self.store: + del self.store[key] + else: + if request.metadata['must_delete']: + raise ValueError('delete failed') + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + return empty_pb2.Empty() + + def GetSecret(self, request, context) -> api_v1.GetSecretResponse: + headers = () + trailers = () + + key = request.key + + headers = headers + (('keyh', key),) + trailers = trailers + (('keyt', key),) + + resp = {key: 'val'} + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + + return api_v1.GetSecretResponse(data=resp) + + def GetBulkSecret(self, request, context) -> api_v1.GetBulkSecretResponse: + headers = () + trailers = () + + headers = headers + (('keyh', 'bulk'),) + trailers = trailers + (('keyt', 'bulk'),) + + resp = {'keya': api_v1.SecretResponse(secrets={'keyb': 'val'})} + + context.send_initial_metadata(headers) + context.set_trailing_metadata(trailers) + + return api_v1.GetBulkSecretResponse(data=resp) + + def GetConfiguration(self, request, context): + items = dict() + for key in request.keys: + items[str(key)] = ConfigurationItem(value='value', version='1.5.0') + return api_v1.GetConfigurationResponse(items=items) + + def SubscribeConfiguration(self, request, context): + items = [] + for key in request.keys: + item = {'key': key, 'value': 'value', 'version': '1.5.0', 'metadata': {}} + items.append(item) + response = {items: items} + responses = [] + responses.append(response) + return api_v1.SubscribeConfigurationResponse(responses=responses) + + def UnsubscribeConfiguration(self, request, context): + return api_v1.UnsubscribeConfigurationResponse(ok=True) + + def QueryStateAlpha1(self, request, context): + self.check_for_exception(context) + + items = [ + QueryStateItem(key=str(key), data=bytes('value of ' + str(key), 'UTF-8')) + for key in range(1, 11) + ] + query = json.loads(request.query) + + tokenIndex = 1 + if 'page' in query: + if 'token' in query['page']: + # For testing purposes, we return a token that is the same as the key + tokenIndex = int(query['page']['token']) + items = items[tokenIndex - 1 :] + if 'limit' in query['page']: + limit = int(query['page']['limit']) + if len(items) > limit: + items = items[:limit] + tokenIndex = tokenIndex + len(items) + + return api_v1.QueryStateResponse(results=items, token=str(tokenIndex)) + + def TryLockAlpha1(self, request: TryLockRequest, context): + lock_id = (request.store_name, request.resource_id) + + if lock_id not in self.locks_to_owner: + self.locks_to_owner[lock_id] = request.lock_owner + return TryLockResponse(success=True) + else: + # Lock already acquired + return TryLockResponse(success=False) + + def UnlockAlpha1(self, request: UnlockRequest, context): + lock_id = (request.store_name, request.resource_id) + + if lock_id not in self.locks_to_owner: + return UnlockResponse(status=UnlockResponse.Status.LOCK_DOES_NOT_EXIST) + elif self.locks_to_owner[lock_id] == request.lock_owner: + del self.locks_to_owner[lock_id] + return UnlockResponse(status=UnlockResponse.Status.SUCCESS) + else: + return UnlockResponse(status=UnlockResponse.Status.LOCK_BELONGS_TO_OTHERS) + + def EncryptAlpha1(self, requests: EncryptRequest, context): + for req in requests: + # mock encrypt operation by uppercasing the data + req.payload.data = req.payload.data.upper() + yield EncryptResponse(payload=req.payload) + + def DecryptAlpha1(self, requests: DecryptRequest, context): + for req in requests: + # mock decrypt operation by lowercasing the data + req.payload.data = req.payload.data.lower() + yield DecryptResponse(payload=req.payload) + + def StartWorkflowBeta1(self, request: StartWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id not in self.workflow_status: + self.workflow_status[instance_id] = WorkflowRuntimeStatus.RUNNING + return StartWorkflowResponse(instance_id=instance_id) + else: + # workflow already running + raise Exception('Unable to start insance of the workflow') + + def GetWorkflowBeta1(self, request: GetWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id in self.workflow_status: + status = str(self.workflow_status[instance_id])[len('WorkflowRuntimeStatus.') :] + return GetWorkflowResponse( + instance_id=instance_id, + workflow_name='example', + created_at=None, + last_updated_at=None, + runtime_status=status, + properties=self.workflow_options, + ) + else: + # workflow non-existent + raise Exception('Workflow instance does not exist') + + def PauseWorkflowBeta1(self, request: PauseWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id in self.workflow_status: + self.workflow_status[instance_id] = WorkflowRuntimeStatus.SUSPENDED + return empty_pb2.Empty() + else: + # workflow non-existent + raise Exception('Workflow instance could not be paused') + + def ResumeWorkflowBeta1(self, request: ResumeWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id in self.workflow_status: + self.workflow_status[instance_id] = WorkflowRuntimeStatus.RUNNING + return empty_pb2.Empty() + else: + # workflow non-existent + raise Exception('Workflow instance could not be resumed') + + def TerminateWorkflowBeta1(self, request: TerminateWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id in self.workflow_status: + self.workflow_status[instance_id] = WorkflowRuntimeStatus.TERMINATED + return empty_pb2.Empty() + else: + # workflow non-existent + raise Exception('Workflow instance could not be terminated') + + def PurgeWorkflowBeta1(self, request: PurgeWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id in self.workflow_status: + del self.workflow_status[instance_id] + return empty_pb2.Empty() + else: + # workflow non-existent + raise Exception('Workflow instance could not be purged') + + def RaiseEventWorkflowBeta1(self, request: RaiseEventWorkflowRequest, context): + instance_id = request.instance_id + + if instance_id in self.workflow_status: + self.workflow_options[instance_id] = request.event_data + return empty_pb2.Empty() + else: + raise Exception('Unable to raise event on workflow instance') + + def GetMetadata(self, request, context): + self.check_for_exception(context) + + return GetMetadataResponse( + id='myapp', + active_actors_count=[ + ActiveActorsCount( + type='Nichelle Nichols', + count=1, + ), + ], + registered_components=[ + RegisteredComponents( + name='lockstore', + type='lock.redis', + version='', + # Missing capabilities definition, + ), + RegisteredComponents( + name='pubsub', type='pubsub.redis', version='v1', capabilities=[] + ), + RegisteredComponents( + name='statestore', + type='state.redis', + version='v1', + capabilities=[ + 'ETAG', + 'TRANSACTIONAL', + 'ACTOR', + ], + ), + ], + extended_metadata=self.metadata, + ) + + def SetMetadata(self, request: SetMetadataRequest, context): + self.metadata[request.key] = request.value + return empty_pb2.Empty() + + def Shutdown(self, request, context): + self.shutdown_received = True + return empty_pb2.Empty() diff --git a/tests/clients/fake_http_server.py b/tests/clients/fake_http_server.py index e08e82d29..7f5616a2f 100644 --- a/tests/clients/fake_http_server.py +++ b/tests/clients/fake_http_server.py @@ -1,117 +1,117 @@ -import time -from ssl import PROTOCOL_TLS_SERVER, SSLContext - -from threading import Thread -from http.server import BaseHTTPRequestHandler, HTTPServer - -from tests.clients.certs import HttpCerts - - -class DaprHandler(BaseHTTPRequestHandler): - protocol_version = 'HTTP/1.1' - - def serve_forever(self): - while not self.running: - self.handle_request() - - def do_request(self, verb): - if self.path == '/v1.0/healthz/outbound': - self.send_response(200) - self.end_headers() - return - - if self.server.sleep_time is not None: - time.sleep(self.server.sleep_time) - self.received_verb = verb - self.server.request_headers = self.headers - if 'Content-Length' in self.headers: - content_length = int(self.headers['Content-Length']) - - self.server.request_body += self.rfile.read(content_length) - - self.send_response(self.server.response_code) - for key, value in self.server.response_header_list: - self.send_header(key, value) - self.send_header('Content-Length', str(len(self.server.response_body))) - self.end_headers() - - self.server.path = self.path - - self.wfile.write(self.server.response_body) - - def do_GET(self): - self.do_request('GET') - - def do_POST(self): - self.do_request('POST') - - def do_PUT(self): - self.do_request('PUT') - - def do_DELETE(self): - self.do_request('DELETE') - - -class FakeHttpServer(Thread): - secure = False - - def __init__(self, port: int = 8080): - super().__init__() - - self.port = port - self.server = HTTPServer(('localhost', self.port), DaprHandler) - - self.server.response_body = b'' - self.server.response_code = 200 - self.server.response_header_list = [] - self.server.request_body = b'' - self.server.sleep_time = None - - def get_port(self): - return self.server.socket.getsockname()[1] - - def reply_header(self, key, value): - self.server.response_header_list.append((key, value)) - - def get_request_headers(self): - return self.server.request_headers - - def shutdown_server(self): - self.server.shutdown() - self.server.socket.close() - self.join() - if self.secure: - HttpCerts.delete_certificates() - - def request_path(self): - return self.server.path - - def set_response(self, body: bytes, code=200): - self.server.response_body = body - self.server.response_code = code - - def get_request_body(self): - return self.server.request_body - - def set_server_delay(self, delay_seconds): - self.server.sleep_time = delay_seconds - - def start_secure(self): - self.secure = True - - HttpCerts.create_certificates() - ssl_context = SSLContext(PROTOCOL_TLS_SERVER) - ssl_context.load_cert_chain(HttpCerts.get_cert_path(), HttpCerts.get_pk_path()) - self.server.socket = ssl_context.wrap_socket(self.server.socket, server_side=True) - - self.start() - - def run(self): - self.server.serve_forever() - - def reset(self): - self.server.response_body = b'' - self.server.response_code = 200 - self.server.response_header_list = [] - self.server.request_body = b'' - self.server.sleep_time = None +import time +from ssl import PROTOCOL_TLS_SERVER, SSLContext + +from threading import Thread +from http.server import BaseHTTPRequestHandler, HTTPServer + +from tests.clients.certs import HttpCerts + + +class DaprHandler(BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' + + def serve_forever(self): + while not self.running: + self.handle_request() + + def do_request(self, verb): + if self.path == '/v1.0/healthz/outbound': + self.send_response(200) + self.end_headers() + return + + if self.server.sleep_time is not None: + time.sleep(self.server.sleep_time) + self.received_verb = verb + self.server.request_headers = self.headers + if 'Content-Length' in self.headers: + content_length = int(self.headers['Content-Length']) + + self.server.request_body += self.rfile.read(content_length) + + self.send_response(self.server.response_code) + for key, value in self.server.response_header_list: + self.send_header(key, value) + self.send_header('Content-Length', str(len(self.server.response_body))) + self.end_headers() + + self.server.path = self.path + + self.wfile.write(self.server.response_body) + + def do_GET(self): + self.do_request('GET') + + def do_POST(self): + self.do_request('POST') + + def do_PUT(self): + self.do_request('PUT') + + def do_DELETE(self): + self.do_request('DELETE') + + +class FakeHttpServer(Thread): + secure = False + + def __init__(self, port: int = 8080): + super().__init__() + + self.port = port + self.server = HTTPServer(('localhost', self.port), DaprHandler) + + self.server.response_body = b'' + self.server.response_code = 200 + self.server.response_header_list = [] + self.server.request_body = b'' + self.server.sleep_time = None + + def get_port(self): + return self.server.socket.getsockname()[1] + + def reply_header(self, key, value): + self.server.response_header_list.append((key, value)) + + def get_request_headers(self): + return self.server.request_headers + + def shutdown_server(self): + self.server.shutdown() + self.server.socket.close() + self.join() + if self.secure: + HttpCerts.delete_certificates() + + def request_path(self): + return self.server.path + + def set_response(self, body: bytes, code=200): + self.server.response_body = body + self.server.response_code = code + + def get_request_body(self): + return self.server.request_body + + def set_server_delay(self, delay_seconds): + self.server.sleep_time = delay_seconds + + def start_secure(self): + self.secure = True + + HttpCerts.create_certificates() + ssl_context = SSLContext(PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(HttpCerts.get_cert_path(), HttpCerts.get_pk_path()) + self.server.socket = ssl_context.wrap_socket(self.server.socket, server_side=True) + + self.start() + + def run(self): + self.server.serve_forever() + + def reset(self): + self.server.response_body = b'' + self.server.response_code = 200 + self.server.response_header_list = [] + self.server.request_body = b'' + self.server.sleep_time = None diff --git a/tests/clients/test_client_interceptor.py b/tests/clients/test_client_interceptor.py index 52ca9618b..b208655a1 100644 --- a/tests/clients/test_client_interceptor.py +++ b/tests/clients/test_client_interceptor.py @@ -1,48 +1,48 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.clients.grpc.interceptors import DaprClientInterceptor, _ClientCallDetails - - -class DaprClientInterceptorTests(unittest.TestCase): - def setUp(self): - self._fake_request = 'fake request' - - def fake_continuation(self, call_details, request): - return call_details - - def test_intercept_unary_unary_single_header(self): - interceptor = DaprClientInterceptor([('api-token', 'test-token')]) - call_details = _ClientCallDetails('method1', 10, None, None, None, None) - response = interceptor.intercept_unary_unary( - self.fake_continuation, call_details, self._fake_request - ) - - self.assertIsNotNone(response) - self.assertEqual(1, len(response.metadata)) - self.assertEqual([('api-token', 'test-token')], response.metadata) - - def test_intercept_unary_unary_existing_metadata(self): - interceptor = DaprClientInterceptor([('api-token', 'test-token')]) - call_details = _ClientCallDetails('method1', 10, [('header', 'value')], None, None, None) - response = interceptor.intercept_unary_unary( - self.fake_continuation, call_details, self._fake_request - ) - - self.assertIsNotNone(response) - self.assertEqual(2, len(response.metadata)) - self.assertEqual([('header', 'value'), ('api-token', 'test-token')], response.metadata) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.clients.grpc.interceptors import DaprClientInterceptor, _ClientCallDetails + + +class DaprClientInterceptorTests(unittest.TestCase): + def setUp(self): + self._fake_request = 'fake request' + + def fake_continuation(self, call_details, request): + return call_details + + def test_intercept_unary_unary_single_header(self): + interceptor = DaprClientInterceptor([('api-token', 'test-token')]) + call_details = _ClientCallDetails('method1', 10, None, None, None, None) + response = interceptor.intercept_unary_unary( + self.fake_continuation, call_details, self._fake_request + ) + + self.assertIsNotNone(response) + self.assertEqual(1, len(response.metadata)) + self.assertEqual([('api-token', 'test-token')], response.metadata) + + def test_intercept_unary_unary_existing_metadata(self): + interceptor = DaprClientInterceptor([('api-token', 'test-token')]) + call_details = _ClientCallDetails('method1', 10, [('header', 'value')], None, None, None) + response = interceptor.intercept_unary_unary( + self.fake_continuation, call_details, self._fake_request + ) + + self.assertIsNotNone(response) + self.assertEqual(2, len(response.metadata)) + self.assertEqual([('header', 'value'), ('api-token', 'test-token')], response.metadata) diff --git a/tests/clients/test_dapr_grpc_client.py b/tests/clients/test_dapr_grpc_client.py index d3eab2363..68f4457c9 100644 --- a/tests/clients/test_dapr_grpc_client.py +++ b/tests/clients/test_dapr_grpc_client.py @@ -1,1169 +1,1169 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import json -import socket -import tempfile -import time -import unittest -import uuid -import asyncio - -from unittest.mock import patch - -from google.rpc import status_pb2, code_pb2 - -from dapr.clients.exceptions import DaprGrpcError -from dapr.clients.grpc.client import DaprGrpcClient -from dapr.clients import DaprClient -from dapr.clients.grpc.subscription import StreamInactiveError -from dapr.proto import common_v1 -from .fake_dapr_server import FakeDaprSidecar -from dapr.conf import settings -from dapr.clients.grpc._helpers import to_bytes -from dapr.clients.grpc._request import TransactionalStateOperation -from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.clients.grpc._response import ( - ConfigurationItem, - ConfigurationResponse, - ConfigurationWatcher, - UnlockResponseStatus, - WorkflowRuntimeStatus, - TopicEventResponse, -) - - -class DaprGrpcClientTests(unittest.TestCase): - grpc_port = 50001 - http_port = 3500 - scheme = '' - error = None - - @classmethod - def setUpClass(cls): - cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) - cls._fake_dapr_server.start() - settings.DAPR_HTTP_PORT = cls.http_port - settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(cls.http_port) - - @classmethod - def tearDownClass(cls): - cls._fake_dapr_server.stop() - - def test_http_extension(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - - # Test POST verb without querystring - ext = dapr._get_http_extension('POST') - self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) - - # Test Non-supported http verb - with self.assertRaises(ValueError): - ext = dapr._get_http_extension('') - - # Test POST verb with querystring - qs = ( - ('query1', 'string1'), - ('query2', 'string2'), - ('query1', 'string 3'), - ) - ext = dapr._get_http_extension('POST', qs) - - self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) - self.assertEqual('query1=string1&query2=string2&query1=string+3', ext.querystring) - - def test_invoke_method_bytes_data(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_method( - app_id='targetId', - method_name='bytes', - data=b'haha', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - http_verb='PUT', - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - def test_invoke_method_no_data(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_method( - app_id='targetId', - method_name='bytes', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - http_verb='PUT', - ) - - self.assertEqual(b'', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - def test_invoke_method_async(self): - dapr = DaprClient(f'{self.scheme}localhost:{self.grpc_port}') - dapr.invocation_client = None # force to use grpc client - - with self.assertRaises(NotImplementedError): - loop = asyncio.new_event_loop() - loop.run_until_complete( - dapr.invoke_method_async( - app_id='targetId', - method_name='bytes', - data=b'haha', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - http_verb='PUT', - ) - ) - - def test_invoke_method_proto_data(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - req = common_v1.StateItem(key='test') - resp = dapr.invoke_method( - app_id='targetId', - method_name='proto', - data=req, - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - self.assertTrue(resp.is_proto()) - - # unpack to new protobuf object - new_resp = common_v1.StateItem() - resp.unpack(new_resp) - self.assertEqual('test', new_resp.key) - - def test_invoke_binding_bytes_data(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_binding( - binding_name='binding', - operation='create', - data=b'haha', - binding_metadata={ - 'key1': 'value1', - 'key2': 'value2', - }, - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual({'key1': 'value1', 'key2': 'value2'}, resp.binding_metadata) - self.assertEqual(2, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - def test_invoke_binding_no_metadata(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_binding( - binding_name='binding', - operation='create', - data=b'haha', - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(0, len(resp.headers)) - - def test_invoke_binding_no_data(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_binding( - binding_name='binding', - operation='create', - ) - - self.assertEqual(b'', resp.data) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(0, len(resp.headers)) - - def test_invoke_binding_no_create(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_binding( - binding_name='binding', - operation='delete', - data=b'haha', - ) - - self.assertEqual(b'INVALID', resp.data) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(0, len(resp.headers)) - - def test_publish_event(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.publish_event(pubsub_name='pubsub', topic_name='example', data=b'test_data') - - self.assertEqual(2, len(resp.headers)) - self.assertEqual(['test_data'], resp.headers['hdata']) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - dapr.publish_event(pubsub_name='pubsub', topic_name='example', data=b'test_data') - - def test_publish_event_with_content_type(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.publish_event( - pubsub_name='pubsub', - topic_name='example', - data=b'{"foo": "bar"}', - data_content_type='application/json', - ) - - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) - self.assertEqual(['application/json'], resp.headers['data_content_type']) - - def test_publish_event_with_metadata(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.publish_event( - pubsub_name='pubsub', - topic_name='example', - data=b'{"foo": "bar"}', - publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, - ) - - print(resp.headers) - self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) - self.assertEqual(['false'], resp.headers['metadata_raw_payload']) - self.assertEqual(['100'], resp.headers['metadata_ttl_in_seconds']) - - def test_publish_error(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaisesRegex(ValueError, "invalid type for data "): - dapr.publish_event( - pubsub_name='pubsub', - topic_name='example', - data=111, - ) - - def test_subscribe_topic(self): - # The fake server we're using sends two messages and then closes the stream - # The client should be able to read both messages, handle the stream closure and reconnect - # which will result in reading the same two messages again. - # That's why message 3 should be the same as message 1 - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - subscription = dapr.subscribe(pubsub_name='pubsub', topic='example') - - # First message - text - message1 = subscription.next_message() - subscription.respond_success(message1) - - self.assertEqual('111', message1.id()) - self.assertEqual('app1', message1.source()) - self.assertEqual('com.example.type2', message1.type()) - self.assertEqual('1.0', message1.spec_version()) - self.assertEqual('text/plain', message1.data_content_type()) - self.assertEqual('TOPIC_A', message1.topic()) - self.assertEqual('pubsub', message1.pubsub_name()) - self.assertEqual(b'hello2', message1.raw_data()) - self.assertEqual('text/plain', message1.data_content_type()) - self.assertEqual('hello2', message1.data()) - - # Second message - json - message2 = subscription.next_message() - subscription.respond_success(message2) - - self.assertEqual('222', message2.id()) - self.assertEqual('app1', message2.source()) - self.assertEqual('com.example.type2', message2.type()) - self.assertEqual('1.0', message2.spec_version()) - self.assertEqual('TOPIC_A', message2.topic()) - self.assertEqual('pubsub', message2.pubsub_name()) - self.assertEqual(b'{"a": 1}', message2.raw_data()) - self.assertEqual('application/json', message2.data_content_type()) - self.assertEqual({'a': 1}, message2.data()) - - # On this call the stream will be closed and return an error, so the message will be none - # but the client will try to reconnect - message3 = subscription.next_message() - self.assertIsNone(message3) - - # The client already reconnected and will start reading the messages again - # Since we're working with a fake server, the messages will be the same - message4 = subscription.next_message() - subscription.respond_success(message4) - self.assertEqual('111', message4.id()) - self.assertEqual('app1', message4.source()) - self.assertEqual('com.example.type2', message4.type()) - self.assertEqual('1.0', message4.spec_version()) - self.assertEqual('text/plain', message4.data_content_type()) - self.assertEqual('TOPIC_A', message4.topic()) - self.assertEqual('pubsub', message4.pubsub_name()) - self.assertEqual(b'hello2', message4.raw_data()) - self.assertEqual('text/plain', message4.data_content_type()) - self.assertEqual('hello2', message4.data()) - - subscription.close() - - def test_subscribe_topic_early_close(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - subscription = dapr.subscribe(pubsub_name='pubsub', topic='example') - subscription.close() - - with self.assertRaises(StreamInactiveError): - subscription.next_message() - - def test_subscribe_topic_with_handler(self): - # The fake server we're using sends two messages and then closes the stream - # The client should be able to read both messages, handle the stream closure and reconnect - # which will result in reading the same two messages again. - # That's why message 3 should be the same as message 1 - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - counter = 0 - - def handler(message): - nonlocal counter - if counter == 0: - self.assertEqual('111', message.id()) - self.assertEqual('app1', message.source()) - self.assertEqual('com.example.type2', message.type()) - self.assertEqual('1.0', message.spec_version()) - self.assertEqual('text/plain', message.data_content_type()) - self.assertEqual('TOPIC_A', message.topic()) - self.assertEqual('pubsub', message.pubsub_name()) - self.assertEqual(b'hello2', message.raw_data()) - self.assertEqual('text/plain', message.data_content_type()) - self.assertEqual('hello2', message.data()) - elif counter == 1: - self.assertEqual('222', message.id()) - self.assertEqual('app1', message.source()) - self.assertEqual('com.example.type2', message.type()) - self.assertEqual('1.0', message.spec_version()) - self.assertEqual('TOPIC_A', message.topic()) - self.assertEqual('pubsub', message.pubsub_name()) - self.assertEqual(b'{"a": 1}', message.raw_data()) - self.assertEqual('application/json', message.data_content_type()) - self.assertEqual({'a': 1}, message.data()) - elif counter == 2: - self.assertEqual('111', message.id()) - self.assertEqual('app1', message.source()) - self.assertEqual('com.example.type2', message.type()) - self.assertEqual('1.0', message.spec_version()) - self.assertEqual('text/plain', message.data_content_type()) - self.assertEqual('TOPIC_A', message.topic()) - self.assertEqual('pubsub', message.pubsub_name()) - self.assertEqual(b'hello2', message.raw_data()) - self.assertEqual('text/plain', message.data_content_type()) - self.assertEqual('hello2', message.data()) - - counter += 1 - - return TopicEventResponse('success') - - close_fn = dapr.subscribe_with_handler( - pubsub_name='pubsub', topic='example', handler_fn=handler - ) - - while counter < 3: - time.sleep(0.1) # Use sleep to prevent a busy-wait loop - close_fn() - - @patch.object(settings, 'DAPR_API_TOKEN', 'test-token') - def test_dapr_api_token_insertion(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.invoke_method( - app_id='targetId', - method_name='bytes', - data=b'haha', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(4, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - self.assertEqual(['test-token'], resp.headers['hdapr-api-token']) - - def test_get_save_delete_state(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - key = 'key_1' - value = 'value_1' - options = StateOptions( - consistency=Consistency.eventual, - concurrency=Concurrency.first_write, - ) - dapr.save_state( - store_name='statestore', - key=key, - value=value, - etag='fake_etag', - options=options, - state_metadata={'capitalize': '1'}, - ) - - resp = dapr.get_state(store_name='statestore', key=key) - self.assertEqual(resp.data, to_bytes(value.capitalize())) - self.assertEqual(resp.etag, 'fake_etag') - - resp = dapr.get_state(store_name='statestore', key=key, state_metadata={'upper': '1'}) - self.assertEqual(resp.data, to_bytes(value.upper())) - self.assertEqual(resp.etag, 'fake_etag') - - resp = dapr.get_state(store_name='statestore', key='NotValidKey') - self.assertEqual(resp.data, b'') - self.assertEqual(resp.etag, '') - - # Check a DaprGrpcError is raised - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError) as context: - dapr.get_state(store_name='my_statestore', key='key||') - - dapr.delete_state(store_name='statestore', key=key) - resp = dapr.get_state(store_name='statestore', key=key) - self.assertEqual(resp.data, b'') - self.assertEqual(resp.etag, '') - - with self.assertRaises(DaprGrpcError) as context: - dapr.delete_state(store_name='statestore', key=key, state_metadata={'must_delete': '1'}) - print(context.exception) - self.assertTrue('delete failed' in str(context.exception)) - - def test_get_save_state_etag_none(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - - value = 'test' - no_etag_key = 'no_etag' - empty_etag_key = 'empty_etag' - dapr.save_state( - store_name='statestore', - key=no_etag_key, - value=value, - ) - - dapr.save_state(store_name='statestore', key=empty_etag_key, value=value, etag='') - - resp = dapr.get_state(store_name='statestore', key=no_etag_key) - self.assertEqual(resp.data, to_bytes(value)) - self.assertEqual(resp.etag, 'ETAG_WAS_NONE') - - resp = dapr.get_state(store_name='statestore', key=empty_etag_key) - self.assertEqual(resp.data, to_bytes(value)) - self.assertEqual(resp.etag, '') - - def test_transaction_then_get_states(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - - key = str(uuid.uuid4()) - value = str(uuid.uuid4()) - another_key = str(uuid.uuid4()) - another_value = str(uuid.uuid4()) - - dapr.execute_state_transaction( - store_name='statestore', - operations=[ - TransactionalStateOperation(key=key, data=value, etag='foo'), - TransactionalStateOperation(key=another_key, data=another_value), - ], - transactional_metadata={'metakey': 'metavalue'}, - ) - - resp = dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].data, to_bytes(value)) - self.assertEqual(resp.items[0].etag, 'foo') - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].data, to_bytes(another_value)) - self.assertEqual(resp.items[1].etag, 'ETAG_WAS_NONE') - - resp = dapr.get_bulk_state( - store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} - ) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].data, to_bytes(value.upper())) - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - dapr.execute_state_transaction( - store_name='statestore', - operations=[ - TransactionalStateOperation(key=key, data=value, etag='foo'), - TransactionalStateOperation(key=another_key, data=another_value), - ], - transactional_metadata={'metakey': 'metavalue'}, - ) - - def test_bulk_save_then_get_states(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - - key = str(uuid.uuid4()) - value = str(uuid.uuid4()) - another_key = str(uuid.uuid4()) - another_value = str(uuid.uuid4()) - - dapr.save_bulk_state( - store_name='statestore', - states=[ - StateItem(key=key, value=value, metadata={'capitalize': '1'}), - StateItem(key=another_key, value=another_value, etag='1'), - ], - metadata=(('metakey', 'metavalue'),), - ) - - resp = dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') - self.assertEqual(resp.items[0].data, to_bytes(value.capitalize())) - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].data, to_bytes(another_value)) - self.assertEqual(resp.items[1].etag, '1') - - resp = dapr.get_bulk_state( - store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} - ) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') - self.assertEqual(resp.items[0].data, to_bytes(value.upper())) - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].etag, '1') - self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - dapr.save_bulk_state( - store_name='statestore', - states=[ - StateItem(key=key, value=value, metadata={'capitalize': '1'}), - StateItem(key=another_key, value=another_value, etag='1'), - ], - metadata=(('metakey', 'metavalue'),), - ) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - dapr.get_bulk_state( - store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} - ) - - def test_get_secret(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - key1 = 'key_1' - resp = dapr.get_secret( - store_name='store_1', - key=key1, - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(1, len(resp.headers)) - self.assertEqual([key1], resp.headers['keyh']) - self.assertEqual({key1: 'val'}, resp._secret) - - def test_get_secret_metadata_absent(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - key1 = 'key_1' - resp = dapr.get_secret( - store_name='store_1', - key=key1, - ) - - self.assertEqual(1, len(resp.headers)) - self.assertEqual([key1], resp.headers['keyh']) - self.assertEqual({key1: 'val'}, resp._secret) - - def test_get_bulk_secret(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.get_bulk_secret( - store_name='store_1', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(1, len(resp.headers)) - self.assertEqual(['bulk'], resp.headers['keyh']) - self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) - - def test_get_bulk_secret_metadata_absent(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - resp = dapr.get_bulk_secret(store_name='store_1') - - self.assertEqual(1, len(resp.headers)) - self.assertEqual(['bulk'], resp.headers['keyh']) - self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) - - def test_get_configuration(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - keys = ['k', 'k1'] - value = 'value' - version = '1.5.0' - metadata = {} - - resp = dapr.get_configuration(store_name='configurationstore', keys=keys) - self.assertEqual(len(resp.items), len(keys)) - self.assertIn(keys[0], resp.items) - item = resp.items[keys[0]] - self.assertEqual(item.value, value) - self.assertEqual(item.version, version) - self.assertEqual(item.metadata, metadata) - - resp = dapr.get_configuration( - store_name='configurationstore', keys=keys, config_metadata=metadata - ) - self.assertEqual(len(resp.items), len(keys)) - self.assertIn(keys[0], resp.items) - item = resp.items[keys[0]] - self.assertEqual(item.value, value) - self.assertEqual(item.version, version) - self.assertEqual(item.metadata, metadata) - - def test_subscribe_configuration(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - - def mock_watch(self, stub, store_name, keys, handler, config_metadata): - handler( - 'id', - ConfigurationResponse( - items={'k': ConfigurationItem(value='test', version='1.7.0')} - ), - ) - return 'id' - - def handler(id: str, resp: ConfigurationResponse): - self.assertEqual(resp.items['k'].value, 'test') - self.assertEqual(resp.items['k'].version, '1.7.0') - - with patch.object(ConfigurationWatcher, 'watch_configuration', mock_watch): - dapr.subscribe_configuration( - store_name='configurationstore', keys=['k'], handler=handler - ) - - def test_unsubscribe_configuration(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - res = dapr.unsubscribe_configuration(store_name='configurationstore', id='k') - self.assertTrue(res) - - def test_query_state(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - - resp = dapr.query_state( - store_name='statestore', - query=json.dumps({'filter': {}, 'page': {'limit': 2}}), - ) - self.assertEqual(resp.results[0].key, '1') - self.assertEqual(len(resp.results), 2) - - resp = dapr.query_state( - store_name='statestore', - query=json.dumps({'filter': {}, 'page': {'limit': 3, 'token': '3'}}), - ) - self.assertEqual(resp.results[0].key, '3') - self.assertEqual(len(resp.results), 3) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - dapr.query_state( - store_name='statestore', - query=json.dumps({'filter': {}, 'page': {'limit': 3, 'token': '3'}}), - ) - - def test_shutdown(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - dapr.shutdown() - self.assertTrue(self._fake_dapr_server.shutdown_received) - - def test_wait_ok(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - dapr.wait(0.1) - - def test_wait_timeout(self): - # First, pick an unused port - port = 0 - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', 0)) - port = s.getsockname()[1] - dapr = DaprGrpcClient(f'localhost:{port}') - with self.assertRaises(Exception) as context: - dapr.wait(0.1) - self.assertTrue('Connection refused' in str(context.exception)) - - def test_lock_acquire_success(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - lock_owner = str(uuid.uuid4()) - expiry_in_seconds = 60 - - success = dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) - self.assertTrue(success) - unlock_response = dapr.unlock(store_name, resource_id, lock_owner) - self.assertEqual(UnlockResponseStatus.success, unlock_response.status) - - def test_lock_release_twice_fails(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - lock_owner = str(uuid.uuid4()) - expiry_in_seconds = 60 - - success = dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) - self.assertTrue(success) - unlock_response = dapr.unlock(store_name, resource_id, lock_owner) - self.assertEqual(UnlockResponseStatus.success, unlock_response.status) - # If client tries again it will discover the lock is gone - unlock_response = dapr.unlock(store_name, resource_id, lock_owner) - self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) - - def test_lock_conflict(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - first_client_id = str(uuid.uuid4()) - second_client_id = str(uuid.uuid4()) - expiry_in_seconds = 60 - - # First client succeeds - success = dapr.try_lock(store_name, resource_id, first_client_id, expiry_in_seconds) - self.assertTrue(success) - # Second client tries and fails - resource already acquired - success = dapr.try_lock(store_name, resource_id, second_client_id, expiry_in_seconds) - self.assertFalse(success) - # Second client is a sneaky fellow and tries to release a lock it doesn't own - unlock_response = dapr.unlock(store_name, resource_id, second_client_id) - self.assertEqual(UnlockResponseStatus.lock_belongs_to_others, unlock_response.status) - # First client can stil return the lock as rightful owner - unlock_response = dapr.unlock(store_name, resource_id, first_client_id) - self.assertEqual(UnlockResponseStatus.success, unlock_response.status) - - def test_lock_not_previously_acquired(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - unlock_response = dapr.unlock( - store_name='lockstore', resource_id=str(uuid.uuid4()), lock_owner=str(uuid.uuid4()) - ) - self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) - - def test_lock_release_twice_fails_with_context_manager(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - first_client_id = str(uuid.uuid4()) - second_client_id = str(uuid.uuid4()) - expiry = 60 - - with dapr.try_lock(store_name, resource_id, first_client_id, expiry) as first_lock: - self.assertTrue(first_lock.success) - # If another client tries to acquire the same lock it will fail - with dapr.try_lock(store_name, resource_id, second_client_id, expiry) as second_lock: - self.assertFalse(second_lock.success) - # At this point lock was auto-released - # If client tries again it will discover the lock is gone - unlock_response = dapr.unlock(store_name, resource_id, first_client_id) - self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) - - def test_lock_are_not_reentrant(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - client_id = str(uuid.uuid4()) - expiry_in_s = 60 - - with dapr.try_lock(store_name, resource_id, client_id, expiry_in_s) as first_attempt: - self.assertTrue(first_attempt.success) - # If the same client tries to acquire the same lock again it will fail. - with dapr.try_lock(store_name, resource_id, client_id, expiry_in_s) as second_attempt: - self.assertFalse(second_attempt.success) - - def test_lock_input_validation(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Sane parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - client_id = str(uuid.uuid4()) - expiry_in_s = 60 - # Invalid inputs for string arguments - for invalid_input in [None, '', ' ']: - # store_name - with self.assertRaises(ValueError): - with dapr.try_lock(invalid_input, resource_id, client_id, expiry_in_s) as res: - self.assertTrue(res.success) - # resource_id - with self.assertRaises(ValueError): - with dapr.try_lock(store_name, invalid_input, client_id, expiry_in_s) as res: - self.assertTrue(res.success) - # client_id - with self.assertRaises(ValueError): - with dapr.try_lock(store_name, resource_id, invalid_input, expiry_in_s) as res: - self.assertTrue(res.success) - # Invalid inputs for expiry_in_s - for invalid_input in [None, -1, 0]: - with self.assertRaises(ValueError): - with dapr.try_lock(store_name, resource_id, client_id, invalid_input) as res: - self.assertTrue(res.success) - - def test_unlock_input_validation(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Sane parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - client_id = str(uuid.uuid4()) - # Invalid inputs for string arguments - for invalid_input in [None, '', ' ']: - # store_name - with self.assertRaises(ValueError): - dapr.unlock(invalid_input, resource_id, client_id) - # resource_id - with self.assertRaises(ValueError): - dapr.unlock(store_name, invalid_input, client_id) - # client_id - with self.assertRaises(ValueError): - dapr.unlock(store_name, resource_id, invalid_input) - - # - # Tests for workflow - # - - def test_workflow(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - # Sane parameters - workflow_name = 'test_workflow' - event_name = 'eventName' - instance_id = str(uuid.uuid4()) - workflow_component = 'dapr' - input = 'paperclips' - event_data = 'cars' - - # Start the workflow - start_response = dapr.start_workflow( - instance_id=instance_id, - workflow_name=workflow_name, - workflow_component=workflow_component, - input=input, - workflow_options=None, - ) - self.assertEqual(instance_id, start_response.instance_id) - - # Get info on the workflow to check that it is running - get_response = dapr.get_workflow( - instance_id=instance_id, workflow_component=workflow_component - ) - self.assertEqual(WorkflowRuntimeStatus.RUNNING.value, get_response.runtime_status) - - # Pause the workflow - dapr.pause_workflow(instance_id, workflow_component) - - # Get info on the workflow to check that it is paused - get_response = dapr.get_workflow(instance_id, workflow_component) - self.assertEqual(WorkflowRuntimeStatus.SUSPENDED.value, get_response.runtime_status) - - # Resume the workflow - dapr.resume_workflow(instance_id, workflow_component) - - # Get info on the workflow to check that it is resumed - get_response = dapr.get_workflow(instance_id, workflow_component) - self.assertEqual(WorkflowRuntimeStatus.RUNNING.value, get_response.runtime_status) - - # Raise an event on the workflow. - dapr.raise_workflow_event(instance_id, workflow_component, event_name, event_data) - get_response = dapr.get_workflow(instance_id, workflow_component) - self.assertEqual(event_data, get_response.properties[instance_id].strip('""')) - - # Terminate the workflow - dapr.terminate_workflow(instance_id, workflow_component) - - # Get info on the workflow to check that it is terminated - get_response = dapr.get_workflow(instance_id, workflow_component) - self.assertEqual(WorkflowRuntimeStatus.TERMINATED.value, get_response.runtime_status) - - # Purge the workflow - dapr.purge_workflow(instance_id, workflow_component) - - # Get information on the workflow to ensure that it has been purged - try: - get_response = dapr.get_workflow(instance_id, workflow_component) - except Exception as err: - self.assertIn('Workflow instance does not exist', str(err)) - - # - # Tests for Metadata API - # - - def test_get_metadata(self): - with DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') as dapr: - response = dapr.get_metadata() - - self.assertIsNotNone(response) - - self.assertEqual(response.application_id, 'myapp') - - actors = response.active_actors_count - self.assertIsNotNone(actors) - self.assertTrue(len(actors) > 0) - for actorType, count in actors.items(): - # Assert both are non-null and non-empty/zero - self.assertTrue(actorType) - self.assertTrue(count) - - self.assertIsNotNone(response.registered_components) - self.assertTrue(len(response.registered_components) > 0) - components = {c.name: c for c in response.registered_components} - # common tests for all components - for c in components.values(): - self.assertTrue(c.name) - self.assertTrue(c.type) - self.assertIsNotNone(c.version) - self.assertIsNotNone(c.capabilities) - self.assertTrue('ETAG' in components['statestore'].capabilities) - - self.assertIsNotNone(response.extended_metadata) - - def test_set_metadata(self): - metadata_key = 'test_set_metadata_attempt' - with DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') as dapr: - for metadata_value in [str(i) for i in range(10)]: - dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) - response = dapr.get_metadata() - self.assertIsNotNone(response) - self.assertIsNotNone(response.extended_metadata) - self.assertEqual(response.extended_metadata[metadata_key], metadata_value) - # Empty string and blank strings should be accepted just fine - # by this API - for metadata_value in ['', ' ']: - dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) - response = dapr.get_metadata() - self.assertIsNotNone(response) - self.assertIsNotNone(response.extended_metadata) - self.assertEqual(response.extended_metadata[metadata_key], metadata_value) - - def test_set_metadata_input_validation(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - valid_attr_name = 'attribute name' - valid_attr_value = 'attribute value' - # Invalid inputs for string arguments - with DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') as dapr: - for invalid_attr_name in [None, '', ' ']: - with self.assertRaises(ValueError): - dapr.set_metadata(invalid_attr_name, valid_attr_value) - # We are less strict with attribute values - we just cannot accept None - for invalid_attr_value in [None]: - with self.assertRaises(ValueError): - dapr.set_metadata(valid_attr_name, invalid_attr_value) - - # - # Tests for Cryptography API - # - - def test_encrypt_empty_component_name(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = EncryptOptions( - component_name='', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertIn('component_name', str(err)) - - def test_encrypt_empty_key_name(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = EncryptOptions( - component_name='crypto_component', - key_name='', - key_wrap_algorithm='RSA', - ) - dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertIn('key_name', str(err)) - - def test_encrypt_empty_key_wrap_algorithm(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='', - ) - dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertIn('key_wrap_algorithm', str(err)) - - def test_encrypt_string_data_read_all(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertEqual(resp.read(), b'HELLO DAPR') - - def test_encrypt_string_data_read_chunks(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertEqual(resp.read(5), b'HELLO') - self.assertEqual(resp.read(5), b' DAPR') - - def test_encrypt_file_data_read_all(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'hello dapr') - temp_file.seek(0) - - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = dapr.encrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(resp.read(), b'HELLO DAPR') - - def test_encrypt_file_data_read_chunks(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'hello dapr') - temp_file.seek(0) - - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = dapr.encrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(resp.read(5), b'HELLO') - self.assertEqual(resp.read(5), b' DAPR') - - def test_decrypt_empty_component_name(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = DecryptOptions( - component_name='', - ) - dapr.decrypt( - data='HELLO DAPR', - options=options, - ) - self.assertIn('component_name', str(err)) - - def test_decrypt_string_data_read_all(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - options = DecryptOptions( - component_name='crypto_component', - ) - resp = dapr.decrypt( - data='HELLO DAPR', - options=options, - ) - self.assertEqual(resp.read(), b'hello dapr') - - def test_decrypt_string_data_read_chunks(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - options = DecryptOptions( - component_name='crypto_component', - ) - resp = dapr.decrypt( - data='HELLO DAPR', - options=options, - ) - self.assertEqual(resp.read(5), b'hello') - self.assertEqual(resp.read(5), b' dapr') - - def test_decrypt_file_data_read_all(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'HELLO DAPR') - temp_file.seek(0) - - options = DecryptOptions( - component_name='crypto_component', - ) - resp = dapr.decrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(resp.read(), b'hello dapr') - - def test_decrypt_file_data_read_chunks(self): - dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'HELLO DAPR') - temp_file.seek(0) - - options = DecryptOptions( - component_name='crypto_component', - ) - resp = dapr.decrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(resp.read(5), b'hello') - self.assertEqual(resp.read(5), b' dapr') - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import json +import socket +import tempfile +import time +import unittest +import uuid +import asyncio + +from unittest.mock import patch + +from google.rpc import status_pb2, code_pb2 + +from dapr.clients.exceptions import DaprGrpcError +from dapr.clients.grpc.client import DaprGrpcClient +from dapr.clients import DaprClient +from dapr.clients.grpc.subscription import StreamInactiveError +from dapr.proto import common_v1 +from .fake_dapr_server import FakeDaprSidecar +from dapr.conf import settings +from dapr.clients.grpc._helpers import to_bytes +from dapr.clients.grpc._request import TransactionalStateOperation +from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.clients.grpc._response import ( + ConfigurationItem, + ConfigurationResponse, + ConfigurationWatcher, + UnlockResponseStatus, + WorkflowRuntimeStatus, + TopicEventResponse, +) + + +class DaprGrpcClientTests(unittest.TestCase): + grpc_port = 50001 + http_port = 3500 + scheme = '' + error = None + + @classmethod + def setUpClass(cls): + cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) + cls._fake_dapr_server.start() + settings.DAPR_HTTP_PORT = cls.http_port + settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(cls.http_port) + + @classmethod + def tearDownClass(cls): + cls._fake_dapr_server.stop() + + def test_http_extension(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + # Test POST verb without querystring + ext = dapr._get_http_extension('POST') + self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) + + # Test Non-supported http verb + with self.assertRaises(ValueError): + ext = dapr._get_http_extension('') + + # Test POST verb with querystring + qs = ( + ('query1', 'string1'), + ('query2', 'string2'), + ('query1', 'string 3'), + ) + ext = dapr._get_http_extension('POST', qs) + + self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) + self.assertEqual('query1=string1&query2=string2&query1=string+3', ext.querystring) + + def test_invoke_method_bytes_data(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_method( + app_id='targetId', + method_name='bytes', + data=b'haha', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + http_verb='PUT', + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + def test_invoke_method_no_data(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_method( + app_id='targetId', + method_name='bytes', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + http_verb='PUT', + ) + + self.assertEqual(b'', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + def test_invoke_method_async(self): + dapr = DaprClient(f'{self.scheme}localhost:{self.grpc_port}') + dapr.invocation_client = None # force to use grpc client + + with self.assertRaises(NotImplementedError): + loop = asyncio.new_event_loop() + loop.run_until_complete( + dapr.invoke_method_async( + app_id='targetId', + method_name='bytes', + data=b'haha', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + http_verb='PUT', + ) + ) + + def test_invoke_method_proto_data(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + req = common_v1.StateItem(key='test') + resp = dapr.invoke_method( + app_id='targetId', + method_name='proto', + data=req, + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + self.assertTrue(resp.is_proto()) + + # unpack to new protobuf object + new_resp = common_v1.StateItem() + resp.unpack(new_resp) + self.assertEqual('test', new_resp.key) + + def test_invoke_binding_bytes_data(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_binding( + binding_name='binding', + operation='create', + data=b'haha', + binding_metadata={ + 'key1': 'value1', + 'key2': 'value2', + }, + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual({'key1': 'value1', 'key2': 'value2'}, resp.binding_metadata) + self.assertEqual(2, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + def test_invoke_binding_no_metadata(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_binding( + binding_name='binding', + operation='create', + data=b'haha', + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(0, len(resp.headers)) + + def test_invoke_binding_no_data(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_binding( + binding_name='binding', + operation='create', + ) + + self.assertEqual(b'', resp.data) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(0, len(resp.headers)) + + def test_invoke_binding_no_create(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_binding( + binding_name='binding', + operation='delete', + data=b'haha', + ) + + self.assertEqual(b'INVALID', resp.data) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(0, len(resp.headers)) + + def test_publish_event(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.publish_event(pubsub_name='pubsub', topic_name='example', data=b'test_data') + + self.assertEqual(2, len(resp.headers)) + self.assertEqual(['test_data'], resp.headers['hdata']) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + dapr.publish_event(pubsub_name='pubsub', topic_name='example', data=b'test_data') + + def test_publish_event_with_content_type(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.publish_event( + pubsub_name='pubsub', + topic_name='example', + data=b'{"foo": "bar"}', + data_content_type='application/json', + ) + + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) + self.assertEqual(['application/json'], resp.headers['data_content_type']) + + def test_publish_event_with_metadata(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.publish_event( + pubsub_name='pubsub', + topic_name='example', + data=b'{"foo": "bar"}', + publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, + ) + + print(resp.headers) + self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) + self.assertEqual(['false'], resp.headers['metadata_raw_payload']) + self.assertEqual(['100'], resp.headers['metadata_ttl_in_seconds']) + + def test_publish_error(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaisesRegex(ValueError, "invalid type for data "): + dapr.publish_event( + pubsub_name='pubsub', + topic_name='example', + data=111, + ) + + def test_subscribe_topic(self): + # The fake server we're using sends two messages and then closes the stream + # The client should be able to read both messages, handle the stream closure and reconnect + # which will result in reading the same two messages again. + # That's why message 3 should be the same as message 1 + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + subscription = dapr.subscribe(pubsub_name='pubsub', topic='example') + + # First message - text + message1 = subscription.next_message() + subscription.respond_success(message1) + + self.assertEqual('111', message1.id()) + self.assertEqual('app1', message1.source()) + self.assertEqual('com.example.type2', message1.type()) + self.assertEqual('1.0', message1.spec_version()) + self.assertEqual('text/plain', message1.data_content_type()) + self.assertEqual('TOPIC_A', message1.topic()) + self.assertEqual('pubsub', message1.pubsub_name()) + self.assertEqual(b'hello2', message1.raw_data()) + self.assertEqual('text/plain', message1.data_content_type()) + self.assertEqual('hello2', message1.data()) + + # Second message - json + message2 = subscription.next_message() + subscription.respond_success(message2) + + self.assertEqual('222', message2.id()) + self.assertEqual('app1', message2.source()) + self.assertEqual('com.example.type2', message2.type()) + self.assertEqual('1.0', message2.spec_version()) + self.assertEqual('TOPIC_A', message2.topic()) + self.assertEqual('pubsub', message2.pubsub_name()) + self.assertEqual(b'{"a": 1}', message2.raw_data()) + self.assertEqual('application/json', message2.data_content_type()) + self.assertEqual({'a': 1}, message2.data()) + + # On this call the stream will be closed and return an error, so the message will be none + # but the client will try to reconnect + message3 = subscription.next_message() + self.assertIsNone(message3) + + # The client already reconnected and will start reading the messages again + # Since we're working with a fake server, the messages will be the same + message4 = subscription.next_message() + subscription.respond_success(message4) + self.assertEqual('111', message4.id()) + self.assertEqual('app1', message4.source()) + self.assertEqual('com.example.type2', message4.type()) + self.assertEqual('1.0', message4.spec_version()) + self.assertEqual('text/plain', message4.data_content_type()) + self.assertEqual('TOPIC_A', message4.topic()) + self.assertEqual('pubsub', message4.pubsub_name()) + self.assertEqual(b'hello2', message4.raw_data()) + self.assertEqual('text/plain', message4.data_content_type()) + self.assertEqual('hello2', message4.data()) + + subscription.close() + + def test_subscribe_topic_early_close(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + subscription = dapr.subscribe(pubsub_name='pubsub', topic='example') + subscription.close() + + with self.assertRaises(StreamInactiveError): + subscription.next_message() + + def test_subscribe_topic_with_handler(self): + # The fake server we're using sends two messages and then closes the stream + # The client should be able to read both messages, handle the stream closure and reconnect + # which will result in reading the same two messages again. + # That's why message 3 should be the same as message 1 + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + counter = 0 + + def handler(message): + nonlocal counter + if counter == 0: + self.assertEqual('111', message.id()) + self.assertEqual('app1', message.source()) + self.assertEqual('com.example.type2', message.type()) + self.assertEqual('1.0', message.spec_version()) + self.assertEqual('text/plain', message.data_content_type()) + self.assertEqual('TOPIC_A', message.topic()) + self.assertEqual('pubsub', message.pubsub_name()) + self.assertEqual(b'hello2', message.raw_data()) + self.assertEqual('text/plain', message.data_content_type()) + self.assertEqual('hello2', message.data()) + elif counter == 1: + self.assertEqual('222', message.id()) + self.assertEqual('app1', message.source()) + self.assertEqual('com.example.type2', message.type()) + self.assertEqual('1.0', message.spec_version()) + self.assertEqual('TOPIC_A', message.topic()) + self.assertEqual('pubsub', message.pubsub_name()) + self.assertEqual(b'{"a": 1}', message.raw_data()) + self.assertEqual('application/json', message.data_content_type()) + self.assertEqual({'a': 1}, message.data()) + elif counter == 2: + self.assertEqual('111', message.id()) + self.assertEqual('app1', message.source()) + self.assertEqual('com.example.type2', message.type()) + self.assertEqual('1.0', message.spec_version()) + self.assertEqual('text/plain', message.data_content_type()) + self.assertEqual('TOPIC_A', message.topic()) + self.assertEqual('pubsub', message.pubsub_name()) + self.assertEqual(b'hello2', message.raw_data()) + self.assertEqual('text/plain', message.data_content_type()) + self.assertEqual('hello2', message.data()) + + counter += 1 + + return TopicEventResponse('success') + + close_fn = dapr.subscribe_with_handler( + pubsub_name='pubsub', topic='example', handler_fn=handler + ) + + while counter < 3: + time.sleep(0.1) # Use sleep to prevent a busy-wait loop + close_fn() + + @patch.object(settings, 'DAPR_API_TOKEN', 'test-token') + def test_dapr_api_token_insertion(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.invoke_method( + app_id='targetId', + method_name='bytes', + data=b'haha', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(4, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + self.assertEqual(['test-token'], resp.headers['hdapr-api-token']) + + def test_get_save_delete_state(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + key = 'key_1' + value = 'value_1' + options = StateOptions( + consistency=Consistency.eventual, + concurrency=Concurrency.first_write, + ) + dapr.save_state( + store_name='statestore', + key=key, + value=value, + etag='fake_etag', + options=options, + state_metadata={'capitalize': '1'}, + ) + + resp = dapr.get_state(store_name='statestore', key=key) + self.assertEqual(resp.data, to_bytes(value.capitalize())) + self.assertEqual(resp.etag, 'fake_etag') + + resp = dapr.get_state(store_name='statestore', key=key, state_metadata={'upper': '1'}) + self.assertEqual(resp.data, to_bytes(value.upper())) + self.assertEqual(resp.etag, 'fake_etag') + + resp = dapr.get_state(store_name='statestore', key='NotValidKey') + self.assertEqual(resp.data, b'') + self.assertEqual(resp.etag, '') + + # Check a DaprGrpcError is raised + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError) as context: + dapr.get_state(store_name='my_statestore', key='key||') + + dapr.delete_state(store_name='statestore', key=key) + resp = dapr.get_state(store_name='statestore', key=key) + self.assertEqual(resp.data, b'') + self.assertEqual(resp.etag, '') + + with self.assertRaises(DaprGrpcError) as context: + dapr.delete_state(store_name='statestore', key=key, state_metadata={'must_delete': '1'}) + print(context.exception) + self.assertTrue('delete failed' in str(context.exception)) + + def test_get_save_state_etag_none(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + value = 'test' + no_etag_key = 'no_etag' + empty_etag_key = 'empty_etag' + dapr.save_state( + store_name='statestore', + key=no_etag_key, + value=value, + ) + + dapr.save_state(store_name='statestore', key=empty_etag_key, value=value, etag='') + + resp = dapr.get_state(store_name='statestore', key=no_etag_key) + self.assertEqual(resp.data, to_bytes(value)) + self.assertEqual(resp.etag, 'ETAG_WAS_NONE') + + resp = dapr.get_state(store_name='statestore', key=empty_etag_key) + self.assertEqual(resp.data, to_bytes(value)) + self.assertEqual(resp.etag, '') + + def test_transaction_then_get_states(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + key = str(uuid.uuid4()) + value = str(uuid.uuid4()) + another_key = str(uuid.uuid4()) + another_value = str(uuid.uuid4()) + + dapr.execute_state_transaction( + store_name='statestore', + operations=[ + TransactionalStateOperation(key=key, data=value, etag='foo'), + TransactionalStateOperation(key=another_key, data=another_value), + ], + transactional_metadata={'metakey': 'metavalue'}, + ) + + resp = dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].data, to_bytes(value)) + self.assertEqual(resp.items[0].etag, 'foo') + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].data, to_bytes(another_value)) + self.assertEqual(resp.items[1].etag, 'ETAG_WAS_NONE') + + resp = dapr.get_bulk_state( + store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} + ) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].data, to_bytes(value.upper())) + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + dapr.execute_state_transaction( + store_name='statestore', + operations=[ + TransactionalStateOperation(key=key, data=value, etag='foo'), + TransactionalStateOperation(key=another_key, data=another_value), + ], + transactional_metadata={'metakey': 'metavalue'}, + ) + + def test_bulk_save_then_get_states(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + key = str(uuid.uuid4()) + value = str(uuid.uuid4()) + another_key = str(uuid.uuid4()) + another_value = str(uuid.uuid4()) + + dapr.save_bulk_state( + store_name='statestore', + states=[ + StateItem(key=key, value=value, metadata={'capitalize': '1'}), + StateItem(key=another_key, value=another_value, etag='1'), + ], + metadata=(('metakey', 'metavalue'),), + ) + + resp = dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') + self.assertEqual(resp.items[0].data, to_bytes(value.capitalize())) + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].data, to_bytes(another_value)) + self.assertEqual(resp.items[1].etag, '1') + + resp = dapr.get_bulk_state( + store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} + ) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') + self.assertEqual(resp.items[0].data, to_bytes(value.upper())) + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].etag, '1') + self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + dapr.save_bulk_state( + store_name='statestore', + states=[ + StateItem(key=key, value=value, metadata={'capitalize': '1'}), + StateItem(key=another_key, value=another_value, etag='1'), + ], + metadata=(('metakey', 'metavalue'),), + ) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + dapr.get_bulk_state( + store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} + ) + + def test_get_secret(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + key1 = 'key_1' + resp = dapr.get_secret( + store_name='store_1', + key=key1, + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(1, len(resp.headers)) + self.assertEqual([key1], resp.headers['keyh']) + self.assertEqual({key1: 'val'}, resp._secret) + + def test_get_secret_metadata_absent(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + key1 = 'key_1' + resp = dapr.get_secret( + store_name='store_1', + key=key1, + ) + + self.assertEqual(1, len(resp.headers)) + self.assertEqual([key1], resp.headers['keyh']) + self.assertEqual({key1: 'val'}, resp._secret) + + def test_get_bulk_secret(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.get_bulk_secret( + store_name='store_1', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(1, len(resp.headers)) + self.assertEqual(['bulk'], resp.headers['keyh']) + self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) + + def test_get_bulk_secret_metadata_absent(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + resp = dapr.get_bulk_secret(store_name='store_1') + + self.assertEqual(1, len(resp.headers)) + self.assertEqual(['bulk'], resp.headers['keyh']) + self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) + + def test_get_configuration(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + keys = ['k', 'k1'] + value = 'value' + version = '1.5.0' + metadata = {} + + resp = dapr.get_configuration(store_name='configurationstore', keys=keys) + self.assertEqual(len(resp.items), len(keys)) + self.assertIn(keys[0], resp.items) + item = resp.items[keys[0]] + self.assertEqual(item.value, value) + self.assertEqual(item.version, version) + self.assertEqual(item.metadata, metadata) + + resp = dapr.get_configuration( + store_name='configurationstore', keys=keys, config_metadata=metadata + ) + self.assertEqual(len(resp.items), len(keys)) + self.assertIn(keys[0], resp.items) + item = resp.items[keys[0]] + self.assertEqual(item.value, value) + self.assertEqual(item.version, version) + self.assertEqual(item.metadata, metadata) + + def test_subscribe_configuration(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + def mock_watch(self, stub, store_name, keys, handler, config_metadata): + handler( + 'id', + ConfigurationResponse( + items={'k': ConfigurationItem(value='test', version='1.7.0')} + ), + ) + return 'id' + + def handler(id: str, resp: ConfigurationResponse): + self.assertEqual(resp.items['k'].value, 'test') + self.assertEqual(resp.items['k'].version, '1.7.0') + + with patch.object(ConfigurationWatcher, 'watch_configuration', mock_watch): + dapr.subscribe_configuration( + store_name='configurationstore', keys=['k'], handler=handler + ) + + def test_unsubscribe_configuration(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + res = dapr.unsubscribe_configuration(store_name='configurationstore', id='k') + self.assertTrue(res) + + def test_query_state(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + resp = dapr.query_state( + store_name='statestore', + query=json.dumps({'filter': {}, 'page': {'limit': 2}}), + ) + self.assertEqual(resp.results[0].key, '1') + self.assertEqual(len(resp.results), 2) + + resp = dapr.query_state( + store_name='statestore', + query=json.dumps({'filter': {}, 'page': {'limit': 3, 'token': '3'}}), + ) + self.assertEqual(resp.results[0].key, '3') + self.assertEqual(len(resp.results), 3) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + dapr.query_state( + store_name='statestore', + query=json.dumps({'filter': {}, 'page': {'limit': 3, 'token': '3'}}), + ) + + def test_shutdown(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + dapr.shutdown() + self.assertTrue(self._fake_dapr_server.shutdown_received) + + def test_wait_ok(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + dapr.wait(0.1) + + def test_wait_timeout(self): + # First, pick an unused port + port = 0 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + port = s.getsockname()[1] + dapr = DaprGrpcClient(f'localhost:{port}') + with self.assertRaises(Exception) as context: + dapr.wait(0.1) + self.assertTrue('Connection refused' in str(context.exception)) + + def test_lock_acquire_success(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + lock_owner = str(uuid.uuid4()) + expiry_in_seconds = 60 + + success = dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) + self.assertTrue(success) + unlock_response = dapr.unlock(store_name, resource_id, lock_owner) + self.assertEqual(UnlockResponseStatus.success, unlock_response.status) + + def test_lock_release_twice_fails(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + lock_owner = str(uuid.uuid4()) + expiry_in_seconds = 60 + + success = dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) + self.assertTrue(success) + unlock_response = dapr.unlock(store_name, resource_id, lock_owner) + self.assertEqual(UnlockResponseStatus.success, unlock_response.status) + # If client tries again it will discover the lock is gone + unlock_response = dapr.unlock(store_name, resource_id, lock_owner) + self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) + + def test_lock_conflict(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + first_client_id = str(uuid.uuid4()) + second_client_id = str(uuid.uuid4()) + expiry_in_seconds = 60 + + # First client succeeds + success = dapr.try_lock(store_name, resource_id, first_client_id, expiry_in_seconds) + self.assertTrue(success) + # Second client tries and fails - resource already acquired + success = dapr.try_lock(store_name, resource_id, second_client_id, expiry_in_seconds) + self.assertFalse(success) + # Second client is a sneaky fellow and tries to release a lock it doesn't own + unlock_response = dapr.unlock(store_name, resource_id, second_client_id) + self.assertEqual(UnlockResponseStatus.lock_belongs_to_others, unlock_response.status) + # First client can stil return the lock as rightful owner + unlock_response = dapr.unlock(store_name, resource_id, first_client_id) + self.assertEqual(UnlockResponseStatus.success, unlock_response.status) + + def test_lock_not_previously_acquired(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + unlock_response = dapr.unlock( + store_name='lockstore', resource_id=str(uuid.uuid4()), lock_owner=str(uuid.uuid4()) + ) + self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) + + def test_lock_release_twice_fails_with_context_manager(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + first_client_id = str(uuid.uuid4()) + second_client_id = str(uuid.uuid4()) + expiry = 60 + + with dapr.try_lock(store_name, resource_id, first_client_id, expiry) as first_lock: + self.assertTrue(first_lock.success) + # If another client tries to acquire the same lock it will fail + with dapr.try_lock(store_name, resource_id, second_client_id, expiry) as second_lock: + self.assertFalse(second_lock.success) + # At this point lock was auto-released + # If client tries again it will discover the lock is gone + unlock_response = dapr.unlock(store_name, resource_id, first_client_id) + self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) + + def test_lock_are_not_reentrant(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + client_id = str(uuid.uuid4()) + expiry_in_s = 60 + + with dapr.try_lock(store_name, resource_id, client_id, expiry_in_s) as first_attempt: + self.assertTrue(first_attempt.success) + # If the same client tries to acquire the same lock again it will fail. + with dapr.try_lock(store_name, resource_id, client_id, expiry_in_s) as second_attempt: + self.assertFalse(second_attempt.success) + + def test_lock_input_validation(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Sane parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + client_id = str(uuid.uuid4()) + expiry_in_s = 60 + # Invalid inputs for string arguments + for invalid_input in [None, '', ' ']: + # store_name + with self.assertRaises(ValueError): + with dapr.try_lock(invalid_input, resource_id, client_id, expiry_in_s) as res: + self.assertTrue(res.success) + # resource_id + with self.assertRaises(ValueError): + with dapr.try_lock(store_name, invalid_input, client_id, expiry_in_s) as res: + self.assertTrue(res.success) + # client_id + with self.assertRaises(ValueError): + with dapr.try_lock(store_name, resource_id, invalid_input, expiry_in_s) as res: + self.assertTrue(res.success) + # Invalid inputs for expiry_in_s + for invalid_input in [None, -1, 0]: + with self.assertRaises(ValueError): + with dapr.try_lock(store_name, resource_id, client_id, invalid_input) as res: + self.assertTrue(res.success) + + def test_unlock_input_validation(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Sane parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + client_id = str(uuid.uuid4()) + # Invalid inputs for string arguments + for invalid_input in [None, '', ' ']: + # store_name + with self.assertRaises(ValueError): + dapr.unlock(invalid_input, resource_id, client_id) + # resource_id + with self.assertRaises(ValueError): + dapr.unlock(store_name, invalid_input, client_id) + # client_id + with self.assertRaises(ValueError): + dapr.unlock(store_name, resource_id, invalid_input) + + # + # Tests for workflow + # + + def test_workflow(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + # Sane parameters + workflow_name = 'test_workflow' + event_name = 'eventName' + instance_id = str(uuid.uuid4()) + workflow_component = 'dapr' + input = 'paperclips' + event_data = 'cars' + + # Start the workflow + start_response = dapr.start_workflow( + instance_id=instance_id, + workflow_name=workflow_name, + workflow_component=workflow_component, + input=input, + workflow_options=None, + ) + self.assertEqual(instance_id, start_response.instance_id) + + # Get info on the workflow to check that it is running + get_response = dapr.get_workflow( + instance_id=instance_id, workflow_component=workflow_component + ) + self.assertEqual(WorkflowRuntimeStatus.RUNNING.value, get_response.runtime_status) + + # Pause the workflow + dapr.pause_workflow(instance_id, workflow_component) + + # Get info on the workflow to check that it is paused + get_response = dapr.get_workflow(instance_id, workflow_component) + self.assertEqual(WorkflowRuntimeStatus.SUSPENDED.value, get_response.runtime_status) + + # Resume the workflow + dapr.resume_workflow(instance_id, workflow_component) + + # Get info on the workflow to check that it is resumed + get_response = dapr.get_workflow(instance_id, workflow_component) + self.assertEqual(WorkflowRuntimeStatus.RUNNING.value, get_response.runtime_status) + + # Raise an event on the workflow. + dapr.raise_workflow_event(instance_id, workflow_component, event_name, event_data) + get_response = dapr.get_workflow(instance_id, workflow_component) + self.assertEqual(event_data, get_response.properties[instance_id].strip('""')) + + # Terminate the workflow + dapr.terminate_workflow(instance_id, workflow_component) + + # Get info on the workflow to check that it is terminated + get_response = dapr.get_workflow(instance_id, workflow_component) + self.assertEqual(WorkflowRuntimeStatus.TERMINATED.value, get_response.runtime_status) + + # Purge the workflow + dapr.purge_workflow(instance_id, workflow_component) + + # Get information on the workflow to ensure that it has been purged + try: + get_response = dapr.get_workflow(instance_id, workflow_component) + except Exception as err: + self.assertIn('Workflow instance does not exist', str(err)) + + # + # Tests for Metadata API + # + + def test_get_metadata(self): + with DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') as dapr: + response = dapr.get_metadata() + + self.assertIsNotNone(response) + + self.assertEqual(response.application_id, 'myapp') + + actors = response.active_actors_count + self.assertIsNotNone(actors) + self.assertTrue(len(actors) > 0) + for actorType, count in actors.items(): + # Assert both are non-null and non-empty/zero + self.assertTrue(actorType) + self.assertTrue(count) + + self.assertIsNotNone(response.registered_components) + self.assertTrue(len(response.registered_components) > 0) + components = {c.name: c for c in response.registered_components} + # common tests for all components + for c in components.values(): + self.assertTrue(c.name) + self.assertTrue(c.type) + self.assertIsNotNone(c.version) + self.assertIsNotNone(c.capabilities) + self.assertTrue('ETAG' in components['statestore'].capabilities) + + self.assertIsNotNone(response.extended_metadata) + + def test_set_metadata(self): + metadata_key = 'test_set_metadata_attempt' + with DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') as dapr: + for metadata_value in [str(i) for i in range(10)]: + dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) + response = dapr.get_metadata() + self.assertIsNotNone(response) + self.assertIsNotNone(response.extended_metadata) + self.assertEqual(response.extended_metadata[metadata_key], metadata_value) + # Empty string and blank strings should be accepted just fine + # by this API + for metadata_value in ['', ' ']: + dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) + response = dapr.get_metadata() + self.assertIsNotNone(response) + self.assertIsNotNone(response.extended_metadata) + self.assertEqual(response.extended_metadata[metadata_key], metadata_value) + + def test_set_metadata_input_validation(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + valid_attr_name = 'attribute name' + valid_attr_value = 'attribute value' + # Invalid inputs for string arguments + with DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') as dapr: + for invalid_attr_name in [None, '', ' ']: + with self.assertRaises(ValueError): + dapr.set_metadata(invalid_attr_name, valid_attr_value) + # We are less strict with attribute values - we just cannot accept None + for invalid_attr_value in [None]: + with self.assertRaises(ValueError): + dapr.set_metadata(valid_attr_name, invalid_attr_value) + + # + # Tests for Cryptography API + # + + def test_encrypt_empty_component_name(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = EncryptOptions( + component_name='', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertIn('component_name', str(err)) + + def test_encrypt_empty_key_name(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = EncryptOptions( + component_name='crypto_component', + key_name='', + key_wrap_algorithm='RSA', + ) + dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertIn('key_name', str(err)) + + def test_encrypt_empty_key_wrap_algorithm(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='', + ) + dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertIn('key_wrap_algorithm', str(err)) + + def test_encrypt_string_data_read_all(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertEqual(resp.read(), b'HELLO DAPR') + + def test_encrypt_string_data_read_chunks(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertEqual(resp.read(5), b'HELLO') + self.assertEqual(resp.read(5), b' DAPR') + + def test_encrypt_file_data_read_all(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'hello dapr') + temp_file.seek(0) + + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = dapr.encrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(resp.read(), b'HELLO DAPR') + + def test_encrypt_file_data_read_chunks(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'hello dapr') + temp_file.seek(0) + + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = dapr.encrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(resp.read(5), b'HELLO') + self.assertEqual(resp.read(5), b' DAPR') + + def test_decrypt_empty_component_name(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = DecryptOptions( + component_name='', + ) + dapr.decrypt( + data='HELLO DAPR', + options=options, + ) + self.assertIn('component_name', str(err)) + + def test_decrypt_string_data_read_all(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + options = DecryptOptions( + component_name='crypto_component', + ) + resp = dapr.decrypt( + data='HELLO DAPR', + options=options, + ) + self.assertEqual(resp.read(), b'hello dapr') + + def test_decrypt_string_data_read_chunks(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + options = DecryptOptions( + component_name='crypto_component', + ) + resp = dapr.decrypt( + data='HELLO DAPR', + options=options, + ) + self.assertEqual(resp.read(5), b'hello') + self.assertEqual(resp.read(5), b' dapr') + + def test_decrypt_file_data_read_all(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'HELLO DAPR') + temp_file.seek(0) + + options = DecryptOptions( + component_name='crypto_component', + ) + resp = dapr.decrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(resp.read(), b'hello dapr') + + def test_decrypt_file_data_read_chunks(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'HELLO DAPR') + temp_file.seek(0) + + options = DecryptOptions( + component_name='crypto_component', + ) + resp = dapr.decrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(resp.read(5), b'hello') + self.assertEqual(resp.read(5), b' dapr') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_client_async.py b/tests/clients/test_dapr_grpc_client_async.py index f15a2d1af..d1514ea82 100644 --- a/tests/clients/test_dapr_grpc_client_async.py +++ b/tests/clients/test_dapr_grpc_client_async.py @@ -1,1118 +1,1118 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import json -import socket -import tempfile -import unittest -import uuid -from unittest.mock import patch - -from google.rpc import status_pb2, code_pb2 - -from dapr.aio.clients.grpc.client import DaprGrpcClientAsync -from dapr.aio.clients import DaprClient -from dapr.clients.exceptions import DaprGrpcError -from dapr.common.pubsub.subscription import StreamInactiveError -from dapr.proto import common_v1 -from .fake_dapr_server import FakeDaprSidecar -from dapr.conf import settings -from dapr.clients.grpc._helpers import to_bytes -from dapr.clients.grpc._request import TransactionalStateOperation -from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.clients.grpc._response import ( - ConfigurationItem, - ConfigurationWatcher, - ConfigurationResponse, - UnlockResponseStatus, -) - - -class DaprGrpcClientAsyncTests(unittest.IsolatedAsyncioTestCase): - grpc_port = 50001 - http_port = 3500 - scheme = '' - - @classmethod - def setUpClass(cls): - cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) - cls._fake_dapr_server.start() - - settings.DAPR_HTTP_PORT = cls.http_port - settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(cls.http_port) - - @classmethod - def tearDownClass(cls): - cls._fake_dapr_server.stop() - - async def test_http_extension(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - - # Test POST verb without querystring - ext = dapr._get_http_extension('POST') - self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) - - # Test Non-supported http verb - with self.assertRaises(ValueError): - ext = dapr._get_http_extension('') - - # Test POST verb with querystring - qs = ( - ('query1', 'string1'), - ('query2', 'string2'), - ('query1', 'string 3'), - ) - ext = dapr._get_http_extension('POST', qs) - - self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) - self.assertEqual('query1=string1&query2=string2&query1=string+3', ext.querystring) - - async def test_invoke_method_bytes_data(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_method( - app_id='targetId', - method_name='bytes', - data=b'haha', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - http_verb='PUT', - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - async def test_invoke_method_no_data(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_method( - app_id='targetId', - method_name='bytes', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - http_verb='PUT', - ) - - self.assertEqual(b'', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - async def test_invoke_method_with_dapr_client(self): - dapr = DaprClient(f'{self.scheme}localhost:{self.grpc_port}') - dapr.invocation_client = None # force to use grpc client - - resp = await dapr.invoke_method( - app_id='targetId', - method_name='bytes', - data=b'haha', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - http_verb='PUT', - ) - self.assertEqual(b'haha', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - async def test_invoke_method_proto_data(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - req = common_v1.StateItem(key='test') - resp = await dapr.invoke_method( - app_id='targetId', - method_name='proto', - data=req, - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - self.assertTrue(resp.is_proto()) - - # unpack to new protobuf object - new_resp = common_v1.StateItem() - resp.unpack(new_resp) - self.assertEqual('test', new_resp.key) - - async def test_invoke_binding_bytes_data(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_binding( - binding_name='binding', - operation='create', - data=b'haha', - binding_metadata={ - 'key1': 'value1', - 'key2': 'value2', - }, - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual({'key1': 'value1', 'key2': 'value2'}, resp.binding_metadata) - self.assertEqual(2, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - - async def test_invoke_binding_no_metadata(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_binding( - binding_name='binding', - operation='create', - data=b'haha', - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(0, len(resp.headers)) - - async def test_invoke_binding_no_data(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_binding( - binding_name='binding', - operation='create', - ) - - self.assertEqual(b'', resp.data) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(0, len(resp.headers)) - - async def test_invoke_binding_no_create(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_binding( - binding_name='binding', - operation='delete', - data=b'haha', - ) - - self.assertEqual(b'INVALID', resp.data) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(0, len(resp.headers)) - - async def test_publish_event(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.publish_event( - pubsub_name='pubsub', topic_name='example', data=b'test_data' - ) - - self.assertEqual(2, len(resp.headers)) - self.assertEqual(['test_data'], resp.headers['hdata']) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - await dapr.publish_event(pubsub_name='pubsub', topic_name='example', data=b'test_data') - - async def test_publish_event_with_content_type(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.publish_event( - pubsub_name='pubsub', - topic_name='example', - data=b'{"foo": "bar"}', - data_content_type='application/json', - ) - - self.assertEqual(3, len(resp.headers)) - self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) - self.assertEqual(['application/json'], resp.headers['data_content_type']) - - async def test_publish_event_with_metadata(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.publish_event( - pubsub_name='pubsub', - topic_name='example', - data=b'{"foo": "bar"}', - publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, - ) - - print(resp.headers) - self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) - self.assertEqual(['false'], resp.headers['metadata_raw_payload']) - self.assertEqual(['100'], resp.headers['metadata_ttl_in_seconds']) - - async def test_publish_error(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaisesRegex(ValueError, "invalid type for data "): - await dapr.publish_event( - pubsub_name='pubsub', - topic_name='example', - data=111, - ) - - async def test_subscribe_topic(self): - # The fake server we're using sends two messages and then closes the stream - # The client should be able to read both messages, handle the stream closure and reconnect - # which will result in reading the same two messages again. - # That's why message 3 should be the same as message 1 - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - subscription = await dapr.subscribe(pubsub_name='pubsub', topic='example') - - # First message - text - message1 = await subscription.next_message() - await subscription.respond_success(message1) - - self.assertEqual('111', message1.id()) - self.assertEqual('app1', message1.source()) - self.assertEqual('com.example.type2', message1.type()) - self.assertEqual('1.0', message1.spec_version()) - self.assertEqual('text/plain', message1.data_content_type()) - self.assertEqual('TOPIC_A', message1.topic()) - self.assertEqual('pubsub', message1.pubsub_name()) - self.assertEqual(b'hello2', message1.raw_data()) - self.assertEqual('text/plain', message1.data_content_type()) - self.assertEqual('hello2', message1.data()) - - # Second message - json - message2 = await subscription.next_message() - await subscription.respond_success(message2) - - self.assertEqual('222', message2.id()) - self.assertEqual('app1', message2.source()) - self.assertEqual('com.example.type2', message2.type()) - self.assertEqual('1.0', message2.spec_version()) - self.assertEqual('TOPIC_A', message2.topic()) - self.assertEqual('pubsub', message2.pubsub_name()) - self.assertEqual(b'{"a": 1}', message2.raw_data()) - self.assertEqual('application/json', message2.data_content_type()) - self.assertEqual({'a': 1}, message2.data()) - - # On this call the stream will be closed and return an error, so the message will be none - # but the client will try to reconnect - message3 = await subscription.next_message() - self.assertIsNone(message3) - - # # The client already reconnected and will start reading the messages again - # # Since we're working with a fake server, the messages will be the same - message4 = await subscription.next_message() - await subscription.respond_success(message4) - self.assertEqual('111', message4.id()) - self.assertEqual('app1', message4.source()) - self.assertEqual('com.example.type2', message4.type()) - self.assertEqual('1.0', message4.spec_version()) - self.assertEqual('text/plain', message4.data_content_type()) - self.assertEqual('TOPIC_A', message4.topic()) - self.assertEqual('pubsub', message4.pubsub_name()) - self.assertEqual(b'hello2', message4.raw_data()) - self.assertEqual('text/plain', message4.data_content_type()) - self.assertEqual('hello2', message4.data()) - - await subscription.close() - - async def test_subscribe_topic_early_close(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - subscription = await dapr.subscribe(pubsub_name='pubsub', topic='example') - await subscription.close() - - with self.assertRaises(StreamInactiveError): - await subscription.next_message() - - # async def test_subscribe_topic_with_handler(self): - # # The fake server we're using sends two messages and then closes the stream - # # The client should be able to read both messages, handle the stream closure and reconnect - # # which will result in reading the same two messages again. - # # That's why message 3 should be the same as message 1 - # dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # counter = 0 - # - # async def handler(message): - # nonlocal counter - # if counter == 0: - # self.assertEqual('111', message.id()) - # self.assertEqual('app1', message.source()) - # self.assertEqual('com.example.type2', message.type()) - # self.assertEqual('1.0', message.spec_version()) - # self.assertEqual('text/plain', message.data_content_type()) - # self.assertEqual('TOPIC_A', message.topic()) - # self.assertEqual('pubsub', message.pubsub_name()) - # self.assertEqual(b'hello2', message.raw_data()) - # self.assertEqual('text/plain', message.data_content_type()) - # self.assertEqual('hello2', message.data()) - # elif counter == 1: - # self.assertEqual('222', message.id()) - # self.assertEqual('app1', message.source()) - # self.assertEqual('com.example.type2', message.type()) - # self.assertEqual('1.0', message.spec_version()) - # self.assertEqual('TOPIC_A', message.topic()) - # self.assertEqual('pubsub', message.pubsub_name()) - # self.assertEqual(b'{"a": 1}', message.raw_data()) - # self.assertEqual('application/json', message.data_content_type()) - # self.assertEqual({'a': 1}, message.data()) - # elif counter == 2: - # self.assertEqual('111', message.id()) - # self.assertEqual('app1', message.source()) - # self.assertEqual('com.example.type2', message.type()) - # self.assertEqual('1.0', message.spec_version()) - # self.assertEqual('text/plain', message.data_content_type()) - # self.assertEqual('TOPIC_A', message.topic()) - # self.assertEqual('pubsub', message.pubsub_name()) - # self.assertEqual(b'hello2', message.raw_data()) - # self.assertEqual('text/plain', message.data_content_type()) - # self.assertEqual('hello2', message.data()) - # - # counter += 1 - # - # return TopicEventResponse("success") - # - # close_fn = await dapr.subscribe_with_handler( - # pubsub_name='pubsub', topic='example', handler_fn=handler - # ) - # - # while counter < 3: - # await asyncio.sleep(0.1) # sleep to prevent a busy loop - # await close_fn() - - @patch.object(settings, 'DAPR_API_TOKEN', 'test-token') - async def test_dapr_api_token_insertion(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.invoke_method( - app_id='targetId', - method_name='bytes', - data=b'haha', - content_type='text/plain', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(b'haha', resp.data) - self.assertEqual('text/plain', resp.content_type) - self.assertEqual(4, len(resp.headers)) - self.assertEqual(['value1'], resp.headers['hkey1']) - self.assertEqual(['test-token'], resp.headers['hdapr-api-token']) - - async def test_get_save_delete_state(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - key = 'key_1' - value = 'value_1' - options = StateOptions( - consistency=Consistency.eventual, - concurrency=Concurrency.first_write, - ) - await dapr.save_state( - store_name='statestore', - key=key, - value=value, - etag='fake_etag', - options=options, - state_metadata={'capitalize': '1'}, - ) - - resp = await dapr.get_state(store_name='statestore', key=key) - self.assertEqual(resp.data, to_bytes(value.capitalize())) - self.assertEqual(resp.etag, 'fake_etag') - - resp = await dapr.get_state(store_name='statestore', key=key, state_metadata={'upper': '1'}) - self.assertEqual(resp.data, to_bytes(value.upper())) - self.assertEqual(resp.etag, 'fake_etag') - - resp = await dapr.get_state(store_name='statestore', key='NotValidKey') - self.assertEqual(resp.data, b'') - self.assertEqual(resp.etag, '') - - # Check a DaprGrpcError is raised - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError) as context: - await dapr.get_state(store_name='my_statestore', key='key||') - - await dapr.delete_state(store_name='statestore', key=key) - resp = await dapr.get_state(store_name='statestore', key=key) - self.assertEqual(resp.data, b'') - self.assertEqual(resp.etag, '') - - with self.assertRaises(DaprGrpcError) as context: - await dapr.delete_state( - store_name='statestore', key=key, state_metadata={'must_delete': '1'} - ) - print(context.exception) - self.assertTrue('delete failed' in str(context.exception)) - - async def test_get_save_state_etag_none(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - - value = 'test' - no_etag_key = 'no_etag' - empty_etag_key = 'empty_etag' - await dapr.save_state( - store_name='statestore', - key=no_etag_key, - value=value, - ) - - await dapr.save_state(store_name='statestore', key=empty_etag_key, value=value, etag='') - - resp = await dapr.get_state(store_name='statestore', key=no_etag_key) - self.assertEqual(resp.data, to_bytes(value)) - self.assertEqual(resp.etag, 'ETAG_WAS_NONE') - - resp = await dapr.get_state(store_name='statestore', key=empty_etag_key) - self.assertEqual(resp.data, to_bytes(value)) - self.assertEqual(resp.etag, '') - - async def test_transaction_then_get_states(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - - key = str(uuid.uuid4()) - value = str(uuid.uuid4()) - another_key = str(uuid.uuid4()) - another_value = str(uuid.uuid4()) - - await dapr.execute_state_transaction( - store_name='statestore', - operations=[ - TransactionalStateOperation(key=key, data=value, etag='foo'), - TransactionalStateOperation(key=another_key, data=another_value), - ], - transactional_metadata={'metakey': 'metavalue'}, - ) - - resp = await dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].data, to_bytes(value)) - self.assertEqual(resp.items[0].etag, 'foo') - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].data, to_bytes(another_value)) - self.assertEqual(resp.items[1].etag, 'ETAG_WAS_NONE') - - resp = await dapr.get_bulk_state( - store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} - ) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].data, to_bytes(value.upper())) - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - await dapr.execute_state_transaction( - store_name='statestore', - operations=[ - TransactionalStateOperation(key=key, data=value, etag='foo'), - TransactionalStateOperation(key=another_key, data=another_value), - ], - transactional_metadata={'metakey': 'metavalue'}, - ) - - async def test_bulk_save_then_get_states(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - - key = str(uuid.uuid4()) - value = str(uuid.uuid4()) - another_key = str(uuid.uuid4()) - another_value = str(uuid.uuid4()) - - await dapr.save_bulk_state( - store_name='statestore', - states=[ - StateItem(key=key, value=value, metadata={'capitalize': '1'}), - StateItem(key=another_key, value=another_value, etag='1'), - ], - metadata=(('metakey', 'metavalue'),), - ) - - resp = await dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') - self.assertEqual(resp.items[0].data, to_bytes(value.capitalize())) - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].data, to_bytes(another_value)) - self.assertEqual(resp.items[1].etag, '1') - - resp = await dapr.get_bulk_state( - store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} - ) - self.assertEqual(resp.items[0].key, key) - self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') - self.assertEqual(resp.items[0].data, to_bytes(value.upper())) - self.assertEqual(resp.items[1].key, another_key) - self.assertEqual(resp.items[1].etag, '1') - self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - await dapr.save_bulk_state( - store_name='statestore', - states=[ - StateItem(key=key, value=value, metadata={'capitalize': '1'}), - StateItem(key=another_key, value=another_value, etag='1'), - ], - metadata=(('metakey', 'metavalue'),), - ) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - await dapr.get_bulk_state( - store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} - ) - - async def test_get_secret(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - key1 = 'key_1' - resp = await dapr.get_secret( - store_name='store_1', - key=key1, - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(1, len(resp.headers)) - self.assertEqual([key1], resp.headers['keyh']) - self.assertEqual({key1: 'val'}, resp._secret) - - async def test_get_secret_metadata_absent(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - key1 = 'key_1' - resp = await dapr.get_secret( - store_name='store_1', - key=key1, - ) - - self.assertEqual(1, len(resp.headers)) - self.assertEqual([key1], resp.headers['keyh']) - self.assertEqual({key1: 'val'}, resp._secret) - - async def test_get_bulk_secret(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.get_bulk_secret( - store_name='store_1', - metadata=( - ('key1', 'value1'), - ('key2', 'value2'), - ), - ) - - self.assertEqual(1, len(resp.headers)) - self.assertEqual(['bulk'], resp.headers['keyh']) - self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) - - async def test_get_bulk_secret_metadata_absent(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - resp = await dapr.get_bulk_secret(store_name='store_1') - - self.assertEqual(1, len(resp.headers)) - self.assertEqual(['bulk'], resp.headers['keyh']) - self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) - - async def test_get_configuration(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - keys = ['k', 'k1'] - value = 'value' - version = '1.5.0' - metadata = {} - - resp = await dapr.get_configuration(store_name='configurationstore', keys=keys) - self.assertEqual(len(resp.items), len(keys)) - self.assertIn(keys[0], resp.items) - item = resp.items[keys[0]] - self.assertEqual(item.value, value) - self.assertEqual(item.version, version) - self.assertEqual(item.metadata, metadata) - - resp = await dapr.get_configuration( - store_name='configurationstore', keys=keys, config_metadata=metadata - ) - self.assertEqual(len(resp.items), len(keys)) - self.assertIn(keys[0], resp.items) - item = resp.items[keys[0]] - self.assertEqual(item.value, value) - self.assertEqual(item.version, version) - self.assertEqual(item.metadata, metadata) - - async def test_subscribe_configuration(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - - def mock_watch(self, stub, store_name, keys, handler, config_metadata): - handler( - 'id', - ConfigurationResponse( - items={'k': ConfigurationItem(value='test', version='1.7.0')} - ), - ) - return 'id' - - def handler(id: str, resp: ConfigurationResponse): - self.assertEqual(resp.items['k'].value, 'test') - self.assertEqual(resp.items['k'].version, '1.7.0') - - with patch.object(ConfigurationWatcher, 'watch_configuration', mock_watch): - await dapr.subscribe_configuration( - store_name='configurationstore', keys=['k'], handler=handler - ) - - async def test_unsubscribe_configuration(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - res = await dapr.unsubscribe_configuration(store_name='configurationstore', id='k') - self.assertTrue(res) - - async def test_query_state(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - - resp = await dapr.query_state( - store_name='statestore', - query=json.dumps({'filter': {}, 'page': {'limit': 2}}), - ) - self.assertEqual(resp.results[0].key, '1') - self.assertEqual(len(resp.results), 2) - - resp = await dapr.query_state( - store_name='statestore', - query=json.dumps({'filter': {}, 'page': {'limit': 3, 'token': '3'}}), - ) - self.assertEqual(resp.results[0].key, '3') - self.assertEqual(len(resp.results), 3) - - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') - ) - with self.assertRaises(DaprGrpcError): - await dapr.query_state( - store_name='statestore', - query=json.dumps({'filter': {}, 'page': {'limit': 2}}), - ) - - async def test_shutdown(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - await dapr.shutdown() - self.assertTrue(self._fake_dapr_server.shutdown_received) - - async def test_wait_ok(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - await dapr.wait(0.1) - - async def test_wait_timeout(self): - # First, pick an unused port - port = 0 - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', 0)) - port = s.getsockname()[1] - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{port}') - with self.assertRaises(Exception) as context: - await dapr.wait(0.1) - self.assertTrue('Connection refused' in str(context.exception)) - - async def test_lock_acquire_success(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - lock_owner = str(uuid.uuid4()) - expiry_in_seconds = 60 - - success = await dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) - self.assertTrue(success) - unlock_response = await dapr.unlock(store_name, resource_id, lock_owner) - self.assertEqual(UnlockResponseStatus.success, unlock_response.status) - - async def test_lock_release_twice_fails(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - lock_owner = str(uuid.uuid4()) - expiry_in_seconds = 60 - - success = await dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) - self.assertTrue(success) - unlock_response = await dapr.unlock(store_name, resource_id, lock_owner) - self.assertEqual(UnlockResponseStatus.success, unlock_response.status) - # If client tries again it will discover the lock is gone - unlock_response = await dapr.unlock(store_name, resource_id, lock_owner) - self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) - - async def test_lock_conflict(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - first_client_id = str(uuid.uuid4()) - second_client_id = str(uuid.uuid4()) - expiry_in_seconds = 60 - - # First client succeeds - success = await dapr.try_lock(store_name, resource_id, first_client_id, expiry_in_seconds) - self.assertTrue(success) - # Second client tries and fails - resource already acquired - success = await dapr.try_lock(store_name, resource_id, second_client_id, expiry_in_seconds) - self.assertFalse(success) - # Second client is a sneaky fellow and tries to release a lock it doesn't own - unlock_response = await dapr.unlock(store_name, resource_id, second_client_id) - self.assertEqual(UnlockResponseStatus.lock_belongs_to_others, unlock_response.status) - # First client can stil return the lock as rightful owner - unlock_response = await dapr.unlock(store_name, resource_id, first_client_id) - self.assertEqual(UnlockResponseStatus.success, unlock_response.status) - - async def test_lock_not_previously_acquired(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - unlock_response = await dapr.unlock( - store_name='lockstore', resource_id=str(uuid.uuid4()), lock_owner=str(uuid.uuid4()) - ) - self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) - - async def test_lock_release_twice_fails_with_context_manager(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - first_client_id = str(uuid.uuid4()) - second_client_id = str(uuid.uuid4()) - expiry = 60 - - async with await dapr.try_lock( - store_name, resource_id, first_client_id, expiry - ) as first_lock: - self.assertTrue(first_lock.success) - # If another client tries to acquire the same lock it will fail - async with await dapr.try_lock( - store_name, resource_id, second_client_id, expiry - ) as second_lock: - self.assertFalse(second_lock.success) - # At this point lock was auto-released - # If client tries again it will discover the lock is gone - unlock_response = await dapr.unlock(store_name, resource_id, first_client_id) - self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) - - async def test_lock_are_not_reentrant(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Lock parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - client_id = str(uuid.uuid4()) - expiry_in_s = 60 - - async with await dapr.try_lock( - store_name, resource_id, client_id, expiry_in_s - ) as first_attempt: - self.assertTrue(first_attempt.success) - # If the same client tries to acquire the same lock again it will fail. - async with await dapr.try_lock( - store_name, resource_id, client_id, expiry_in_s - ) as second_attempt: - self.assertFalse(second_attempt.success) - - async def test_lock_input_validation(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Sane parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - client_id = str(uuid.uuid4()) - expiry_in_s = 60 - # Invalid inputs for string arguments - for invalid_input in [None, '', ' ']: - # store_name - with self.assertRaises(ValueError): - async with await dapr.try_lock( - invalid_input, resource_id, client_id, expiry_in_s - ) as res: - self.assertTrue(res.success) - # resource_id - with self.assertRaises(ValueError): - async with await dapr.try_lock( - store_name, invalid_input, client_id, expiry_in_s - ) as res: - self.assertTrue(res.success) - # client_id - with self.assertRaises(ValueError): - async with await dapr.try_lock( - store_name, resource_id, invalid_input, expiry_in_s - ) as res: - self.assertTrue(res.success) - # Invalid inputs for expiry_in_s - for invalid_input in [None, -1, 0]: - with self.assertRaises(ValueError): - async with await dapr.try_lock( - store_name, resource_id, client_id, invalid_input - ) as res: - self.assertTrue(res.success) - - async def test_unlock_input_validation(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - # Sane parameters - store_name = 'lockstore' - resource_id = str(uuid.uuid4()) - client_id = str(uuid.uuid4()) - # Invalid inputs for string arguments - for invalid_input in [None, '', ' ']: - # store_name - with self.assertRaises(ValueError): - await dapr.unlock(invalid_input, resource_id, client_id) - # resource_id - with self.assertRaises(ValueError): - await dapr.unlock(store_name, invalid_input, client_id) - # client_id - with self.assertRaises(ValueError): - await dapr.unlock(store_name, resource_id, invalid_input) - - # - # Tests for Metadata API - # - - async def test_get_metadata(self): - async with DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') as dapr: - response = await dapr.get_metadata() - - self.assertIsNotNone(response) - - self.assertEqual(response.application_id, 'myapp') - - actors = response.active_actors_count - self.assertIsNotNone(actors) - self.assertTrue(len(actors) > 0) - for actorType, count in actors.items(): - # Assert both are non-null and non-empty/zero - self.assertTrue(actorType) - self.assertTrue(count) - - self.assertIsNotNone(response.registered_components) - self.assertTrue(len(response.registered_components) > 0) - components = {c.name: c for c in response.registered_components} - # common tests for all components - for c in components.values(): - self.assertTrue(c.name) - self.assertTrue(c.type) - self.assertIsNotNone(c.version) - self.assertIsNotNone(c.capabilities) - self.assertTrue('ETAG' in components['statestore'].capabilities) - - self.assertIsNotNone(response.extended_metadata) - - async def test_set_metadata(self): - metadata_key = 'test_set_metadata_attempt' - async with DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') as dapr: - for metadata_value in [str(i) for i in range(10)]: - await dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) - response = await dapr.get_metadata() - self.assertIsNotNone(response) - self.assertIsNotNone(response.extended_metadata) - self.assertEqual(response.extended_metadata[metadata_key], metadata_value) - # Empty string and blank strings should be accepted just fine - # by this API - for metadata_value in ['', ' ']: - await dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) - response = await dapr.get_metadata() - self.assertIsNotNone(response) - self.assertIsNotNone(response.extended_metadata) - self.assertEqual(response.extended_metadata[metadata_key], metadata_value) - - async def test_set_metadata_input_validation(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - valid_attr_name = 'attribute name' - valid_attr_value = 'attribute value' - # Invalid inputs for string arguments - async with DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') as dapr: - for invalid_attr_name in [None, '', ' ']: - with self.assertRaises(ValueError): - await dapr.set_metadata(invalid_attr_name, valid_attr_value) - # We are less strict with attribute values - we just cannot accept None - for invalid_attr_value in [None]: - with self.assertRaises(ValueError): - await dapr.set_metadata(valid_attr_name, invalid_attr_value) - - # - # Tests for Cryptography API - # - - async def test_encrypt_empty_component_name(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = EncryptOptions( - component_name='', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - await dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertIn('component_name', str(err)) - - async def test_encrypt_empty_key_name(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = EncryptOptions( - component_name='crypto_component', - key_name='', - key_wrap_algorithm='RSA', - ) - await dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertIn('key_name', str(err)) - - async def test_encrypt_empty_key_wrap_algorithm(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='', - ) - await dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertIn('key_wrap_algorithm', str(err)) - - async def test_encrypt_string_data_read_all(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = await dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertEqual(await resp.read(), b'HELLO DAPR') - - async def test_encrypt_string_data_read_chunks(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = await dapr.encrypt( - data='hello dapr', - options=options, - ) - self.assertEqual(await resp.read(5), b'HELLO') - self.assertEqual(await resp.read(5), b' DAPR') - - async def test_encrypt_file_data_read_all(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'hello dapr') - temp_file.seek(0) - - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = await dapr.encrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(await resp.read(), b'HELLO DAPR') - - async def test_encrypt_file_data_read_chunks(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'hello dapr') - temp_file.seek(0) - - options = EncryptOptions( - component_name='crypto_component', - key_name='crypto_key', - key_wrap_algorithm='RSA', - ) - resp = await dapr.encrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(await resp.read(5), b'HELLO') - self.assertEqual(await resp.read(5), b' DAPR') - - async def test_decrypt_empty_component_name(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with self.assertRaises(ValueError) as err: - options = DecryptOptions( - component_name='', - ) - await dapr.decrypt( - data='HELLO DAPR', - options=options, - ) - self.assertIn('component_name', str(err)) - - async def test_decrypt_string_data_read_all(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - options = DecryptOptions( - component_name='crypto_component', - ) - resp = await dapr.decrypt( - data='HELLO DAPR', - options=options, - ) - self.assertEqual(await resp.read(), b'hello dapr') - - async def test_decrypt_string_data_read_chunks(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - options = DecryptOptions( - component_name='crypto_component', - ) - resp = await dapr.decrypt( - data='HELLO DAPR', - options=options, - ) - self.assertEqual(await resp.read(5), b'hello') - self.assertEqual(await resp.read(5), b' dapr') - - async def test_decrypt_file_data_read_all(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'HELLO DAPR') - temp_file.seek(0) - - options = DecryptOptions( - component_name='crypto_component', - ) - resp = await dapr.decrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(await resp.read(), b'hello dapr') - - async def test_decrypt_file_data_read_chunks(self): - dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') - with tempfile.TemporaryFile(mode='w+b') as temp_file: - temp_file.write(b'HELLO DAPR') - temp_file.seek(0) - - options = DecryptOptions( - component_name='crypto_component', - ) - resp = await dapr.decrypt( - data=temp_file.read(), - options=options, - ) - self.assertEqual(await resp.read(5), b'hello') - self.assertEqual(await resp.read(5), b' dapr') - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import json +import socket +import tempfile +import unittest +import uuid +from unittest.mock import patch + +from google.rpc import status_pb2, code_pb2 + +from dapr.aio.clients.grpc.client import DaprGrpcClientAsync +from dapr.aio.clients import DaprClient +from dapr.clients.exceptions import DaprGrpcError +from dapr.common.pubsub.subscription import StreamInactiveError +from dapr.proto import common_v1 +from .fake_dapr_server import FakeDaprSidecar +from dapr.conf import settings +from dapr.clients.grpc._helpers import to_bytes +from dapr.clients.grpc._request import TransactionalStateOperation +from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.clients.grpc._response import ( + ConfigurationItem, + ConfigurationWatcher, + ConfigurationResponse, + UnlockResponseStatus, +) + + +class DaprGrpcClientAsyncTests(unittest.IsolatedAsyncioTestCase): + grpc_port = 50001 + http_port = 3500 + scheme = '' + + @classmethod + def setUpClass(cls): + cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) + cls._fake_dapr_server.start() + + settings.DAPR_HTTP_PORT = cls.http_port + settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(cls.http_port) + + @classmethod + def tearDownClass(cls): + cls._fake_dapr_server.stop() + + async def test_http_extension(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + # Test POST verb without querystring + ext = dapr._get_http_extension('POST') + self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) + + # Test Non-supported http verb + with self.assertRaises(ValueError): + ext = dapr._get_http_extension('') + + # Test POST verb with querystring + qs = ( + ('query1', 'string1'), + ('query2', 'string2'), + ('query1', 'string 3'), + ) + ext = dapr._get_http_extension('POST', qs) + + self.assertEqual(common_v1.HTTPExtension.Verb.POST, ext.verb) + self.assertEqual('query1=string1&query2=string2&query1=string+3', ext.querystring) + + async def test_invoke_method_bytes_data(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_method( + app_id='targetId', + method_name='bytes', + data=b'haha', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + http_verb='PUT', + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + async def test_invoke_method_no_data(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_method( + app_id='targetId', + method_name='bytes', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + http_verb='PUT', + ) + + self.assertEqual(b'', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + async def test_invoke_method_with_dapr_client(self): + dapr = DaprClient(f'{self.scheme}localhost:{self.grpc_port}') + dapr.invocation_client = None # force to use grpc client + + resp = await dapr.invoke_method( + app_id='targetId', + method_name='bytes', + data=b'haha', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + http_verb='PUT', + ) + self.assertEqual(b'haha', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + async def test_invoke_method_proto_data(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + req = common_v1.StateItem(key='test') + resp = await dapr.invoke_method( + app_id='targetId', + method_name='proto', + data=req, + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + self.assertTrue(resp.is_proto()) + + # unpack to new protobuf object + new_resp = common_v1.StateItem() + resp.unpack(new_resp) + self.assertEqual('test', new_resp.key) + + async def test_invoke_binding_bytes_data(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_binding( + binding_name='binding', + operation='create', + data=b'haha', + binding_metadata={ + 'key1': 'value1', + 'key2': 'value2', + }, + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual({'key1': 'value1', 'key2': 'value2'}, resp.binding_metadata) + self.assertEqual(2, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + + async def test_invoke_binding_no_metadata(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_binding( + binding_name='binding', + operation='create', + data=b'haha', + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(0, len(resp.headers)) + + async def test_invoke_binding_no_data(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_binding( + binding_name='binding', + operation='create', + ) + + self.assertEqual(b'', resp.data) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(0, len(resp.headers)) + + async def test_invoke_binding_no_create(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_binding( + binding_name='binding', + operation='delete', + data=b'haha', + ) + + self.assertEqual(b'INVALID', resp.data) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(0, len(resp.headers)) + + async def test_publish_event(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.publish_event( + pubsub_name='pubsub', topic_name='example', data=b'test_data' + ) + + self.assertEqual(2, len(resp.headers)) + self.assertEqual(['test_data'], resp.headers['hdata']) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + await dapr.publish_event(pubsub_name='pubsub', topic_name='example', data=b'test_data') + + async def test_publish_event_with_content_type(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.publish_event( + pubsub_name='pubsub', + topic_name='example', + data=b'{"foo": "bar"}', + data_content_type='application/json', + ) + + self.assertEqual(3, len(resp.headers)) + self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) + self.assertEqual(['application/json'], resp.headers['data_content_type']) + + async def test_publish_event_with_metadata(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.publish_event( + pubsub_name='pubsub', + topic_name='example', + data=b'{"foo": "bar"}', + publish_metadata={'ttlInSeconds': '100', 'rawPayload': 'false'}, + ) + + print(resp.headers) + self.assertEqual(['{"foo": "bar"}'], resp.headers['hdata']) + self.assertEqual(['false'], resp.headers['metadata_raw_payload']) + self.assertEqual(['100'], resp.headers['metadata_ttl_in_seconds']) + + async def test_publish_error(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaisesRegex(ValueError, "invalid type for data "): + await dapr.publish_event( + pubsub_name='pubsub', + topic_name='example', + data=111, + ) + + async def test_subscribe_topic(self): + # The fake server we're using sends two messages and then closes the stream + # The client should be able to read both messages, handle the stream closure and reconnect + # which will result in reading the same two messages again. + # That's why message 3 should be the same as message 1 + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + subscription = await dapr.subscribe(pubsub_name='pubsub', topic='example') + + # First message - text + message1 = await subscription.next_message() + await subscription.respond_success(message1) + + self.assertEqual('111', message1.id()) + self.assertEqual('app1', message1.source()) + self.assertEqual('com.example.type2', message1.type()) + self.assertEqual('1.0', message1.spec_version()) + self.assertEqual('text/plain', message1.data_content_type()) + self.assertEqual('TOPIC_A', message1.topic()) + self.assertEqual('pubsub', message1.pubsub_name()) + self.assertEqual(b'hello2', message1.raw_data()) + self.assertEqual('text/plain', message1.data_content_type()) + self.assertEqual('hello2', message1.data()) + + # Second message - json + message2 = await subscription.next_message() + await subscription.respond_success(message2) + + self.assertEqual('222', message2.id()) + self.assertEqual('app1', message2.source()) + self.assertEqual('com.example.type2', message2.type()) + self.assertEqual('1.0', message2.spec_version()) + self.assertEqual('TOPIC_A', message2.topic()) + self.assertEqual('pubsub', message2.pubsub_name()) + self.assertEqual(b'{"a": 1}', message2.raw_data()) + self.assertEqual('application/json', message2.data_content_type()) + self.assertEqual({'a': 1}, message2.data()) + + # On this call the stream will be closed and return an error, so the message will be none + # but the client will try to reconnect + message3 = await subscription.next_message() + self.assertIsNone(message3) + + # # The client already reconnected and will start reading the messages again + # # Since we're working with a fake server, the messages will be the same + message4 = await subscription.next_message() + await subscription.respond_success(message4) + self.assertEqual('111', message4.id()) + self.assertEqual('app1', message4.source()) + self.assertEqual('com.example.type2', message4.type()) + self.assertEqual('1.0', message4.spec_version()) + self.assertEqual('text/plain', message4.data_content_type()) + self.assertEqual('TOPIC_A', message4.topic()) + self.assertEqual('pubsub', message4.pubsub_name()) + self.assertEqual(b'hello2', message4.raw_data()) + self.assertEqual('text/plain', message4.data_content_type()) + self.assertEqual('hello2', message4.data()) + + await subscription.close() + + async def test_subscribe_topic_early_close(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + subscription = await dapr.subscribe(pubsub_name='pubsub', topic='example') + await subscription.close() + + with self.assertRaises(StreamInactiveError): + await subscription.next_message() + + # async def test_subscribe_topic_with_handler(self): + # # The fake server we're using sends two messages and then closes the stream + # # The client should be able to read both messages, handle the stream closure and reconnect + # # which will result in reading the same two messages again. + # # That's why message 3 should be the same as message 1 + # dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # counter = 0 + # + # async def handler(message): + # nonlocal counter + # if counter == 0: + # self.assertEqual('111', message.id()) + # self.assertEqual('app1', message.source()) + # self.assertEqual('com.example.type2', message.type()) + # self.assertEqual('1.0', message.spec_version()) + # self.assertEqual('text/plain', message.data_content_type()) + # self.assertEqual('TOPIC_A', message.topic()) + # self.assertEqual('pubsub', message.pubsub_name()) + # self.assertEqual(b'hello2', message.raw_data()) + # self.assertEqual('text/plain', message.data_content_type()) + # self.assertEqual('hello2', message.data()) + # elif counter == 1: + # self.assertEqual('222', message.id()) + # self.assertEqual('app1', message.source()) + # self.assertEqual('com.example.type2', message.type()) + # self.assertEqual('1.0', message.spec_version()) + # self.assertEqual('TOPIC_A', message.topic()) + # self.assertEqual('pubsub', message.pubsub_name()) + # self.assertEqual(b'{"a": 1}', message.raw_data()) + # self.assertEqual('application/json', message.data_content_type()) + # self.assertEqual({'a': 1}, message.data()) + # elif counter == 2: + # self.assertEqual('111', message.id()) + # self.assertEqual('app1', message.source()) + # self.assertEqual('com.example.type2', message.type()) + # self.assertEqual('1.0', message.spec_version()) + # self.assertEqual('text/plain', message.data_content_type()) + # self.assertEqual('TOPIC_A', message.topic()) + # self.assertEqual('pubsub', message.pubsub_name()) + # self.assertEqual(b'hello2', message.raw_data()) + # self.assertEqual('text/plain', message.data_content_type()) + # self.assertEqual('hello2', message.data()) + # + # counter += 1 + # + # return TopicEventResponse("success") + # + # close_fn = await dapr.subscribe_with_handler( + # pubsub_name='pubsub', topic='example', handler_fn=handler + # ) + # + # while counter < 3: + # await asyncio.sleep(0.1) # sleep to prevent a busy loop + # await close_fn() + + @patch.object(settings, 'DAPR_API_TOKEN', 'test-token') + async def test_dapr_api_token_insertion(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.invoke_method( + app_id='targetId', + method_name='bytes', + data=b'haha', + content_type='text/plain', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(b'haha', resp.data) + self.assertEqual('text/plain', resp.content_type) + self.assertEqual(4, len(resp.headers)) + self.assertEqual(['value1'], resp.headers['hkey1']) + self.assertEqual(['test-token'], resp.headers['hdapr-api-token']) + + async def test_get_save_delete_state(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + key = 'key_1' + value = 'value_1' + options = StateOptions( + consistency=Consistency.eventual, + concurrency=Concurrency.first_write, + ) + await dapr.save_state( + store_name='statestore', + key=key, + value=value, + etag='fake_etag', + options=options, + state_metadata={'capitalize': '1'}, + ) + + resp = await dapr.get_state(store_name='statestore', key=key) + self.assertEqual(resp.data, to_bytes(value.capitalize())) + self.assertEqual(resp.etag, 'fake_etag') + + resp = await dapr.get_state(store_name='statestore', key=key, state_metadata={'upper': '1'}) + self.assertEqual(resp.data, to_bytes(value.upper())) + self.assertEqual(resp.etag, 'fake_etag') + + resp = await dapr.get_state(store_name='statestore', key='NotValidKey') + self.assertEqual(resp.data, b'') + self.assertEqual(resp.etag, '') + + # Check a DaprGrpcError is raised + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError) as context: + await dapr.get_state(store_name='my_statestore', key='key||') + + await dapr.delete_state(store_name='statestore', key=key) + resp = await dapr.get_state(store_name='statestore', key=key) + self.assertEqual(resp.data, b'') + self.assertEqual(resp.etag, '') + + with self.assertRaises(DaprGrpcError) as context: + await dapr.delete_state( + store_name='statestore', key=key, state_metadata={'must_delete': '1'} + ) + print(context.exception) + self.assertTrue('delete failed' in str(context.exception)) + + async def test_get_save_state_etag_none(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + value = 'test' + no_etag_key = 'no_etag' + empty_etag_key = 'empty_etag' + await dapr.save_state( + store_name='statestore', + key=no_etag_key, + value=value, + ) + + await dapr.save_state(store_name='statestore', key=empty_etag_key, value=value, etag='') + + resp = await dapr.get_state(store_name='statestore', key=no_etag_key) + self.assertEqual(resp.data, to_bytes(value)) + self.assertEqual(resp.etag, 'ETAG_WAS_NONE') + + resp = await dapr.get_state(store_name='statestore', key=empty_etag_key) + self.assertEqual(resp.data, to_bytes(value)) + self.assertEqual(resp.etag, '') + + async def test_transaction_then_get_states(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + key = str(uuid.uuid4()) + value = str(uuid.uuid4()) + another_key = str(uuid.uuid4()) + another_value = str(uuid.uuid4()) + + await dapr.execute_state_transaction( + store_name='statestore', + operations=[ + TransactionalStateOperation(key=key, data=value, etag='foo'), + TransactionalStateOperation(key=another_key, data=another_value), + ], + transactional_metadata={'metakey': 'metavalue'}, + ) + + resp = await dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].data, to_bytes(value)) + self.assertEqual(resp.items[0].etag, 'foo') + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].data, to_bytes(another_value)) + self.assertEqual(resp.items[1].etag, 'ETAG_WAS_NONE') + + resp = await dapr.get_bulk_state( + store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} + ) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].data, to_bytes(value.upper())) + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + await dapr.execute_state_transaction( + store_name='statestore', + operations=[ + TransactionalStateOperation(key=key, data=value, etag='foo'), + TransactionalStateOperation(key=another_key, data=another_value), + ], + transactional_metadata={'metakey': 'metavalue'}, + ) + + async def test_bulk_save_then_get_states(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + key = str(uuid.uuid4()) + value = str(uuid.uuid4()) + another_key = str(uuid.uuid4()) + another_value = str(uuid.uuid4()) + + await dapr.save_bulk_state( + store_name='statestore', + states=[ + StateItem(key=key, value=value, metadata={'capitalize': '1'}), + StateItem(key=another_key, value=another_value, etag='1'), + ], + metadata=(('metakey', 'metavalue'),), + ) + + resp = await dapr.get_bulk_state(store_name='statestore', keys=[key, another_key]) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') + self.assertEqual(resp.items[0].data, to_bytes(value.capitalize())) + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].data, to_bytes(another_value)) + self.assertEqual(resp.items[1].etag, '1') + + resp = await dapr.get_bulk_state( + store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} + ) + self.assertEqual(resp.items[0].key, key) + self.assertEqual(resp.items[0].etag, 'ETAG_WAS_NONE') + self.assertEqual(resp.items[0].data, to_bytes(value.upper())) + self.assertEqual(resp.items[1].key, another_key) + self.assertEqual(resp.items[1].etag, '1') + self.assertEqual(resp.items[1].data, to_bytes(another_value.upper())) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + await dapr.save_bulk_state( + store_name='statestore', + states=[ + StateItem(key=key, value=value, metadata={'capitalize': '1'}), + StateItem(key=another_key, value=another_value, etag='1'), + ], + metadata=(('metakey', 'metavalue'),), + ) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + await dapr.get_bulk_state( + store_name='statestore', keys=[key, another_key], states_metadata={'upper': '1'} + ) + + async def test_get_secret(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + key1 = 'key_1' + resp = await dapr.get_secret( + store_name='store_1', + key=key1, + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(1, len(resp.headers)) + self.assertEqual([key1], resp.headers['keyh']) + self.assertEqual({key1: 'val'}, resp._secret) + + async def test_get_secret_metadata_absent(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + key1 = 'key_1' + resp = await dapr.get_secret( + store_name='store_1', + key=key1, + ) + + self.assertEqual(1, len(resp.headers)) + self.assertEqual([key1], resp.headers['keyh']) + self.assertEqual({key1: 'val'}, resp._secret) + + async def test_get_bulk_secret(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.get_bulk_secret( + store_name='store_1', + metadata=( + ('key1', 'value1'), + ('key2', 'value2'), + ), + ) + + self.assertEqual(1, len(resp.headers)) + self.assertEqual(['bulk'], resp.headers['keyh']) + self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) + + async def test_get_bulk_secret_metadata_absent(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + resp = await dapr.get_bulk_secret(store_name='store_1') + + self.assertEqual(1, len(resp.headers)) + self.assertEqual(['bulk'], resp.headers['keyh']) + self.assertEqual({'keya': {'keyb': 'val'}}, resp._secrets) + + async def test_get_configuration(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + keys = ['k', 'k1'] + value = 'value' + version = '1.5.0' + metadata = {} + + resp = await dapr.get_configuration(store_name='configurationstore', keys=keys) + self.assertEqual(len(resp.items), len(keys)) + self.assertIn(keys[0], resp.items) + item = resp.items[keys[0]] + self.assertEqual(item.value, value) + self.assertEqual(item.version, version) + self.assertEqual(item.metadata, metadata) + + resp = await dapr.get_configuration( + store_name='configurationstore', keys=keys, config_metadata=metadata + ) + self.assertEqual(len(resp.items), len(keys)) + self.assertIn(keys[0], resp.items) + item = resp.items[keys[0]] + self.assertEqual(item.value, value) + self.assertEqual(item.version, version) + self.assertEqual(item.metadata, metadata) + + async def test_subscribe_configuration(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + def mock_watch(self, stub, store_name, keys, handler, config_metadata): + handler( + 'id', + ConfigurationResponse( + items={'k': ConfigurationItem(value='test', version='1.7.0')} + ), + ) + return 'id' + + def handler(id: str, resp: ConfigurationResponse): + self.assertEqual(resp.items['k'].value, 'test') + self.assertEqual(resp.items['k'].version, '1.7.0') + + with patch.object(ConfigurationWatcher, 'watch_configuration', mock_watch): + await dapr.subscribe_configuration( + store_name='configurationstore', keys=['k'], handler=handler + ) + + async def test_unsubscribe_configuration(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + res = await dapr.unsubscribe_configuration(store_name='configurationstore', id='k') + self.assertTrue(res) + + async def test_query_state(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + resp = await dapr.query_state( + store_name='statestore', + query=json.dumps({'filter': {}, 'page': {'limit': 2}}), + ) + self.assertEqual(resp.results[0].key, '1') + self.assertEqual(len(resp.results), 2) + + resp = await dapr.query_state( + store_name='statestore', + query=json.dumps({'filter': {}, 'page': {'limit': 3, 'token': '3'}}), + ) + self.assertEqual(resp.results[0].key, '3') + self.assertEqual(len(resp.results), 3) + + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='my invalid argument message') + ) + with self.assertRaises(DaprGrpcError): + await dapr.query_state( + store_name='statestore', + query=json.dumps({'filter': {}, 'page': {'limit': 2}}), + ) + + async def test_shutdown(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + await dapr.shutdown() + self.assertTrue(self._fake_dapr_server.shutdown_received) + + async def test_wait_ok(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + await dapr.wait(0.1) + + async def test_wait_timeout(self): + # First, pick an unused port + port = 0 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + port = s.getsockname()[1] + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{port}') + with self.assertRaises(Exception) as context: + await dapr.wait(0.1) + self.assertTrue('Connection refused' in str(context.exception)) + + async def test_lock_acquire_success(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + lock_owner = str(uuid.uuid4()) + expiry_in_seconds = 60 + + success = await dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) + self.assertTrue(success) + unlock_response = await dapr.unlock(store_name, resource_id, lock_owner) + self.assertEqual(UnlockResponseStatus.success, unlock_response.status) + + async def test_lock_release_twice_fails(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + lock_owner = str(uuid.uuid4()) + expiry_in_seconds = 60 + + success = await dapr.try_lock(store_name, resource_id, lock_owner, expiry_in_seconds) + self.assertTrue(success) + unlock_response = await dapr.unlock(store_name, resource_id, lock_owner) + self.assertEqual(UnlockResponseStatus.success, unlock_response.status) + # If client tries again it will discover the lock is gone + unlock_response = await dapr.unlock(store_name, resource_id, lock_owner) + self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) + + async def test_lock_conflict(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + first_client_id = str(uuid.uuid4()) + second_client_id = str(uuid.uuid4()) + expiry_in_seconds = 60 + + # First client succeeds + success = await dapr.try_lock(store_name, resource_id, first_client_id, expiry_in_seconds) + self.assertTrue(success) + # Second client tries and fails - resource already acquired + success = await dapr.try_lock(store_name, resource_id, second_client_id, expiry_in_seconds) + self.assertFalse(success) + # Second client is a sneaky fellow and tries to release a lock it doesn't own + unlock_response = await dapr.unlock(store_name, resource_id, second_client_id) + self.assertEqual(UnlockResponseStatus.lock_belongs_to_others, unlock_response.status) + # First client can stil return the lock as rightful owner + unlock_response = await dapr.unlock(store_name, resource_id, first_client_id) + self.assertEqual(UnlockResponseStatus.success, unlock_response.status) + + async def test_lock_not_previously_acquired(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + unlock_response = await dapr.unlock( + store_name='lockstore', resource_id=str(uuid.uuid4()), lock_owner=str(uuid.uuid4()) + ) + self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) + + async def test_lock_release_twice_fails_with_context_manager(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + first_client_id = str(uuid.uuid4()) + second_client_id = str(uuid.uuid4()) + expiry = 60 + + async with await dapr.try_lock( + store_name, resource_id, first_client_id, expiry + ) as first_lock: + self.assertTrue(first_lock.success) + # If another client tries to acquire the same lock it will fail + async with await dapr.try_lock( + store_name, resource_id, second_client_id, expiry + ) as second_lock: + self.assertFalse(second_lock.success) + # At this point lock was auto-released + # If client tries again it will discover the lock is gone + unlock_response = await dapr.unlock(store_name, resource_id, first_client_id) + self.assertEqual(UnlockResponseStatus.lock_does_not_exist, unlock_response.status) + + async def test_lock_are_not_reentrant(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Lock parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + client_id = str(uuid.uuid4()) + expiry_in_s = 60 + + async with await dapr.try_lock( + store_name, resource_id, client_id, expiry_in_s + ) as first_attempt: + self.assertTrue(first_attempt.success) + # If the same client tries to acquire the same lock again it will fail. + async with await dapr.try_lock( + store_name, resource_id, client_id, expiry_in_s + ) as second_attempt: + self.assertFalse(second_attempt.success) + + async def test_lock_input_validation(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Sane parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + client_id = str(uuid.uuid4()) + expiry_in_s = 60 + # Invalid inputs for string arguments + for invalid_input in [None, '', ' ']: + # store_name + with self.assertRaises(ValueError): + async with await dapr.try_lock( + invalid_input, resource_id, client_id, expiry_in_s + ) as res: + self.assertTrue(res.success) + # resource_id + with self.assertRaises(ValueError): + async with await dapr.try_lock( + store_name, invalid_input, client_id, expiry_in_s + ) as res: + self.assertTrue(res.success) + # client_id + with self.assertRaises(ValueError): + async with await dapr.try_lock( + store_name, resource_id, invalid_input, expiry_in_s + ) as res: + self.assertTrue(res.success) + # Invalid inputs for expiry_in_s + for invalid_input in [None, -1, 0]: + with self.assertRaises(ValueError): + async with await dapr.try_lock( + store_name, resource_id, client_id, invalid_input + ) as res: + self.assertTrue(res.success) + + async def test_unlock_input_validation(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + # Sane parameters + store_name = 'lockstore' + resource_id = str(uuid.uuid4()) + client_id = str(uuid.uuid4()) + # Invalid inputs for string arguments + for invalid_input in [None, '', ' ']: + # store_name + with self.assertRaises(ValueError): + await dapr.unlock(invalid_input, resource_id, client_id) + # resource_id + with self.assertRaises(ValueError): + await dapr.unlock(store_name, invalid_input, client_id) + # client_id + with self.assertRaises(ValueError): + await dapr.unlock(store_name, resource_id, invalid_input) + + # + # Tests for Metadata API + # + + async def test_get_metadata(self): + async with DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') as dapr: + response = await dapr.get_metadata() + + self.assertIsNotNone(response) + + self.assertEqual(response.application_id, 'myapp') + + actors = response.active_actors_count + self.assertIsNotNone(actors) + self.assertTrue(len(actors) > 0) + for actorType, count in actors.items(): + # Assert both are non-null and non-empty/zero + self.assertTrue(actorType) + self.assertTrue(count) + + self.assertIsNotNone(response.registered_components) + self.assertTrue(len(response.registered_components) > 0) + components = {c.name: c for c in response.registered_components} + # common tests for all components + for c in components.values(): + self.assertTrue(c.name) + self.assertTrue(c.type) + self.assertIsNotNone(c.version) + self.assertIsNotNone(c.capabilities) + self.assertTrue('ETAG' in components['statestore'].capabilities) + + self.assertIsNotNone(response.extended_metadata) + + async def test_set_metadata(self): + metadata_key = 'test_set_metadata_attempt' + async with DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') as dapr: + for metadata_value in [str(i) for i in range(10)]: + await dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) + response = await dapr.get_metadata() + self.assertIsNotNone(response) + self.assertIsNotNone(response.extended_metadata) + self.assertEqual(response.extended_metadata[metadata_key], metadata_value) + # Empty string and blank strings should be accepted just fine + # by this API + for metadata_value in ['', ' ']: + await dapr.set_metadata(attributeName=metadata_key, attributeValue=metadata_value) + response = await dapr.get_metadata() + self.assertIsNotNone(response) + self.assertIsNotNone(response.extended_metadata) + self.assertEqual(response.extended_metadata[metadata_key], metadata_value) + + async def test_set_metadata_input_validation(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + valid_attr_name = 'attribute name' + valid_attr_value = 'attribute value' + # Invalid inputs for string arguments + async with DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') as dapr: + for invalid_attr_name in [None, '', ' ']: + with self.assertRaises(ValueError): + await dapr.set_metadata(invalid_attr_name, valid_attr_value) + # We are less strict with attribute values - we just cannot accept None + for invalid_attr_value in [None]: + with self.assertRaises(ValueError): + await dapr.set_metadata(valid_attr_name, invalid_attr_value) + + # + # Tests for Cryptography API + # + + async def test_encrypt_empty_component_name(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = EncryptOptions( + component_name='', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + await dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertIn('component_name', str(err)) + + async def test_encrypt_empty_key_name(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = EncryptOptions( + component_name='crypto_component', + key_name='', + key_wrap_algorithm='RSA', + ) + await dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertIn('key_name', str(err)) + + async def test_encrypt_empty_key_wrap_algorithm(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='', + ) + await dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertIn('key_wrap_algorithm', str(err)) + + async def test_encrypt_string_data_read_all(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = await dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertEqual(await resp.read(), b'HELLO DAPR') + + async def test_encrypt_string_data_read_chunks(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = await dapr.encrypt( + data='hello dapr', + options=options, + ) + self.assertEqual(await resp.read(5), b'HELLO') + self.assertEqual(await resp.read(5), b' DAPR') + + async def test_encrypt_file_data_read_all(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'hello dapr') + temp_file.seek(0) + + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = await dapr.encrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(await resp.read(), b'HELLO DAPR') + + async def test_encrypt_file_data_read_chunks(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'hello dapr') + temp_file.seek(0) + + options = EncryptOptions( + component_name='crypto_component', + key_name='crypto_key', + key_wrap_algorithm='RSA', + ) + resp = await dapr.encrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(await resp.read(5), b'HELLO') + self.assertEqual(await resp.read(5), b' DAPR') + + async def test_decrypt_empty_component_name(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with self.assertRaises(ValueError) as err: + options = DecryptOptions( + component_name='', + ) + await dapr.decrypt( + data='HELLO DAPR', + options=options, + ) + self.assertIn('component_name', str(err)) + + async def test_decrypt_string_data_read_all(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + options = DecryptOptions( + component_name='crypto_component', + ) + resp = await dapr.decrypt( + data='HELLO DAPR', + options=options, + ) + self.assertEqual(await resp.read(), b'hello dapr') + + async def test_decrypt_string_data_read_chunks(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + options = DecryptOptions( + component_name='crypto_component', + ) + resp = await dapr.decrypt( + data='HELLO DAPR', + options=options, + ) + self.assertEqual(await resp.read(5), b'hello') + self.assertEqual(await resp.read(5), b' dapr') + + async def test_decrypt_file_data_read_all(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'HELLO DAPR') + temp_file.seek(0) + + options = DecryptOptions( + component_name='crypto_component', + ) + resp = await dapr.decrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(await resp.read(), b'hello dapr') + + async def test_decrypt_file_data_read_chunks(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + with tempfile.TemporaryFile(mode='w+b') as temp_file: + temp_file.write(b'HELLO DAPR') + temp_file.seek(0) + + options = DecryptOptions( + component_name='crypto_component', + ) + resp = await dapr.decrypt( + data=temp_file.read(), + options=options, + ) + self.assertEqual(await resp.read(5), b'hello') + self.assertEqual(await resp.read(5), b' dapr') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_client_async_secure.py b/tests/clients/test_dapr_grpc_client_async_secure.py index 652feac20..9ebe09573 100644 --- a/tests/clients/test_dapr_grpc_client_async_secure.py +++ b/tests/clients/test_dapr_grpc_client_async_secure.py @@ -1,76 +1,76 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from unittest.mock import patch - -from dapr.aio.clients.grpc.client import DaprGrpcClientAsync -from dapr.clients.health import DaprHealth -from tests.clients.certs import replacement_get_credentials_func, replacement_get_health_context -from tests.clients.test_dapr_grpc_client_async import DaprGrpcClientAsyncTests -from .fake_dapr_server import FakeDaprSidecar -from dapr.conf import settings - - -DaprGrpcClientAsync.get_credentials = replacement_get_credentials_func -DaprHealth.get_ssl_context = replacement_get_health_context - - -class DaprSecureGrpcClientAsyncTests(DaprGrpcClientAsyncTests): - grpc_port = 50001 - http_port = 4443 # The http server is used for health checks only, and doesn't need TLS - scheme = 'https://' - - @classmethod - def setUpClass(cls): - cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) - cls._fake_dapr_server.start_secure() - settings.DAPR_HTTP_PORT = cls.http_port - settings.DAPR_HTTP_ENDPOINT = 'https://127.0.0.1:{}'.format(cls.http_port) - - @classmethod - def tearDownClass(cls): - cls._fake_dapr_server.stop_secure() - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'dns:domain1.com:5000') - def test_init_with_DAPR_GRPC_ENDPOINT(self): - dapr = DaprGrpcClientAsync() - self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'dns:domain1.com:5000') - def test_init_with_DAPR_GRPC_ENDPOINT_and_argument(self): - dapr = DaprGrpcClientAsync('dns:domain2.com:5002') - self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'dns:domain1.com:5000') - @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain2.com') - @patch.object(settings, 'DAPR_GRPC_PORT', '5002') - def test_init_with_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): - dapr = DaprGrpcClientAsync() - self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) - - @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain1.com') - @patch.object(settings, 'DAPR_GRPC_PORT', '5000') - def test_init_with_argument_and_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): - dapr = DaprGrpcClientAsync('dns:domain2.com:5002') - self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) - - async def test_dapr_api_token_insertion(self): - pass - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from unittest.mock import patch + +from dapr.aio.clients.grpc.client import DaprGrpcClientAsync +from dapr.clients.health import DaprHealth +from tests.clients.certs import replacement_get_credentials_func, replacement_get_health_context +from tests.clients.test_dapr_grpc_client_async import DaprGrpcClientAsyncTests +from .fake_dapr_server import FakeDaprSidecar +from dapr.conf import settings + + +DaprGrpcClientAsync.get_credentials = replacement_get_credentials_func +DaprHealth.get_ssl_context = replacement_get_health_context + + +class DaprSecureGrpcClientAsyncTests(DaprGrpcClientAsyncTests): + grpc_port = 50001 + http_port = 4443 # The http server is used for health checks only, and doesn't need TLS + scheme = 'https://' + + @classmethod + def setUpClass(cls): + cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) + cls._fake_dapr_server.start_secure() + settings.DAPR_HTTP_PORT = cls.http_port + settings.DAPR_HTTP_ENDPOINT = 'https://127.0.0.1:{}'.format(cls.http_port) + + @classmethod + def tearDownClass(cls): + cls._fake_dapr_server.stop_secure() + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'dns:domain1.com:5000') + def test_init_with_DAPR_GRPC_ENDPOINT(self): + dapr = DaprGrpcClientAsync() + self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'dns:domain1.com:5000') + def test_init_with_DAPR_GRPC_ENDPOINT_and_argument(self): + dapr = DaprGrpcClientAsync('dns:domain2.com:5002') + self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'dns:domain1.com:5000') + @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain2.com') + @patch.object(settings, 'DAPR_GRPC_PORT', '5002') + def test_init_with_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): + dapr = DaprGrpcClientAsync() + self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) + + @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain1.com') + @patch.object(settings, 'DAPR_GRPC_PORT', '5000') + def test_init_with_argument_and_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): + dapr = DaprGrpcClientAsync('dns:domain2.com:5002') + self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) + + async def test_dapr_api_token_insertion(self): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_client_secure.py b/tests/clients/test_dapr_grpc_client_secure.py index 41dedca1a..4f3255828 100644 --- a/tests/clients/test_dapr_grpc_client_secure.py +++ b/tests/clients/test_dapr_grpc_client_secure.py @@ -1,71 +1,71 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import unittest -from unittest.mock import patch - -from dapr.clients.grpc.client import DaprGrpcClient -from dapr.clients.health import DaprHealth -from dapr.conf import settings -from tests.clients.certs import replacement_get_credentials_func, replacement_get_health_context - -from tests.clients.test_dapr_grpc_client import DaprGrpcClientTests -from .fake_dapr_server import FakeDaprSidecar - - -class DaprSecureGrpcClientTests(DaprGrpcClientTests): - grpc_port = 50001 - http_port = 4443 # The http server is used for health checks only, and doesn't need TLS - scheme = 'https://' - - DaprGrpcClient.get_credentials = replacement_get_credentials_func - DaprHealth.get_ssl_context = replacement_get_health_context - - @classmethod - def setUpClass(cls): - cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) - cls._fake_dapr_server.start_secure() - settings.DAPR_HTTP_PORT = cls.http_port - settings.DAPR_HTTP_ENDPOINT = 'https://127.0.0.1:{}'.format(cls.http_port) - - @classmethod - def tearDownClass(cls): - cls._fake_dapr_server.stop_secure() - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') - def test_init_with_DAPR_GRPC_ENDPOINT(self): - dapr = DaprGrpcClient() - self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') - def test_init_with_DAPR_GRPC_ENDPOINT_and_argument(self): - dapr = DaprGrpcClient('https://domain2.com:5002') - self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) - - @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') - @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain2.com') - @patch.object(settings, 'DAPR_GRPC_PORT', '5002') - def test_init_with_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): - dapr = DaprGrpcClient() - self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) - - @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain1.com') - @patch.object(settings, 'DAPR_GRPC_PORT', '5000') - def test_init_with_argument_and_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): - dapr = DaprGrpcClient('https://domain2.com:5002') - self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import unittest +from unittest.mock import patch + +from dapr.clients.grpc.client import DaprGrpcClient +from dapr.clients.health import DaprHealth +from dapr.conf import settings +from tests.clients.certs import replacement_get_credentials_func, replacement_get_health_context + +from tests.clients.test_dapr_grpc_client import DaprGrpcClientTests +from .fake_dapr_server import FakeDaprSidecar + + +class DaprSecureGrpcClientTests(DaprGrpcClientTests): + grpc_port = 50001 + http_port = 4443 # The http server is used for health checks only, and doesn't need TLS + scheme = 'https://' + + DaprGrpcClient.get_credentials = replacement_get_credentials_func + DaprHealth.get_ssl_context = replacement_get_health_context + + @classmethod + def setUpClass(cls): + cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls.grpc_port, http_port=cls.http_port) + cls._fake_dapr_server.start_secure() + settings.DAPR_HTTP_PORT = cls.http_port + settings.DAPR_HTTP_ENDPOINT = 'https://127.0.0.1:{}'.format(cls.http_port) + + @classmethod + def tearDownClass(cls): + cls._fake_dapr_server.stop_secure() + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') + def test_init_with_DAPR_GRPC_ENDPOINT(self): + dapr = DaprGrpcClient() + self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') + def test_init_with_DAPR_GRPC_ENDPOINT_and_argument(self): + dapr = DaprGrpcClient('https://domain2.com:5002') + self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) + + @patch.object(settings, 'DAPR_GRPC_ENDPOINT', 'https://domain1.com:5000') + @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain2.com') + @patch.object(settings, 'DAPR_GRPC_PORT', '5002') + def test_init_with_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): + dapr = DaprGrpcClient() + self.assertEqual('dns:domain1.com:5000', dapr._uri.endpoint) + + @patch.object(settings, 'DAPR_RUNTIME_HOST', 'domain1.com') + @patch.object(settings, 'DAPR_GRPC_PORT', '5000') + def test_init_with_argument_and_DAPR_GRPC_ENDPOINT_and_DAPR_RUNTIME_HOST(self): + dapr = DaprGrpcClient('https://domain2.com:5002') + self.assertEqual('dns:domain2.com:5002', dapr._uri.endpoint) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_request.py b/tests/clients/test_dapr_grpc_request.py index 98d8e2005..393966ef4 100644 --- a/tests/clients/test_dapr_grpc_request.py +++ b/tests/clients/test_dapr_grpc_request.py @@ -1,208 +1,208 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import io -import unittest - -from dapr.clients.grpc._request import ( - InvokeMethodRequest, - BindingRequest, - EncryptRequestIterator, - DecryptRequestIterator, -) -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.proto import api_v1, common_v1 - - -class InvokeMethodRequestTests(unittest.TestCase): - def test_bytes_data(self): - # act - req = InvokeMethodRequest(data=b'hello dapr') - - # arrange - self.assertEqual(b'hello dapr', req.data) - self.assertEqual('application/json; charset=utf-8', req.content_type) - - def test_proto_message_data(self): - # arrange - fake_req = common_v1.InvokeRequest(method='test') - - # act - req = InvokeMethodRequest(data=fake_req) - - # assert - self.assertIsNotNone(req.proto) - self.assertEqual( - 'type.googleapis.com/dapr.proto.common.v1.InvokeRequest', req.proto.type_url - ) - self.assertIsNotNone(req.proto.value) - self.assertIsNone(req.content_type) - - def test_invalid_data(self): - with self.assertRaises(ValueError): - data = InvokeMethodRequest(data=123) - self.assertIsNone(data, 'This should not be reached.') - - -class InvokeBindingRequestDataTests(unittest.TestCase): - def test_bytes_data(self): - # act - data = BindingRequest(data=b'hello dapr') - - # arrange - self.assertEqual(b'hello dapr', data.data) - self.assertEqual({}, data.metadata) - - def test_str_data(self): - # act - data = BindingRequest(data='hello dapr') - - # arrange - self.assertEqual(b'hello dapr', data.data) - self.assertEqual({}, data.metadata) - - def test_non_empty_metadata(self): - # act - data = BindingRequest(data='hello dapr', binding_metadata={'ttlInSeconds': '1000'}) - - # arrange - self.assertEqual(b'hello dapr', data.data) - self.assertEqual({'ttlInSeconds': '1000'}, data.binding_metadata) - - -class EncryptRequestIteratorTests(unittest.TestCase): - def test_encrypt_request_iterator(self): - # arrange - encrypt_options = EncryptOptions( - component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' - ) - - # act - req_iter = EncryptRequestIterator( - data='hello dapr', - options=encrypt_options, - ) - req = next(req_iter) - - # assert - self.assertEqual(req.__class__, api_v1.EncryptRequest) - self.assertEqual(req.payload.data, b'hello dapr') - self.assertEqual(req.payload.seq, 0) - with self.assertRaises(StopIteration): - next(req_iter) - - def test_encrypt_request_iterator_empty_data(self): - # arrange - encrypt_options = EncryptOptions( - component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' - ) - - # act - req_iter = EncryptRequestIterator( - data='', - options=encrypt_options, - ) - - # assert - with self.assertRaises(StopIteration): - next(req_iter) - - def test_encrypt_request_iterator_large_data(self): - # arrange - buffer = io.BytesIO() - for _ in range(100): - buffer.write(b'a' * 2048) - - encrypt_options = EncryptOptions( - component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' - ) - - # act - req_iter = EncryptRequestIterator( - data=buffer.read(), - options=encrypt_options, - ) - - # assert - for seq, req in enumerate(req_iter): - self.assertEqual(req.__class__, api_v1.EncryptRequest) - self.assertEqual(req.payload.data, b'a') - self.assertEqual(req.payload.seq, seq) - with self.assertRaises(StopIteration): - next(req_iter) - - -class DecryptRequestIteratorTests(unittest.TestCase): - def test_decrypt_request_iterator(self): - # arrange - decrypt_options = DecryptOptions( - component_name='crypto_component', - ) - - # act - req_iter = DecryptRequestIterator( - data='hello dapr', - options=decrypt_options, - ) - req = next(req_iter) - - # assert - self.assertEqual(req.__class__, api_v1.DecryptRequest) - self.assertEqual(req.payload.data, b'hello dapr') - self.assertEqual(req.payload.seq, 0) - with self.assertRaises(StopIteration): - next(req_iter) - - def test_decrypt_request_iterator_empty_data(self): - # arrange - decrypt_options = DecryptOptions( - component_name='crypto_component', - ) - - # act - req_iter = DecryptRequestIterator( - data='', - options=decrypt_options, - ) - - # assert - with self.assertRaises(StopIteration): - next(req_iter) - - def test_decrypt_request_iterator_large_data(self): - # arrange - buffer = io.BytesIO() - for _ in range(100): - buffer.write(b'a' * 2048) - - encrypt_options = DecryptOptions(component_name='crypto_component') - - # act - req_iter = DecryptRequestIterator( - data=buffer.read(), - options=encrypt_options, - ) - - # assert - for seq, req in enumerate(req_iter): - self.assertEqual(req.__class__, api_v1.DecryptRequest) - self.assertEqual(req.payload.data, b'a') - self.assertEqual(req.payload.seq, seq) - with self.assertRaises(StopIteration): - next(req_iter) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import io +import unittest + +from dapr.clients.grpc._request import ( + InvokeMethodRequest, + BindingRequest, + EncryptRequestIterator, + DecryptRequestIterator, +) +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.proto import api_v1, common_v1 + + +class InvokeMethodRequestTests(unittest.TestCase): + def test_bytes_data(self): + # act + req = InvokeMethodRequest(data=b'hello dapr') + + # arrange + self.assertEqual(b'hello dapr', req.data) + self.assertEqual('application/json; charset=utf-8', req.content_type) + + def test_proto_message_data(self): + # arrange + fake_req = common_v1.InvokeRequest(method='test') + + # act + req = InvokeMethodRequest(data=fake_req) + + # assert + self.assertIsNotNone(req.proto) + self.assertEqual( + 'type.googleapis.com/dapr.proto.common.v1.InvokeRequest', req.proto.type_url + ) + self.assertIsNotNone(req.proto.value) + self.assertIsNone(req.content_type) + + def test_invalid_data(self): + with self.assertRaises(ValueError): + data = InvokeMethodRequest(data=123) + self.assertIsNone(data, 'This should not be reached.') + + +class InvokeBindingRequestDataTests(unittest.TestCase): + def test_bytes_data(self): + # act + data = BindingRequest(data=b'hello dapr') + + # arrange + self.assertEqual(b'hello dapr', data.data) + self.assertEqual({}, data.metadata) + + def test_str_data(self): + # act + data = BindingRequest(data='hello dapr') + + # arrange + self.assertEqual(b'hello dapr', data.data) + self.assertEqual({}, data.metadata) + + def test_non_empty_metadata(self): + # act + data = BindingRequest(data='hello dapr', binding_metadata={'ttlInSeconds': '1000'}) + + # arrange + self.assertEqual(b'hello dapr', data.data) + self.assertEqual({'ttlInSeconds': '1000'}, data.binding_metadata) + + +class EncryptRequestIteratorTests(unittest.TestCase): + def test_encrypt_request_iterator(self): + # arrange + encrypt_options = EncryptOptions( + component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' + ) + + # act + req_iter = EncryptRequestIterator( + data='hello dapr', + options=encrypt_options, + ) + req = next(req_iter) + + # assert + self.assertEqual(req.__class__, api_v1.EncryptRequest) + self.assertEqual(req.payload.data, b'hello dapr') + self.assertEqual(req.payload.seq, 0) + with self.assertRaises(StopIteration): + next(req_iter) + + def test_encrypt_request_iterator_empty_data(self): + # arrange + encrypt_options = EncryptOptions( + component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' + ) + + # act + req_iter = EncryptRequestIterator( + data='', + options=encrypt_options, + ) + + # assert + with self.assertRaises(StopIteration): + next(req_iter) + + def test_encrypt_request_iterator_large_data(self): + # arrange + buffer = io.BytesIO() + for _ in range(100): + buffer.write(b'a' * 2048) + + encrypt_options = EncryptOptions( + component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' + ) + + # act + req_iter = EncryptRequestIterator( + data=buffer.read(), + options=encrypt_options, + ) + + # assert + for seq, req in enumerate(req_iter): + self.assertEqual(req.__class__, api_v1.EncryptRequest) + self.assertEqual(req.payload.data, b'a') + self.assertEqual(req.payload.seq, seq) + with self.assertRaises(StopIteration): + next(req_iter) + + +class DecryptRequestIteratorTests(unittest.TestCase): + def test_decrypt_request_iterator(self): + # arrange + decrypt_options = DecryptOptions( + component_name='crypto_component', + ) + + # act + req_iter = DecryptRequestIterator( + data='hello dapr', + options=decrypt_options, + ) + req = next(req_iter) + + # assert + self.assertEqual(req.__class__, api_v1.DecryptRequest) + self.assertEqual(req.payload.data, b'hello dapr') + self.assertEqual(req.payload.seq, 0) + with self.assertRaises(StopIteration): + next(req_iter) + + def test_decrypt_request_iterator_empty_data(self): + # arrange + decrypt_options = DecryptOptions( + component_name='crypto_component', + ) + + # act + req_iter = DecryptRequestIterator( + data='', + options=decrypt_options, + ) + + # assert + with self.assertRaises(StopIteration): + next(req_iter) + + def test_decrypt_request_iterator_large_data(self): + # arrange + buffer = io.BytesIO() + for _ in range(100): + buffer.write(b'a' * 2048) + + encrypt_options = DecryptOptions(component_name='crypto_component') + + # act + req_iter = DecryptRequestIterator( + data=buffer.read(), + options=encrypt_options, + ) + + # assert + for seq, req in enumerate(req_iter): + self.assertEqual(req.__class__, api_v1.DecryptRequest) + self.assertEqual(req.payload.data, b'a') + self.assertEqual(req.payload.seq, seq) + with self.assertRaises(StopIteration): + next(req_iter) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_request_async.py b/tests/clients/test_dapr_grpc_request_async.py index 75fe74fce..bfee86898 100644 --- a/tests/clients/test_dapr_grpc_request_async.py +++ b/tests/clients/test_dapr_grpc_request_async.py @@ -1,149 +1,149 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import io -import unittest - -from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions -from dapr.aio.clients.grpc._request import EncryptRequestIterator, DecryptRequestIterator -from dapr.proto import api_v1 - - -class EncryptRequestIteratorAsyncTests(unittest.IsolatedAsyncioTestCase): - async def test_encrypt_request_iterator(self): - # arrange - encrypt_options = EncryptOptions( - component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' - ) - - # act - req_iter = EncryptRequestIterator( - data='hello dapr', - options=encrypt_options, - ) - req = await req_iter.__anext__() - - # assert - self.assertEqual(req.__class__, api_v1.EncryptRequest) - self.assertEqual(req.payload.data, b'hello dapr') - self.assertEqual(req.payload.seq, 0) - with self.assertRaises(StopAsyncIteration): - await req_iter.__anext__() - - async def test_encrypt_request_iterator_empty_data(self): - # arrange - encrypt_options = EncryptOptions( - component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' - ) - - # act - req_iter = EncryptRequestIterator( - data='', - options=encrypt_options, - ) - - # assert - with self.assertRaises(StopAsyncIteration): - await req_iter.__anext__() - - async def test_encrypt_request_iterator_large_data(self): - # arrange - buffer = io.BytesIO() - for _ in range(100): - buffer.write(b'a' * 2048) - - encrypt_options = EncryptOptions( - component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' - ) - - # act - req_iter = EncryptRequestIterator( - data=buffer.read(), - options=encrypt_options, - ) - - # assert - for seq, req in enumerate([req async for req in req_iter]): - self.assertEqual(req.__class__, api_v1.EncryptRequest) - self.assertEqual(req.payload.data, b'a') - self.assertEqual(req.payload.seq, seq) - with self.assertRaises(StopAsyncIteration): - await req_iter.__anext__() - - -class DecryptRequestIteratorAsyncTests(unittest.IsolatedAsyncioTestCase): - async def test_decrypt_request_iterator(self): - # arrange - decrypt_options = DecryptOptions( - component_name='crypto_component', - ) - - # act - req_iter = DecryptRequestIterator( - data='hello dapr', - options=decrypt_options, - ) - req = await req_iter.__anext__() - - # assert - self.assertEqual(req.__class__, api_v1.DecryptRequest) - self.assertEqual(req.payload.data, b'hello dapr') - self.assertEqual(req.payload.seq, 0) - with self.assertRaises(StopAsyncIteration): - await req_iter.__anext__() - - async def test_decrypt_request_iterator_empty_data(self): - # arrange - decrypt_options = DecryptOptions( - component_name='crypto_component', - ) - - # act - req_iter = DecryptRequestIterator( - data='', - options=decrypt_options, - ) - - # assert - with self.assertRaises(StopAsyncIteration): - await req_iter.__anext__() - - async def test_decrypt_request_iterator_large_data(self): - # arrange - buffer = io.BytesIO() - for _ in range(100): - buffer.write(b'a' * 2048) - - decrypt_options = DecryptOptions( - component_name='crypto_component', - ) - - # act - req_iter = DecryptRequestIterator( - data=buffer.read(), - options=decrypt_options, - ) - - # assert - for seq, req in enumerate([req async for req in req_iter]): - self.assertEqual(req.__class__, api_v1.EncryptRequest) - self.assertEqual(req.payload.data, b'a') - self.assertEqual(req.payload.seq, seq) - with self.assertRaises(StopAsyncIteration): - await req_iter.__anext__() - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import io +import unittest + +from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions +from dapr.aio.clients.grpc._request import EncryptRequestIterator, DecryptRequestIterator +from dapr.proto import api_v1 + + +class EncryptRequestIteratorAsyncTests(unittest.IsolatedAsyncioTestCase): + async def test_encrypt_request_iterator(self): + # arrange + encrypt_options = EncryptOptions( + component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' + ) + + # act + req_iter = EncryptRequestIterator( + data='hello dapr', + options=encrypt_options, + ) + req = await req_iter.__anext__() + + # assert + self.assertEqual(req.__class__, api_v1.EncryptRequest) + self.assertEqual(req.payload.data, b'hello dapr') + self.assertEqual(req.payload.seq, 0) + with self.assertRaises(StopAsyncIteration): + await req_iter.__anext__() + + async def test_encrypt_request_iterator_empty_data(self): + # arrange + encrypt_options = EncryptOptions( + component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' + ) + + # act + req_iter = EncryptRequestIterator( + data='', + options=encrypt_options, + ) + + # assert + with self.assertRaises(StopAsyncIteration): + await req_iter.__anext__() + + async def test_encrypt_request_iterator_large_data(self): + # arrange + buffer = io.BytesIO() + for _ in range(100): + buffer.write(b'a' * 2048) + + encrypt_options = EncryptOptions( + component_name='crypto_component', key_name='crypto_key', key_wrap_algorithm='RSA' + ) + + # act + req_iter = EncryptRequestIterator( + data=buffer.read(), + options=encrypt_options, + ) + + # assert + for seq, req in enumerate([req async for req in req_iter]): + self.assertEqual(req.__class__, api_v1.EncryptRequest) + self.assertEqual(req.payload.data, b'a') + self.assertEqual(req.payload.seq, seq) + with self.assertRaises(StopAsyncIteration): + await req_iter.__anext__() + + +class DecryptRequestIteratorAsyncTests(unittest.IsolatedAsyncioTestCase): + async def test_decrypt_request_iterator(self): + # arrange + decrypt_options = DecryptOptions( + component_name='crypto_component', + ) + + # act + req_iter = DecryptRequestIterator( + data='hello dapr', + options=decrypt_options, + ) + req = await req_iter.__anext__() + + # assert + self.assertEqual(req.__class__, api_v1.DecryptRequest) + self.assertEqual(req.payload.data, b'hello dapr') + self.assertEqual(req.payload.seq, 0) + with self.assertRaises(StopAsyncIteration): + await req_iter.__anext__() + + async def test_decrypt_request_iterator_empty_data(self): + # arrange + decrypt_options = DecryptOptions( + component_name='crypto_component', + ) + + # act + req_iter = DecryptRequestIterator( + data='', + options=decrypt_options, + ) + + # assert + with self.assertRaises(StopAsyncIteration): + await req_iter.__anext__() + + async def test_decrypt_request_iterator_large_data(self): + # arrange + buffer = io.BytesIO() + for _ in range(100): + buffer.write(b'a' * 2048) + + decrypt_options = DecryptOptions( + component_name='crypto_component', + ) + + # act + req_iter = DecryptRequestIterator( + data=buffer.read(), + options=decrypt_options, + ) + + # assert + for seq, req in enumerate([req async for req in req_iter]): + self.assertEqual(req.__class__, api_v1.EncryptRequest) + self.assertEqual(req.payload.data, b'a') + self.assertEqual(req.payload.seq, seq) + with self.assertRaises(StopAsyncIteration): + await req_iter.__anext__() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_response.py b/tests/clients/test_dapr_grpc_response.py index 1c91805eb..19a9c72c6 100644 --- a/tests/clients/test_dapr_grpc_response.py +++ b/tests/clients/test_dapr_grpc_response.py @@ -1,159 +1,159 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from google.protobuf.any_pb2 import Any as GrpcAny - -from dapr.clients.grpc._response import ( - DaprResponse, - InvokeMethodResponse, - BindingResponse, - StateResponse, - BulkStateItem, - EncryptResponse, - DecryptResponse, -) - -from dapr.proto import api_v1, common_v1 - - -class DaprResponseTests(unittest.TestCase): - test_headers = ( - ('key1', 'value1'), - ('key2', 'value2'), - ('key3', 'value3'), - ) - - def test_convert_metadata(self): - # act - resp = DaprResponse(self.test_headers) - - # assert - self.assertEqual(3, len(resp.headers)) - for k, v in self.test_headers: - self.assertEqual(resp.headers[k], [v]) - - -class InvokeMethodResponseTests(unittest.TestCase): - def test_non_protobuf_message(self): - with self.assertRaises(ValueError): - resp = InvokeMethodResponse(data=123) - self.assertIsNone(resp, 'This should not be reached.') - - def test_is_proto_for_non_protobuf(self): - test_data = GrpcAny(value=b'hello dapr') - resp = InvokeMethodResponse(data=test_data, content_type='application/json') - self.assertFalse(resp.is_proto()) - - def test_is_proto_for_protobuf(self): - fake_req = common_v1.InvokeRequest(method='test') - test_data = GrpcAny() - test_data.Pack(fake_req) - resp = InvokeMethodResponse(data=test_data) - self.assertTrue(resp.is_proto()) - - def test_proto(self): - fake_req = common_v1.InvokeRequest(method='test') - resp = InvokeMethodResponse(data=fake_req) - self.assertIsNotNone(resp.proto) - - def test_data(self): - test_data = GrpcAny(value=b'hello dapr') - resp = InvokeMethodResponse(data=test_data, content_type='application/json') - self.assertEqual(b'hello dapr', resp.data) - self.assertEqual('hello dapr', resp.text()) - self.assertEqual('application/json', resp.content_type) - - def test_json_data(self): - resp = InvokeMethodResponse(data=b'{ "status": "ok" }', content_type='application/json') - self.assertEqual({'status': 'ok'}, resp.json()) - - def test_unpack(self): - # arrange - fake_req = common_v1.InvokeRequest(method='test') - - # act - resp = InvokeMethodResponse(data=fake_req) - resp_proto = common_v1.InvokeRequest() - resp.unpack(resp_proto) - - # assert - self.assertEqual('test', resp_proto.method) - - -class InvokeBindingResponseTests(unittest.TestCase): - def test_bytes_message(self): - resp = BindingResponse(data=b'data', binding_metadata={}) - self.assertEqual({}, resp.binding_metadata) - self.assertEqual(b'data', resp.data) - self.assertEqual('data', resp.text()) - - def test_json_data(self): - resp = BindingResponse(data=b'{"status": "ok"}', binding_metadata={}) - self.assertEqual({'status': 'ok'}, resp.json()) - - def test_metadata(self): - resp = BindingResponse(data=b'data', binding_metadata={'status': 'ok'}) - self.assertEqual({'status': 'ok'}, resp.binding_metadata) - self.assertEqual(b'data', resp.data) - self.assertEqual('data', resp.text()) - - -class StateResponseTests(unittest.TestCase): - def test_data(self): - resp = StateResponse(data=b'hello dapr') - self.assertEqual('hello dapr', resp.text()) - self.assertEqual(b'hello dapr', resp.data) - - def test_json_data(self): - resp = StateResponse(data=b'{"status": "ok"}') - self.assertEqual({'status': 'ok'}, resp.json()) - - -class BulkStateItemTests(unittest.TestCase): - def test_data(self): - item = BulkStateItem(key='item1', data=b'{ "status": "ok" }') - self.assertEqual({'status': 'ok'}, item.json()) - - -class CryptoResponseTests(unittest.TestCase): - def response_stream(self): - stream1 = common_v1.StreamPayload(data=b'hello', seq=0) - stream2 = common_v1.StreamPayload(data=b' dapr', seq=1) - for strm in (stream1, stream2): - yield api_v1.EncryptResponse(payload=strm) - - def test_encrypt_response_read_bytes(self): - resp = EncryptResponse(stream=self.response_stream()) - self.assertEqual(resp.read(5), b'hello') - self.assertEqual(resp.read(5), b' dapr') - - def test_encrypt_response_read_all(self): - resp = EncryptResponse(stream=self.response_stream()) - self.assertEqual(resp.read(), b'hello dapr') - - def test_decrypt_response_read_bytes(self): - resp = DecryptResponse(stream=self.response_stream()) - self.assertEqual(resp.read(5), b'hello') - self.assertEqual(resp.read(5), b' dapr') - - def test_decrypt_response_read_all(self): - resp = DecryptResponse(stream=self.response_stream()) - self.assertEqual(resp.read(), b'hello dapr') - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from google.protobuf.any_pb2 import Any as GrpcAny + +from dapr.clients.grpc._response import ( + DaprResponse, + InvokeMethodResponse, + BindingResponse, + StateResponse, + BulkStateItem, + EncryptResponse, + DecryptResponse, +) + +from dapr.proto import api_v1, common_v1 + + +class DaprResponseTests(unittest.TestCase): + test_headers = ( + ('key1', 'value1'), + ('key2', 'value2'), + ('key3', 'value3'), + ) + + def test_convert_metadata(self): + # act + resp = DaprResponse(self.test_headers) + + # assert + self.assertEqual(3, len(resp.headers)) + for k, v in self.test_headers: + self.assertEqual(resp.headers[k], [v]) + + +class InvokeMethodResponseTests(unittest.TestCase): + def test_non_protobuf_message(self): + with self.assertRaises(ValueError): + resp = InvokeMethodResponse(data=123) + self.assertIsNone(resp, 'This should not be reached.') + + def test_is_proto_for_non_protobuf(self): + test_data = GrpcAny(value=b'hello dapr') + resp = InvokeMethodResponse(data=test_data, content_type='application/json') + self.assertFalse(resp.is_proto()) + + def test_is_proto_for_protobuf(self): + fake_req = common_v1.InvokeRequest(method='test') + test_data = GrpcAny() + test_data.Pack(fake_req) + resp = InvokeMethodResponse(data=test_data) + self.assertTrue(resp.is_proto()) + + def test_proto(self): + fake_req = common_v1.InvokeRequest(method='test') + resp = InvokeMethodResponse(data=fake_req) + self.assertIsNotNone(resp.proto) + + def test_data(self): + test_data = GrpcAny(value=b'hello dapr') + resp = InvokeMethodResponse(data=test_data, content_type='application/json') + self.assertEqual(b'hello dapr', resp.data) + self.assertEqual('hello dapr', resp.text()) + self.assertEqual('application/json', resp.content_type) + + def test_json_data(self): + resp = InvokeMethodResponse(data=b'{ "status": "ok" }', content_type='application/json') + self.assertEqual({'status': 'ok'}, resp.json()) + + def test_unpack(self): + # arrange + fake_req = common_v1.InvokeRequest(method='test') + + # act + resp = InvokeMethodResponse(data=fake_req) + resp_proto = common_v1.InvokeRequest() + resp.unpack(resp_proto) + + # assert + self.assertEqual('test', resp_proto.method) + + +class InvokeBindingResponseTests(unittest.TestCase): + def test_bytes_message(self): + resp = BindingResponse(data=b'data', binding_metadata={}) + self.assertEqual({}, resp.binding_metadata) + self.assertEqual(b'data', resp.data) + self.assertEqual('data', resp.text()) + + def test_json_data(self): + resp = BindingResponse(data=b'{"status": "ok"}', binding_metadata={}) + self.assertEqual({'status': 'ok'}, resp.json()) + + def test_metadata(self): + resp = BindingResponse(data=b'data', binding_metadata={'status': 'ok'}) + self.assertEqual({'status': 'ok'}, resp.binding_metadata) + self.assertEqual(b'data', resp.data) + self.assertEqual('data', resp.text()) + + +class StateResponseTests(unittest.TestCase): + def test_data(self): + resp = StateResponse(data=b'hello dapr') + self.assertEqual('hello dapr', resp.text()) + self.assertEqual(b'hello dapr', resp.data) + + def test_json_data(self): + resp = StateResponse(data=b'{"status": "ok"}') + self.assertEqual({'status': 'ok'}, resp.json()) + + +class BulkStateItemTests(unittest.TestCase): + def test_data(self): + item = BulkStateItem(key='item1', data=b'{ "status": "ok" }') + self.assertEqual({'status': 'ok'}, item.json()) + + +class CryptoResponseTests(unittest.TestCase): + def response_stream(self): + stream1 = common_v1.StreamPayload(data=b'hello', seq=0) + stream2 = common_v1.StreamPayload(data=b' dapr', seq=1) + for strm in (stream1, stream2): + yield api_v1.EncryptResponse(payload=strm) + + def test_encrypt_response_read_bytes(self): + resp = EncryptResponse(stream=self.response_stream()) + self.assertEqual(resp.read(5), b'hello') + self.assertEqual(resp.read(5), b' dapr') + + def test_encrypt_response_read_all(self): + resp = EncryptResponse(stream=self.response_stream()) + self.assertEqual(resp.read(), b'hello dapr') + + def test_decrypt_response_read_bytes(self): + resp = DecryptResponse(stream=self.response_stream()) + self.assertEqual(resp.read(5), b'hello') + self.assertEqual(resp.read(5), b' dapr') + + def test_decrypt_response_read_all(self): + resp = DecryptResponse(stream=self.response_stream()) + self.assertEqual(resp.read(), b'hello dapr') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_dapr_grpc_response_async.py b/tests/clients/test_dapr_grpc_response_async.py index 2626cbf41..803c177af 100644 --- a/tests/clients/test_dapr_grpc_response_async.py +++ b/tests/clients/test_dapr_grpc_response_async.py @@ -1,49 +1,49 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest - -from dapr.aio.clients.grpc._response import EncryptResponse, DecryptResponse -from dapr.proto import api_v1, common_v1 - - -class CryptoResponseAsyncTests(unittest.IsolatedAsyncioTestCase): - async def response_stream(self): - stream1 = common_v1.StreamPayload(data=b'hello', seq=0) - stream2 = common_v1.StreamPayload(data=b' dapr', seq=1) - for strm in (stream1, stream2): - yield api_v1.EncryptResponse(payload=strm) - - async def test_encrypt_response_read_bytes(self): - resp = EncryptResponse(stream=self.response_stream()) - self.assertEqual(await resp.read(5), b'hello') - self.assertEqual(await resp.read(5), b' dapr') - - async def test_encrypt_response_read_all(self): - resp = EncryptResponse(stream=self.response_stream()) - self.assertEqual(await resp.read(), b'hello dapr') - - async def test_decrypt_response_read_bytes(self): - resp = DecryptResponse(stream=self.response_stream()) - self.assertEqual(await resp.read(5), b'hello') - self.assertEqual(await resp.read(5), b' dapr') - - async def test_decrypt_response_read_all(self): - resp = DecryptResponse(stream=self.response_stream()) - self.assertEqual(await resp.read(), b'hello dapr') - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest + +from dapr.aio.clients.grpc._response import EncryptResponse, DecryptResponse +from dapr.proto import api_v1, common_v1 + + +class CryptoResponseAsyncTests(unittest.IsolatedAsyncioTestCase): + async def response_stream(self): + stream1 = common_v1.StreamPayload(data=b'hello', seq=0) + stream2 = common_v1.StreamPayload(data=b' dapr', seq=1) + for strm in (stream1, stream2): + yield api_v1.EncryptResponse(payload=strm) + + async def test_encrypt_response_read_bytes(self): + resp = EncryptResponse(stream=self.response_stream()) + self.assertEqual(await resp.read(5), b'hello') + self.assertEqual(await resp.read(5), b' dapr') + + async def test_encrypt_response_read_all(self): + resp = EncryptResponse(stream=self.response_stream()) + self.assertEqual(await resp.read(), b'hello dapr') + + async def test_decrypt_response_read_bytes(self): + resp = DecryptResponse(stream=self.response_stream()) + self.assertEqual(await resp.read(5), b'hello') + self.assertEqual(await resp.read(5), b' dapr') + + async def test_decrypt_response_read_all(self): + resp = DecryptResponse(stream=self.response_stream()) + self.assertEqual(await resp.read(), b'hello dapr') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/clients/test_exceptions.py b/tests/clients/test_exceptions.py index fb349b091..6efddec17 100644 --- a/tests/clients/test_exceptions.py +++ b/tests/clients/test_exceptions.py @@ -1,218 +1,218 @@ -import unittest - -import grpc -from google.rpc import error_details_pb2, status_pb2, code_pb2 -from google.protobuf.any_pb2 import Any -from google.protobuf.duration_pb2 import Duration - -from dapr.clients import DaprGrpcClient -from dapr.clients.exceptions import DaprGrpcError -from dapr.conf import settings - -from .fake_dapr_server import FakeDaprSidecar - - -def create_expected_status(): - detail1 = Any() - detail1.Pack(error_details_pb2.ErrorInfo(reason='DAPR_ERROR_CODE')) - - detail2 = Any() - detail2.Pack( - error_details_pb2.ResourceInfo( - resource_type='my_resource_type', resource_name='my_resource' - ) - ) - - detail3 = Any() - detail3.Pack( - error_details_pb2.BadRequest( - field_violations=[ - error_details_pb2.BadRequest.FieldViolation( - field='my_field', description='my field violation message' - ) - ] - ) - ) - - detail4 = Any() - help_message = error_details_pb2.Help() - link = error_details_pb2.Help.Link(description='Help Link', url='https://my_help_link') - help_message.links.extend([link]) - detail4.Pack(help_message) - - detail5 = Any() - retry_info = error_details_pb2.RetryInfo(retry_delay=Duration(seconds=5)) - detail5.Pack(retry_info) - - detail6 = Any() - detail6.Pack(error_details_pb2.DebugInfo(stack_entries=['stack_entry_1', 'stack_entry_2'])) - - detail7 = Any() - detail7.Pack(error_details_pb2.LocalizedMessage(locale='en-US', message='my localized message')) - - detail8 = Any() - violation = error_details_pb2.PreconditionFailure.Violation( - type='your_violation_type', - subject='your_violation_subject', - description='your_violation_description', - ) - detail8.Pack(error_details_pb2.PreconditionFailure(violations=[violation])) - - detail9 = Any() - violation = error_details_pb2.QuotaFailure.Violation( - subject='your_violation_subject', description='your_violation_description' - ) - detail9.Pack(error_details_pb2.QuotaFailure(violations=[violation])) - - detail10 = Any() - detail10.Pack( - error_details_pb2.RequestInfo( - request_id='your_request_id', serving_data='your_serving_data' - ) - ) - - return status_pb2.Status( - code=code_pb2.INTERNAL, - message='my invalid argument message', - details=[ - detail1, - detail2, - detail3, - detail4, - detail5, - detail6, - detail7, - detail8, - detail9, - detail10, - ], - ) - - -class DaprExceptionsTestCase(unittest.TestCase): - _grpc_port = 50001 - _http_port = 3500 - - @classmethod - def setUpClass(cls): - cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls._grpc_port, http_port=cls._http_port) - settings.DAPR_HTTP_PORT = cls._http_port - settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(cls._http_port) - cls._fake_dapr_server.start() - cls._expected_status = create_expected_status() - - @classmethod - def tearDownClass(cls): - cls._fake_dapr_server.stop() - - def test_exception_status_parsing(self): - dapr = DaprGrpcClient(f'localhost:{self._grpc_port}') - - self._fake_dapr_server.raise_exception_on_next_call(self._expected_status) - with self.assertRaises(DaprGrpcError) as context: - dapr.get_metadata() - - dapr_error = context.exception - - self.assertEqual(dapr_error.code(), grpc.StatusCode.INTERNAL) - self.assertEqual(dapr_error.details(), 'my invalid argument message') - self.assertEqual(dapr_error.error_code(), 'DAPR_ERROR_CODE') - - self.assertIsNotNone(dapr_error._details.error_info) - self.assertEqual(dapr_error.status_details().error_info['reason'], 'DAPR_ERROR_CODE') - # - self.assertIsNotNone(dapr_error.status_details().resource_info) - self.assertEqual( - dapr_error.status_details().resource_info['resource_type'], 'my_resource_type' - ) - self.assertEqual(dapr_error.status_details().resource_info['resource_name'], 'my_resource') - - self.assertIsNotNone(dapr_error.status_details().bad_request) - self.assertEqual(len(dapr_error.status_details().bad_request['field_violations']), 1) - self.assertEqual( - dapr_error.status_details().bad_request['field_violations'][0]['field'], 'my_field' - ) - self.assertEqual( - dapr_error.status_details().bad_request['field_violations'][0]['description'], - 'my field violation message', - ) - - self.assertIsNotNone(dapr_error.status_details().help) - self.assertEqual( - dapr_error.status_details().help['links'][0]['url'], 'https://my_help_link' - ) - - self.assertIsNotNone(dapr_error.status_details().retry_info) - self.assertEqual(dapr_error.status_details().retry_info['retry_delay'], '5s') - - self.assertIsNotNone(dapr_error.status_details().debug_info) - self.assertEqual(len(dapr_error.status_details().debug_info['stack_entries']), 2) - self.assertEqual( - dapr_error.status_details().debug_info['stack_entries'][0], 'stack_entry_1' - ) - self.assertEqual( - dapr_error.status_details().debug_info['stack_entries'][1], 'stack_entry_2' - ) - - self.assertIsNotNone(dapr_error.status_details().localized_message) - self.assertEqual(dapr_error.status_details().localized_message['locale'], 'en-US') - self.assertEqual( - dapr_error.status_details().localized_message['message'], 'my localized message' - ) - - self.assertIsNotNone(dapr_error.status_details().precondition_failure) - self.assertEqual(len(dapr_error.status_details().precondition_failure['violations']), 1) - self.assertEqual( - dapr_error.status_details().precondition_failure['violations'][0]['type'], - 'your_violation_type', - ) - self.assertEqual( - dapr_error.status_details().precondition_failure['violations'][0]['subject'], - 'your_violation_subject', - ) - self.assertEqual( - dapr_error.status_details().precondition_failure['violations'][0]['description'], - 'your_violation_description', - ) - - self.assertIsNotNone(dapr_error.status_details().quota_failure) - self.assertEqual(len(dapr_error.status_details().quota_failure['violations']), 1) - self.assertEqual( - dapr_error.status_details().quota_failure['violations'][0]['subject'], - 'your_violation_subject', - ) - self.assertEqual( - dapr_error.status_details().quota_failure['violations'][0]['description'], - 'your_violation_description', - ) - - self.assertIsNotNone(dapr_error.status_details().request_info) - self.assertEqual(dapr_error.status_details().request_info['request_id'], 'your_request_id') - self.assertEqual( - dapr_error.status_details().request_info['serving_data'], 'your_serving_data' - ) - - def test_error_code(self): - dapr = DaprGrpcClient(f'localhost:{self._grpc_port}') - - expected_status = create_expected_status() - - self._fake_dapr_server.raise_exception_on_next_call(expected_status) - with self.assertRaises(DaprGrpcError) as context: - dapr.get_metadata() - - dapr_error = context.exception - - self.assertEqual(dapr_error.error_code(), 'DAPR_ERROR_CODE') - - # No ErrorInfo - self._fake_dapr_server.raise_exception_on_next_call( - status_pb2.Status(code=code_pb2.INTERNAL, message='my invalid argument message') - ) - - with self.assertRaises(DaprGrpcError) as context: - dapr.get_metadata() - - dapr_error = context.exception - - self.assertEqual(dapr_error.error_code(), 'UNKNOWN') +import unittest + +import grpc +from google.rpc import error_details_pb2, status_pb2, code_pb2 +from google.protobuf.any_pb2 import Any +from google.protobuf.duration_pb2 import Duration + +from dapr.clients import DaprGrpcClient +from dapr.clients.exceptions import DaprGrpcError +from dapr.conf import settings + +from .fake_dapr_server import FakeDaprSidecar + + +def create_expected_status(): + detail1 = Any() + detail1.Pack(error_details_pb2.ErrorInfo(reason='DAPR_ERROR_CODE')) + + detail2 = Any() + detail2.Pack( + error_details_pb2.ResourceInfo( + resource_type='my_resource_type', resource_name='my_resource' + ) + ) + + detail3 = Any() + detail3.Pack( + error_details_pb2.BadRequest( + field_violations=[ + error_details_pb2.BadRequest.FieldViolation( + field='my_field', description='my field violation message' + ) + ] + ) + ) + + detail4 = Any() + help_message = error_details_pb2.Help() + link = error_details_pb2.Help.Link(description='Help Link', url='https://my_help_link') + help_message.links.extend([link]) + detail4.Pack(help_message) + + detail5 = Any() + retry_info = error_details_pb2.RetryInfo(retry_delay=Duration(seconds=5)) + detail5.Pack(retry_info) + + detail6 = Any() + detail6.Pack(error_details_pb2.DebugInfo(stack_entries=['stack_entry_1', 'stack_entry_2'])) + + detail7 = Any() + detail7.Pack(error_details_pb2.LocalizedMessage(locale='en-US', message='my localized message')) + + detail8 = Any() + violation = error_details_pb2.PreconditionFailure.Violation( + type='your_violation_type', + subject='your_violation_subject', + description='your_violation_description', + ) + detail8.Pack(error_details_pb2.PreconditionFailure(violations=[violation])) + + detail9 = Any() + violation = error_details_pb2.QuotaFailure.Violation( + subject='your_violation_subject', description='your_violation_description' + ) + detail9.Pack(error_details_pb2.QuotaFailure(violations=[violation])) + + detail10 = Any() + detail10.Pack( + error_details_pb2.RequestInfo( + request_id='your_request_id', serving_data='your_serving_data' + ) + ) + + return status_pb2.Status( + code=code_pb2.INTERNAL, + message='my invalid argument message', + details=[ + detail1, + detail2, + detail3, + detail4, + detail5, + detail6, + detail7, + detail8, + detail9, + detail10, + ], + ) + + +class DaprExceptionsTestCase(unittest.TestCase): + _grpc_port = 50001 + _http_port = 3500 + + @classmethod + def setUpClass(cls): + cls._fake_dapr_server = FakeDaprSidecar(grpc_port=cls._grpc_port, http_port=cls._http_port) + settings.DAPR_HTTP_PORT = cls._http_port + settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(cls._http_port) + cls._fake_dapr_server.start() + cls._expected_status = create_expected_status() + + @classmethod + def tearDownClass(cls): + cls._fake_dapr_server.stop() + + def test_exception_status_parsing(self): + dapr = DaprGrpcClient(f'localhost:{self._grpc_port}') + + self._fake_dapr_server.raise_exception_on_next_call(self._expected_status) + with self.assertRaises(DaprGrpcError) as context: + dapr.get_metadata() + + dapr_error = context.exception + + self.assertEqual(dapr_error.code(), grpc.StatusCode.INTERNAL) + self.assertEqual(dapr_error.details(), 'my invalid argument message') + self.assertEqual(dapr_error.error_code(), 'DAPR_ERROR_CODE') + + self.assertIsNotNone(dapr_error._details.error_info) + self.assertEqual(dapr_error.status_details().error_info['reason'], 'DAPR_ERROR_CODE') + # + self.assertIsNotNone(dapr_error.status_details().resource_info) + self.assertEqual( + dapr_error.status_details().resource_info['resource_type'], 'my_resource_type' + ) + self.assertEqual(dapr_error.status_details().resource_info['resource_name'], 'my_resource') + + self.assertIsNotNone(dapr_error.status_details().bad_request) + self.assertEqual(len(dapr_error.status_details().bad_request['field_violations']), 1) + self.assertEqual( + dapr_error.status_details().bad_request['field_violations'][0]['field'], 'my_field' + ) + self.assertEqual( + dapr_error.status_details().bad_request['field_violations'][0]['description'], + 'my field violation message', + ) + + self.assertIsNotNone(dapr_error.status_details().help) + self.assertEqual( + dapr_error.status_details().help['links'][0]['url'], 'https://my_help_link' + ) + + self.assertIsNotNone(dapr_error.status_details().retry_info) + self.assertEqual(dapr_error.status_details().retry_info['retry_delay'], '5s') + + self.assertIsNotNone(dapr_error.status_details().debug_info) + self.assertEqual(len(dapr_error.status_details().debug_info['stack_entries']), 2) + self.assertEqual( + dapr_error.status_details().debug_info['stack_entries'][0], 'stack_entry_1' + ) + self.assertEqual( + dapr_error.status_details().debug_info['stack_entries'][1], 'stack_entry_2' + ) + + self.assertIsNotNone(dapr_error.status_details().localized_message) + self.assertEqual(dapr_error.status_details().localized_message['locale'], 'en-US') + self.assertEqual( + dapr_error.status_details().localized_message['message'], 'my localized message' + ) + + self.assertIsNotNone(dapr_error.status_details().precondition_failure) + self.assertEqual(len(dapr_error.status_details().precondition_failure['violations']), 1) + self.assertEqual( + dapr_error.status_details().precondition_failure['violations'][0]['type'], + 'your_violation_type', + ) + self.assertEqual( + dapr_error.status_details().precondition_failure['violations'][0]['subject'], + 'your_violation_subject', + ) + self.assertEqual( + dapr_error.status_details().precondition_failure['violations'][0]['description'], + 'your_violation_description', + ) + + self.assertIsNotNone(dapr_error.status_details().quota_failure) + self.assertEqual(len(dapr_error.status_details().quota_failure['violations']), 1) + self.assertEqual( + dapr_error.status_details().quota_failure['violations'][0]['subject'], + 'your_violation_subject', + ) + self.assertEqual( + dapr_error.status_details().quota_failure['violations'][0]['description'], + 'your_violation_description', + ) + + self.assertIsNotNone(dapr_error.status_details().request_info) + self.assertEqual(dapr_error.status_details().request_info['request_id'], 'your_request_id') + self.assertEqual( + dapr_error.status_details().request_info['serving_data'], 'your_serving_data' + ) + + def test_error_code(self): + dapr = DaprGrpcClient(f'localhost:{self._grpc_port}') + + expected_status = create_expected_status() + + self._fake_dapr_server.raise_exception_on_next_call(expected_status) + with self.assertRaises(DaprGrpcError) as context: + dapr.get_metadata() + + dapr_error = context.exception + + self.assertEqual(dapr_error.error_code(), 'DAPR_ERROR_CODE') + + # No ErrorInfo + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INTERNAL, message='my invalid argument message') + ) + + with self.assertRaises(DaprGrpcError) as context: + dapr.get_metadata() + + dapr_error = context.exception + + self.assertEqual(dapr_error.error_code(), 'UNKNOWN') diff --git a/tests/clients/test_heatlhcheck.py b/tests/clients/test_heatlhcheck.py index f3be8a475..0e5aa636c 100644 --- a/tests/clients/test_heatlhcheck.py +++ b/tests/clients/test_heatlhcheck.py @@ -1,76 +1,76 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import time -import unittest -from unittest.mock import patch, MagicMock - -from dapr.clients.health import DaprHealth -from dapr.conf import settings -from dapr.version import __version__ - - -class DaprHealthCheckTests(unittest.TestCase): - @patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500') - @patch('urllib.request.urlopen') - def test_wait_until_ready_success(self, mock_urlopen): - mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200) - - try: - DaprHealth.wait_until_ready() - except Exception as e: - self.fail(f'wait_until_ready() raised an exception unexpectedly: {e}') - - mock_urlopen.assert_called_once() - - called_url = mock_urlopen.call_args[0][0].full_url - self.assertEqual(called_url, 'http://domain.com:3500/v1.0/healthz/outbound') - - # Check headers are properly set - headers = mock_urlopen.call_args[0][0].headers - self.assertIn('User-agent', headers) - self.assertEqual(headers['User-agent'], f'dapr-sdk-python/{__version__}') - - @patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500') - @patch.object(settings, 'DAPR_API_TOKEN', 'mytoken') - @patch('urllib.request.urlopen') - def test_wait_until_ready_success_with_api_token(self, mock_urlopen): - mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200) - - try: - DaprHealth.wait_until_ready() - except Exception as e: - self.fail(f'wait_until_ready() raised an exception unexpectedly: {e}') - - mock_urlopen.assert_called_once() - - # Check headers are properly set - headers = mock_urlopen.call_args[0][0].headers - self.assertIn('User-agent', headers) - self.assertEqual(headers['User-agent'], f'dapr-sdk-python/{__version__}') - self.assertIn('Dapr-api-token', headers) - self.assertEqual(headers['Dapr-api-token'], 'mytoken') - - @patch.object(settings, 'DAPR_HEALTH_TIMEOUT', '2.5') - @patch('urllib.request.urlopen') - def test_wait_until_ready_timeout(self, mock_urlopen): - mock_urlopen.return_value.__enter__.return_value = MagicMock(status=500) - - start = time.time() - - with self.assertRaises(TimeoutError): - DaprHealth.wait_until_ready() - - self.assertGreaterEqual(time.time() - start, 2.5) - self.assertGreater(mock_urlopen.call_count, 1) +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import time +import unittest +from unittest.mock import patch, MagicMock + +from dapr.clients.health import DaprHealth +from dapr.conf import settings +from dapr.version import __version__ + + +class DaprHealthCheckTests(unittest.TestCase): + @patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500') + @patch('urllib.request.urlopen') + def test_wait_until_ready_success(self, mock_urlopen): + mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200) + + try: + DaprHealth.wait_until_ready() + except Exception as e: + self.fail(f'wait_until_ready() raised an exception unexpectedly: {e}') + + mock_urlopen.assert_called_once() + + called_url = mock_urlopen.call_args[0][0].full_url + self.assertEqual(called_url, 'http://domain.com:3500/v1.0/healthz/outbound') + + # Check headers are properly set + headers = mock_urlopen.call_args[0][0].headers + self.assertIn('User-agent', headers) + self.assertEqual(headers['User-agent'], f'dapr-sdk-python/{__version__}') + + @patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500') + @patch.object(settings, 'DAPR_API_TOKEN', 'mytoken') + @patch('urllib.request.urlopen') + def test_wait_until_ready_success_with_api_token(self, mock_urlopen): + mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200) + + try: + DaprHealth.wait_until_ready() + except Exception as e: + self.fail(f'wait_until_ready() raised an exception unexpectedly: {e}') + + mock_urlopen.assert_called_once() + + # Check headers are properly set + headers = mock_urlopen.call_args[0][0].headers + self.assertIn('User-agent', headers) + self.assertEqual(headers['User-agent'], f'dapr-sdk-python/{__version__}') + self.assertIn('Dapr-api-token', headers) + self.assertEqual(headers['Dapr-api-token'], 'mytoken') + + @patch.object(settings, 'DAPR_HEALTH_TIMEOUT', '2.5') + @patch('urllib.request.urlopen') + def test_wait_until_ready_timeout(self, mock_urlopen): + mock_urlopen.return_value.__enter__.return_value = MagicMock(status=500) + + start = time.time() + + with self.assertRaises(TimeoutError): + DaprHealth.wait_until_ready() + + self.assertGreaterEqual(time.time() - start, 2.5) + self.assertGreater(mock_urlopen.call_count, 1) diff --git a/tests/clients/test_http_helpers.py b/tests/clients/test_http_helpers.py index ab173cd73..567ddbaa8 100644 --- a/tests/clients/test_http_helpers.py +++ b/tests/clients/test_http_helpers.py @@ -1,22 +1,22 @@ -import unittest -from unittest.mock import patch - -from dapr.conf import settings -from dapr.clients.http.helpers import get_api_url - - -class DaprHttpClientHelpersTests(unittest.TestCase): - def test_get_api_url_default(self, dapr=None): - self.assertEqual( - 'http://{}:{}/{}'.format( - settings.DAPR_RUNTIME_HOST, settings.DAPR_HTTP_PORT, settings.DAPR_API_VERSION - ), - get_api_url(), - ) - - @patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'https://domain1.com:5000') - def test_get_api_url_endpoint_as_env_variable(self): - self.assertEqual( - 'https://domain1.com:5000/{}'.format(settings.DAPR_API_VERSION), - get_api_url(), - ) +import unittest +from unittest.mock import patch + +from dapr.conf import settings +from dapr.clients.http.helpers import get_api_url + + +class DaprHttpClientHelpersTests(unittest.TestCase): + def test_get_api_url_default(self, dapr=None): + self.assertEqual( + 'http://{}:{}/{}'.format( + settings.DAPR_RUNTIME_HOST, settings.DAPR_HTTP_PORT, settings.DAPR_API_VERSION + ), + get_api_url(), + ) + + @patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'https://domain1.com:5000') + def test_get_api_url_endpoint_as_env_variable(self): + self.assertEqual( + 'https://domain1.com:5000/{}'.format(settings.DAPR_API_VERSION), + get_api_url(), + ) diff --git a/tests/clients/test_http_service_invocation_client.py b/tests/clients/test_http_service_invocation_client.py index d45c530ba..f5fed0865 100644 --- a/tests/clients/test_http_service_invocation_client.py +++ b/tests/clients/test_http_service_invocation_client.py @@ -1,326 +1,326 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import json -import typing -import unittest -from asyncio import TimeoutError - -from opentelemetry import trace -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter -from opentelemetry.sdk.trace.sampling import ALWAYS_ON -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator - - -from dapr.clients.exceptions import DaprInternalError -from dapr.conf import settings -from dapr.proto import common_v1 - -from .fake_http_server import FakeHttpServer -from dapr.clients import DaprClient - - -class DaprInvocationHttpClientTests(unittest.TestCase): - server_port = 3500 - - @classmethod - def setUpClass(cls): - cls.server = FakeHttpServer(cls.server_port) - cls.server.start() - - cls.app_id = 'fakeapp' - cls.method_name = 'fakemethod' - cls.invoke_url = f'/v1.0/invoke/{cls.app_id}/method/{cls.method_name}' - - @classmethod - def tearDownClass(cls): - cls.server.shutdown_server() - - def setUp(self): - settings.DAPR_API_TOKEN = None - settings.DAPR_HTTP_PORT = self.server_port - settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' - settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(self.server_port) - - self.server.reset() - self.client = DaprClient() - - def test_basic_invoke(self): - self.server.set_response(b'STRING_BODY') - - response = self.client.invoke_method(self.app_id, self.method_name, '') - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(self.invoke_url, self.server.request_path()) - - def test_coroutine_basic_invoke(self): - self.server.set_response(b'STRING_BODY') - - import asyncio - - loop = asyncio.new_event_loop() - response = loop.run_until_complete( - self.client.invoke_method_async(self.app_id, self.method_name, '') - ) - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(self.invoke_url, self.server.request_path()) - - def test_invoke_PUT_with_body(self): - self.server.set_response(b'STRING_BODY') - - response = self.client.invoke_method(self.app_id, self.method_name, b'FOO', http_verb='PUT') - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(self.invoke_url, self.server.request_path()) - self.assertEqual(b'FOO', self.server.get_request_body()) - - def test_invoke_PUT_with_bytes_body(self): - self.server.set_response(b'STRING_BODY') - - response = self.client.invoke_method(self.app_id, self.method_name, b'FOO', http_verb='PUT') - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(self.invoke_url, self.server.request_path()) - self.assertEqual(b'FOO', self.server.get_request_body()) - - def test_invoke_GET_with_query_params(self): - self.server.set_response(b'STRING_BODY') - query_params = (('key1', 'value1'), ('key2', 'value2')) - - response = self.client.invoke_method( - self.app_id, self.method_name, '', http_querystring=query_params - ) - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(f'{self.invoke_url}?key1=value1&key2=value2', self.server.request_path()) - - def test_invoke_GET_with_duplicate_query_params(self): - self.server.set_response(b'STRING_BODY') - query_params = (('key1', 'value1'), ('key1', 'value2')) - - response = self.client.invoke_method( - self.app_id, self.method_name, '', http_querystring=query_params - ) - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(f'{self.invoke_url}?key1=value1&key1=value2', self.server.request_path()) - - def test_invoke_PUT_with_content_type(self): - self.server.set_response(b'STRING_BODY') - - sample_object = {'foo': ['val1', 'val2']} - - response = self.client.invoke_method( - self.app_id, - self.method_name, - json.dumps(sample_object), - content_type='application/json', - ) - - self.assertEqual(b'STRING_BODY', response.data) - self.assertEqual(b'{"foo": ["val1", "val2"]}', self.server.get_request_body()) - - def test_invoke_method_proto_data(self): - self.server.set_response(b'\x0a\x04resp') - self.server.reply_header('Content-Type', 'application/x-protobuf') - - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method(self.app_id, self.method_name, http_verb='PUT', data=req) - - self.assertEqual(b'\x0a\x04test', self.server.get_request_body()) - # unpack to new protobuf object - new_resp = common_v1.StateItem() - self.assertEqual(resp.headers['Content-Type'], ['application/x-protobuf']) - resp.unpack(new_resp) - self.assertEqual('resp', new_resp.key) - - def test_invoke_method_metadata(self): - self.server.set_response(b'FOO') - - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data=req, - metadata=(('header1', 'value1'), ('header2', 'value2')), - ) - - request_headers = self.server.get_request_headers() - - self.assertEqual(b'FOO', resp.data) - - self.assertEqual('value1', request_headers['header1']) - self.assertEqual('value2', request_headers['header2']) - - def test_invoke_method_protobuf_response_with_suffix(self): - self.server.set_response(b'\x0a\x04resp') - self.server.reply_header('Content-Type', 'application/x-protobuf; gzip') - - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data=req, - metadata=(('header1', 'value1'), ('header2', 'value2')), - ) - self.assertEqual(b'\x0a\x04test', self.server.get_request_body()) - # unpack to new protobuf object - new_resp = common_v1.StateItem() - resp.unpack(new_resp) - self.assertEqual('resp', new_resp.key) - - def test_invoke_method_protobuf_response_case_insensitive(self): - self.server.set_response(b'\x0a\x04resp') - self.server.reply_header('Content-Type', 'apPlicaTion/x-protobuf; gzip') - - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data=req, - metadata=(('header1', 'value1'), ('header2', 'value2')), - ) - - self.assertEqual(b'\x0a\x04test', self.server.get_request_body()) - # unpack to new protobuf object - new_resp = common_v1.StateItem() - resp.unpack(new_resp) - self.assertEqual('resp', new_resp.key) - - def test_invoke_method_error_returned(self): - error_response = b'{"errorCode":"ERR_DIRECT_INVOKE","message":"Something bad happend"}' - self.server.set_response(error_response, 500) - - expected_msg = "('Something bad happend', 'ERR_DIRECT_INVOKE')" - - with self.assertRaises(DaprInternalError) as ctx: - self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data='FOO', - ) - self.assertEqual(expected_msg, str(ctx.exception)) - - def test_invoke_method_non_dapr_error(self): - error_response = b'UNPARSABLE_ERROR' - self.server.set_response(error_response, 500) - - expected_msg = 'Unknown Dapr Error. HTTP status code: 500' - - with self.assertRaises(DaprInternalError) as ctx: - self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data='FOO', - ) - self.assertEqual(expected_msg, str(ctx.exception)) - - def test_generic_client_unknown_protocol(self): - settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'unknown' - - expected_msg = 'Unknown value for DAPR_API_METHOD_INVOCATION_PROTOCOL: UNKNOWN' - - with self.assertRaises(DaprInternalError) as ctx: - client = DaprClient() - - self.assertEqual(expected_msg, str(ctx.exception)) - - settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'grpc' - client = DaprClient() - - self.assertIsNotNone(client) - - settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' - client = DaprClient() - - self.assertIsNotNone(client) - - def test_invoke_method_with_api_token(self): - self.server.set_response(b'FOO') - settings.DAPR_API_TOKEN = 'c29saSBkZW8gZ2xvcmlhCg==' - - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data=req, - ) - - request_headers = self.server.get_request_headers() - - self.assertEqual('c29saSBkZW8gZ2xvcmlhCg==', request_headers['dapr-api-token']) - self.assertEqual(b'FOO', resp.data) - - def test_invoke_method_with_tracer(self): - # Create a tracer provider - tracer_provider = TracerProvider(sampler=ALWAYS_ON) - - # Create a span processor - span_processor = BatchSpanProcessor(ConsoleSpanExporter()) - - # Add the span processor to the tracer provider - tracer_provider.add_span_processor(span_processor) - - # Set the tracer provider - trace.set_tracer_provider(tracer_provider) - - # Get the tracer - tracer = trace.get_tracer(__name__) - - def trace_injector() -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier=headers) - return headers - - self.client = DaprClient(headers_callback=trace_injector) - self.server.set_response(b'FOO') - - with tracer.start_as_current_span(name='test'): - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data=req, - ) - - request_headers = self.server.get_request_headers() - - self.assertIn('Traceparent', request_headers) - self.assertEqual(b'FOO', resp.data) - - def test_timeout_exception_thrown_when_timeout_reached(self): - new_client = DaprClient(http_timeout_seconds=1) - self.server.set_server_delay(1.5) - with self.assertRaises(TimeoutError): - new_client.invoke_method(self.app_id, self.method_name, '') - - def test_global_timeout_setting_is_honored(self): - previous_timeout = settings.DAPR_HTTP_TIMEOUT_SECONDS - settings.DAPR_HTTP_TIMEOUT_SECONDS = 1 - new_client = DaprClient() - self.server.set_server_delay(1.5) - with self.assertRaises(TimeoutError): - new_client.invoke_method(self.app_id, self.method_name, '') - - settings.DAPR_HTTP_TIMEOUT_SECONDS = previous_timeout +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import json +import typing +import unittest +from asyncio import TimeoutError + +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter +from opentelemetry.sdk.trace.sampling import ALWAYS_ON +from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + + +from dapr.clients.exceptions import DaprInternalError +from dapr.conf import settings +from dapr.proto import common_v1 + +from .fake_http_server import FakeHttpServer +from dapr.clients import DaprClient + + +class DaprInvocationHttpClientTests(unittest.TestCase): + server_port = 3500 + + @classmethod + def setUpClass(cls): + cls.server = FakeHttpServer(cls.server_port) + cls.server.start() + + cls.app_id = 'fakeapp' + cls.method_name = 'fakemethod' + cls.invoke_url = f'/v1.0/invoke/{cls.app_id}/method/{cls.method_name}' + + @classmethod + def tearDownClass(cls): + cls.server.shutdown_server() + + def setUp(self): + settings.DAPR_API_TOKEN = None + settings.DAPR_HTTP_PORT = self.server_port + settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' + settings.DAPR_HTTP_ENDPOINT = 'http://127.0.0.1:{}'.format(self.server_port) + + self.server.reset() + self.client = DaprClient() + + def test_basic_invoke(self): + self.server.set_response(b'STRING_BODY') + + response = self.client.invoke_method(self.app_id, self.method_name, '') + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(self.invoke_url, self.server.request_path()) + + def test_coroutine_basic_invoke(self): + self.server.set_response(b'STRING_BODY') + + import asyncio + + loop = asyncio.new_event_loop() + response = loop.run_until_complete( + self.client.invoke_method_async(self.app_id, self.method_name, '') + ) + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(self.invoke_url, self.server.request_path()) + + def test_invoke_PUT_with_body(self): + self.server.set_response(b'STRING_BODY') + + response = self.client.invoke_method(self.app_id, self.method_name, b'FOO', http_verb='PUT') + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(self.invoke_url, self.server.request_path()) + self.assertEqual(b'FOO', self.server.get_request_body()) + + def test_invoke_PUT_with_bytes_body(self): + self.server.set_response(b'STRING_BODY') + + response = self.client.invoke_method(self.app_id, self.method_name, b'FOO', http_verb='PUT') + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(self.invoke_url, self.server.request_path()) + self.assertEqual(b'FOO', self.server.get_request_body()) + + def test_invoke_GET_with_query_params(self): + self.server.set_response(b'STRING_BODY') + query_params = (('key1', 'value1'), ('key2', 'value2')) + + response = self.client.invoke_method( + self.app_id, self.method_name, '', http_querystring=query_params + ) + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(f'{self.invoke_url}?key1=value1&key2=value2', self.server.request_path()) + + def test_invoke_GET_with_duplicate_query_params(self): + self.server.set_response(b'STRING_BODY') + query_params = (('key1', 'value1'), ('key1', 'value2')) + + response = self.client.invoke_method( + self.app_id, self.method_name, '', http_querystring=query_params + ) + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(f'{self.invoke_url}?key1=value1&key1=value2', self.server.request_path()) + + def test_invoke_PUT_with_content_type(self): + self.server.set_response(b'STRING_BODY') + + sample_object = {'foo': ['val1', 'val2']} + + response = self.client.invoke_method( + self.app_id, + self.method_name, + json.dumps(sample_object), + content_type='application/json', + ) + + self.assertEqual(b'STRING_BODY', response.data) + self.assertEqual(b'{"foo": ["val1", "val2"]}', self.server.get_request_body()) + + def test_invoke_method_proto_data(self): + self.server.set_response(b'\x0a\x04resp') + self.server.reply_header('Content-Type', 'application/x-protobuf') + + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method(self.app_id, self.method_name, http_verb='PUT', data=req) + + self.assertEqual(b'\x0a\x04test', self.server.get_request_body()) + # unpack to new protobuf object + new_resp = common_v1.StateItem() + self.assertEqual(resp.headers['Content-Type'], ['application/x-protobuf']) + resp.unpack(new_resp) + self.assertEqual('resp', new_resp.key) + + def test_invoke_method_metadata(self): + self.server.set_response(b'FOO') + + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data=req, + metadata=(('header1', 'value1'), ('header2', 'value2')), + ) + + request_headers = self.server.get_request_headers() + + self.assertEqual(b'FOO', resp.data) + + self.assertEqual('value1', request_headers['header1']) + self.assertEqual('value2', request_headers['header2']) + + def test_invoke_method_protobuf_response_with_suffix(self): + self.server.set_response(b'\x0a\x04resp') + self.server.reply_header('Content-Type', 'application/x-protobuf; gzip') + + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data=req, + metadata=(('header1', 'value1'), ('header2', 'value2')), + ) + self.assertEqual(b'\x0a\x04test', self.server.get_request_body()) + # unpack to new protobuf object + new_resp = common_v1.StateItem() + resp.unpack(new_resp) + self.assertEqual('resp', new_resp.key) + + def test_invoke_method_protobuf_response_case_insensitive(self): + self.server.set_response(b'\x0a\x04resp') + self.server.reply_header('Content-Type', 'apPlicaTion/x-protobuf; gzip') + + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data=req, + metadata=(('header1', 'value1'), ('header2', 'value2')), + ) + + self.assertEqual(b'\x0a\x04test', self.server.get_request_body()) + # unpack to new protobuf object + new_resp = common_v1.StateItem() + resp.unpack(new_resp) + self.assertEqual('resp', new_resp.key) + + def test_invoke_method_error_returned(self): + error_response = b'{"errorCode":"ERR_DIRECT_INVOKE","message":"Something bad happend"}' + self.server.set_response(error_response, 500) + + expected_msg = "('Something bad happend', 'ERR_DIRECT_INVOKE')" + + with self.assertRaises(DaprInternalError) as ctx: + self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data='FOO', + ) + self.assertEqual(expected_msg, str(ctx.exception)) + + def test_invoke_method_non_dapr_error(self): + error_response = b'UNPARSABLE_ERROR' + self.server.set_response(error_response, 500) + + expected_msg = 'Unknown Dapr Error. HTTP status code: 500' + + with self.assertRaises(DaprInternalError) as ctx: + self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data='FOO', + ) + self.assertEqual(expected_msg, str(ctx.exception)) + + def test_generic_client_unknown_protocol(self): + settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'unknown' + + expected_msg = 'Unknown value for DAPR_API_METHOD_INVOCATION_PROTOCOL: UNKNOWN' + + with self.assertRaises(DaprInternalError) as ctx: + client = DaprClient() + + self.assertEqual(expected_msg, str(ctx.exception)) + + settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'grpc' + client = DaprClient() + + self.assertIsNotNone(client) + + settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' + client = DaprClient() + + self.assertIsNotNone(client) + + def test_invoke_method_with_api_token(self): + self.server.set_response(b'FOO') + settings.DAPR_API_TOKEN = 'c29saSBkZW8gZ2xvcmlhCg==' + + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data=req, + ) + + request_headers = self.server.get_request_headers() + + self.assertEqual('c29saSBkZW8gZ2xvcmlhCg==', request_headers['dapr-api-token']) + self.assertEqual(b'FOO', resp.data) + + def test_invoke_method_with_tracer(self): + # Create a tracer provider + tracer_provider = TracerProvider(sampler=ALWAYS_ON) + + # Create a span processor + span_processor = BatchSpanProcessor(ConsoleSpanExporter()) + + # Add the span processor to the tracer provider + tracer_provider.add_span_processor(span_processor) + + # Set the tracer provider + trace.set_tracer_provider(tracer_provider) + + # Get the tracer + tracer = trace.get_tracer(__name__) + + def trace_injector() -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier=headers) + return headers + + self.client = DaprClient(headers_callback=trace_injector) + self.server.set_response(b'FOO') + + with tracer.start_as_current_span(name='test'): + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data=req, + ) + + request_headers = self.server.get_request_headers() + + self.assertIn('Traceparent', request_headers) + self.assertEqual(b'FOO', resp.data) + + def test_timeout_exception_thrown_when_timeout_reached(self): + new_client = DaprClient(http_timeout_seconds=1) + self.server.set_server_delay(1.5) + with self.assertRaises(TimeoutError): + new_client.invoke_method(self.app_id, self.method_name, '') + + def test_global_timeout_setting_is_honored(self): + previous_timeout = settings.DAPR_HTTP_TIMEOUT_SECONDS + settings.DAPR_HTTP_TIMEOUT_SECONDS = 1 + new_client = DaprClient() + self.server.set_server_delay(1.5) + with self.assertRaises(TimeoutError): + new_client.invoke_method(self.app_id, self.method_name, '') + + settings.DAPR_HTTP_TIMEOUT_SECONDS = previous_timeout diff --git a/tests/clients/test_retries_policy.py b/tests/clients/test_retries_policy.py index b5137e643..b0e253302 100644 --- a/tests/clients/test_retries_policy.py +++ b/tests/clients/test_retries_policy.py @@ -1,332 +1,332 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2024 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import unittest -from unittest import mock -from unittest.mock import Mock, MagicMock, patch, AsyncMock - -from grpc import StatusCode, RpcError - -from dapr.clients.retry import RetryPolicy -from dapr.serializers import DefaultJSONSerializer - - -class RetryPolicyTests(unittest.TestCase): - async def httpSetUp(self): - # Setup your test environment and mocks here - self.session = MagicMock() - self.session.request = AsyncMock() - - self.serializer = (DefaultJSONSerializer(),) - - # Example request - self.req = { - 'method': 'GET', - 'url': 'http://example.com', - 'data': None, - 'headers': None, - 'sslcontext': None, - 'params': None, - 'timeout': None, - } - - def test_init_success_default(self): - policy = RetryPolicy() - - self.assertEqual(0, policy.max_attempts) - self.assertEqual(1, policy.initial_backoff) - self.assertEqual(20, policy.max_backoff) - self.assertEqual(1.5, policy.backoff_multiplier) - self.assertEqual([408, 429, 500, 502, 503, 504], policy.retryable_http_status_codes) - self.assertEqual( - [StatusCode.UNAVAILABLE, StatusCode.DEADLINE_EXCEEDED], - policy.retryable_grpc_status_codes, - ) - - def test_init_success(self): - policy = RetryPolicy( - max_attempts=3, - initial_backoff=2, - max_backoff=10, - backoff_multiplier=2, - retryable_grpc_status_codes=[StatusCode.UNAVAILABLE], - retryable_http_status_codes=[408, 429], - ) - self.assertEqual(3, policy.max_attempts) - self.assertEqual(2, policy.initial_backoff) - self.assertEqual(10, policy.max_backoff) - self.assertEqual(2, policy.backoff_multiplier) - self.assertEqual([StatusCode.UNAVAILABLE], policy.retryable_grpc_status_codes) - self.assertEqual([408, 429], policy.retryable_http_status_codes) - - def test_init_with_errors(self): - with self.assertRaises(ValueError): - RetryPolicy(max_attempts=-2) - - with self.assertRaises(ValueError): - RetryPolicy(initial_backoff=0) - - with self.assertRaises(ValueError): - RetryPolicy(max_backoff=0) - - with self.assertRaises(ValueError): - RetryPolicy(backoff_multiplier=0) - - with self.assertRaises(ValueError): - RetryPolicy(retryable_http_status_codes=[]) - - with self.assertRaises(ValueError): - RetryPolicy(retryable_grpc_status_codes=[]) - - def test_run_rpc_with_retry_success(self): - mock_func = Mock(return_value='success') - - policy = RetryPolicy(max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) - result = policy.run_rpc(mock_func, 'foo', 'bar', arg1=1, arg2=2) - - self.assertEqual(result, 'success') - mock_func.assert_called_once_with('foo', 'bar', arg1=1, arg2=2) - - def test_run_rpc_with_retry_no_retry(self): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = MagicMock(side_effect=mock_error) - - policy = RetryPolicy(max_attempts=0) - with self.assertRaises(RpcError): - policy.run_rpc(mock_func) - mock_func.assert_called_once() - - @patch('time.sleep', return_value=None) # To speed up tests - def test_run_rpc_with_retry_fail(self, mock_sleep): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = MagicMock(side_effect=mock_error) - with self.assertRaises(RpcError): - policy = RetryPolicy(max_attempts=4, initial_backoff=2, backoff_multiplier=1.5) - policy.run_rpc(mock_func) - - self.assertEqual(mock_func.call_count, 4) - expected_sleep_calls = [ - mock.call(2.0), # First sleep call - mock.call(3.0), # Second sleep call - mock.call(4.5), # Third sleep call - ] - mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) - - def test_run_rpc_with_retry_fail_with_another_status_code(self): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.FAILED_PRECONDITION) - mock_func = MagicMock(side_effect=mock_error) - - with self.assertRaises(RpcError): - policy = RetryPolicy( - max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE] - ) - policy.run_rpc(mock_func) - - mock_func.assert_called_once() - - @patch('time.sleep', return_value=None) # To speed up tests - def test_run_rpc_with_retry_fail_with_max_backoff(self, mock_sleep): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = MagicMock(side_effect=mock_error) - with self.assertRaises(RpcError): - policy = RetryPolicy( - max_attempts=4, initial_backoff=2, backoff_multiplier=1.5, max_backoff=3 - ) - policy.run_rpc( - mock_func, - ) - - self.assertEqual(mock_func.call_count, 4) - expected_sleep_calls = [ - mock.call(2.0), # First sleep call - mock.call(3.0), # Second sleep call - mock.call(3.0), # Third sleep call - ] - mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) - - @patch('time.sleep', return_value=None) # To speed up tests - def test_run_rpc_with_infinite_retries(self, mock_sleep): - # Testing a function that's supposed to run forever is tricky, so we'll simulate it - # Instead of a fixed side effect, we'll create a function that's supposed to - # break out of the cycle after X calls. - # Then we assert that the function was called X times before breaking the loop - - # Configure the policy to simulate infinite retries - policy = RetryPolicy(max_attempts=-1, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) - - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = MagicMock() - - # Use a side effect on the mock to count calls and eventually interrupt the loop - call_count = 0 - - def side_effect(*args, **kwargs): - nonlocal call_count - call_count += 1 - - if call_count >= 10: # Five calls before breaking the loop - raise Exception('Test interrupt') - - raise mock_error - - mock_func.side_effect = side_effect - - # Run the test, expecting the custom exception to break the loop - with self.assertRaises(Exception) as context: - policy.run_rpc(mock_func) - - self.assertEqual(str(context.exception), 'Test interrupt') - - # Verify the function was retried the expected number of times before interrupting - self.assertEqual(call_count, 10) - - # Test retrying async rpc calls - async def test_run_rpc_async_with_retry_success(self): - mock_func = AsyncMock(return_value='success') - - policy = RetryPolicy(max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) - result, _ = await policy.async_run_rpc(mock_func, 'foo', arg1=1, arg2=2) - - self.assertEqual(result, 'success') - mock_func.assert_awaited_once_with('foo', arg1=1, arg2=2) - - async def test_run_rpc_async_with_retry_no_retry(self): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = AsyncMock(side_effect=mock_error) - - with self.assertRaises(RpcError): - policy = RetryPolicy(max_attempts=0) - await policy.async_run_rpc(mock_func) - mock_func.assert_awaited_once() - - # Test retrying http requests - async def test_http_call_with_success(self): - # Mock the request to succeed on the first try - self.session.request.return_value.status = 200 - - policy = RetryPolicy() - response = await policy.make_http_call(self.session, self.req) - - self.session.request.assert_called_once() - self.assertEqual(200, response.status) - - async def test_http_call_success_with_no_retry(self): - self.session.request.return_value.status = 200 - - policy = RetryPolicy(max_attempts=0) - response = await policy.make_http_call(self.session, self.req) - - self.session.request.assert_called_once() - self.assertEqual(200, response.status) - - async def test_http_call_fail_with_no_retry(self): - self.session.request.return_value.status = 408 - - policy = RetryPolicy(max_attempts=0) - response = await policy.make_http_call(self.session, self.req) - - self.session.request.assert_called_once() - self.assertEqual(408, response.status) - - @patch('asyncio.sleep', return_value=None) - async def test_http_call_retry_eventually_succeeds(self, _): - # Mock the request to fail twice then succeed - self.session.request.side_effect = [ - MagicMock(status=500), # First attempt fails - MagicMock(status=502), # Second attempt fails - MagicMock(status=200), # Third attempt succeeds - ] - - policy = RetryPolicy(max_attempts=3) - response = await policy.make_http_call(self.session, self.req) - - self.assertEqual(3, self.session.request.call_count) - self.assertEqual(200, response.status) - - @patch('asyncio.sleep', return_value=None) - async def test_http_call_retry_eventually_fails(self, _): - self.session.request.return_value.status = 408 - - policy = RetryPolicy(max_attempts=3) - response = await policy.make_http_call(self.session, self.req) - - self.assertEqual(3, self.session.request.call_count) - self.assertEqual(408, response.status) - - @patch('asyncio.sleep', return_value=None) - async def test_http_call_retry_fails_with_a_different_code(self, _): - # Mock the request to fail twice then succeed - self.session.request.return_value.status = 501 - - policy = RetryPolicy(max_attempts=3, retryable_http_status_codes=[500]) - response = await policy.make_http_call(self.session, self.req) - - self.session.request.assert_called_once() - self.assertEqual(response.status, 501) - - @patch('asyncio.sleep', return_value=None) - async def test_http_call_retries_exhausted(self, _): - # Mock the request to fail three times - self.session.request.return_value = MagicMock(status=500) - - policy = RetryPolicy(max_attempts=3, retryable_http_status_codes=[500]) - response = await policy.make_http_call(self.session, self.req) - - self.assertEqual(3, self.session.request.call_count) - self.assertEqual(500, response.status) - - @patch('asyncio.sleep', return_value=None) - async def test_http_call_max_backoff(self, mock_sleep): - self.session.request.return_value.status = 500 - - policy = RetryPolicy(max_attempts=4, initial_backoff=2, backoff_multiplier=2, max_backoff=3) - response = await policy.make_http_call(self.session, self.req) - - expected_sleep_calls = [ - mock.call(2.0), # First sleep call - mock.call(3.0), # Second sleep call - mock.call(3.0), # Third sleep call - ] - self.assertEqual(4, self.session.request.call_count) - mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) - self.assertEqual(500, response.status) - - @patch('asyncio.sleep', return_value=None) - async def test_http_call_infinite_retries(self, _): - retry_count = 0 - max_test_retries = 6 # Simulates "indefinite" retries for test purposes - - # Function to simulate request behavior - async def mock_request(*args, **kwargs): - nonlocal retry_count - retry_count += 1 - if retry_count < max_test_retries: - return MagicMock(status=500) # Simulate failure - else: - return MagicMock(status=200) # Simulate success to stop retrying - - self.session.request = mock_request - - policy = RetryPolicy(max_attempts=-1, retryable_http_status_codes=[500]) - response = await policy.make_http_call(self.session, self.req) - - # Assert that the retry logic was executed the expected number of times - self.assertEqual(response.status, 200) - self.assertEqual(retry_count, max_test_retries) +# -*- coding: utf-8 -*- + +""" +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import unittest +from unittest import mock +from unittest.mock import Mock, MagicMock, patch, AsyncMock + +from grpc import StatusCode, RpcError + +from dapr.clients.retry import RetryPolicy +from dapr.serializers import DefaultJSONSerializer + + +class RetryPolicyTests(unittest.TestCase): + async def httpSetUp(self): + # Setup your test environment and mocks here + self.session = MagicMock() + self.session.request = AsyncMock() + + self.serializer = (DefaultJSONSerializer(),) + + # Example request + self.req = { + 'method': 'GET', + 'url': 'http://example.com', + 'data': None, + 'headers': None, + 'sslcontext': None, + 'params': None, + 'timeout': None, + } + + def test_init_success_default(self): + policy = RetryPolicy() + + self.assertEqual(0, policy.max_attempts) + self.assertEqual(1, policy.initial_backoff) + self.assertEqual(20, policy.max_backoff) + self.assertEqual(1.5, policy.backoff_multiplier) + self.assertEqual([408, 429, 500, 502, 503, 504], policy.retryable_http_status_codes) + self.assertEqual( + [StatusCode.UNAVAILABLE, StatusCode.DEADLINE_EXCEEDED], + policy.retryable_grpc_status_codes, + ) + + def test_init_success(self): + policy = RetryPolicy( + max_attempts=3, + initial_backoff=2, + max_backoff=10, + backoff_multiplier=2, + retryable_grpc_status_codes=[StatusCode.UNAVAILABLE], + retryable_http_status_codes=[408, 429], + ) + self.assertEqual(3, policy.max_attempts) + self.assertEqual(2, policy.initial_backoff) + self.assertEqual(10, policy.max_backoff) + self.assertEqual(2, policy.backoff_multiplier) + self.assertEqual([StatusCode.UNAVAILABLE], policy.retryable_grpc_status_codes) + self.assertEqual([408, 429], policy.retryable_http_status_codes) + + def test_init_with_errors(self): + with self.assertRaises(ValueError): + RetryPolicy(max_attempts=-2) + + with self.assertRaises(ValueError): + RetryPolicy(initial_backoff=0) + + with self.assertRaises(ValueError): + RetryPolicy(max_backoff=0) + + with self.assertRaises(ValueError): + RetryPolicy(backoff_multiplier=0) + + with self.assertRaises(ValueError): + RetryPolicy(retryable_http_status_codes=[]) + + with self.assertRaises(ValueError): + RetryPolicy(retryable_grpc_status_codes=[]) + + def test_run_rpc_with_retry_success(self): + mock_func = Mock(return_value='success') + + policy = RetryPolicy(max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) + result = policy.run_rpc(mock_func, 'foo', 'bar', arg1=1, arg2=2) + + self.assertEqual(result, 'success') + mock_func.assert_called_once_with('foo', 'bar', arg1=1, arg2=2) + + def test_run_rpc_with_retry_no_retry(self): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = MagicMock(side_effect=mock_error) + + policy = RetryPolicy(max_attempts=0) + with self.assertRaises(RpcError): + policy.run_rpc(mock_func) + mock_func.assert_called_once() + + @patch('time.sleep', return_value=None) # To speed up tests + def test_run_rpc_with_retry_fail(self, mock_sleep): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = MagicMock(side_effect=mock_error) + with self.assertRaises(RpcError): + policy = RetryPolicy(max_attempts=4, initial_backoff=2, backoff_multiplier=1.5) + policy.run_rpc(mock_func) + + self.assertEqual(mock_func.call_count, 4) + expected_sleep_calls = [ + mock.call(2.0), # First sleep call + mock.call(3.0), # Second sleep call + mock.call(4.5), # Third sleep call + ] + mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) + + def test_run_rpc_with_retry_fail_with_another_status_code(self): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.FAILED_PRECONDITION) + mock_func = MagicMock(side_effect=mock_error) + + with self.assertRaises(RpcError): + policy = RetryPolicy( + max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE] + ) + policy.run_rpc(mock_func) + + mock_func.assert_called_once() + + @patch('time.sleep', return_value=None) # To speed up tests + def test_run_rpc_with_retry_fail_with_max_backoff(self, mock_sleep): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = MagicMock(side_effect=mock_error) + with self.assertRaises(RpcError): + policy = RetryPolicy( + max_attempts=4, initial_backoff=2, backoff_multiplier=1.5, max_backoff=3 + ) + policy.run_rpc( + mock_func, + ) + + self.assertEqual(mock_func.call_count, 4) + expected_sleep_calls = [ + mock.call(2.0), # First sleep call + mock.call(3.0), # Second sleep call + mock.call(3.0), # Third sleep call + ] + mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) + + @patch('time.sleep', return_value=None) # To speed up tests + def test_run_rpc_with_infinite_retries(self, mock_sleep): + # Testing a function that's supposed to run forever is tricky, so we'll simulate it + # Instead of a fixed side effect, we'll create a function that's supposed to + # break out of the cycle after X calls. + # Then we assert that the function was called X times before breaking the loop + + # Configure the policy to simulate infinite retries + policy = RetryPolicy(max_attempts=-1, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) + + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = MagicMock() + + # Use a side effect on the mock to count calls and eventually interrupt the loop + call_count = 0 + + def side_effect(*args, **kwargs): + nonlocal call_count + call_count += 1 + + if call_count >= 10: # Five calls before breaking the loop + raise Exception('Test interrupt') + + raise mock_error + + mock_func.side_effect = side_effect + + # Run the test, expecting the custom exception to break the loop + with self.assertRaises(Exception) as context: + policy.run_rpc(mock_func) + + self.assertEqual(str(context.exception), 'Test interrupt') + + # Verify the function was retried the expected number of times before interrupting + self.assertEqual(call_count, 10) + + # Test retrying async rpc calls + async def test_run_rpc_async_with_retry_success(self): + mock_func = AsyncMock(return_value='success') + + policy = RetryPolicy(max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) + result, _ = await policy.async_run_rpc(mock_func, 'foo', arg1=1, arg2=2) + + self.assertEqual(result, 'success') + mock_func.assert_awaited_once_with('foo', arg1=1, arg2=2) + + async def test_run_rpc_async_with_retry_no_retry(self): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = AsyncMock(side_effect=mock_error) + + with self.assertRaises(RpcError): + policy = RetryPolicy(max_attempts=0) + await policy.async_run_rpc(mock_func) + mock_func.assert_awaited_once() + + # Test retrying http requests + async def test_http_call_with_success(self): + # Mock the request to succeed on the first try + self.session.request.return_value.status = 200 + + policy = RetryPolicy() + response = await policy.make_http_call(self.session, self.req) + + self.session.request.assert_called_once() + self.assertEqual(200, response.status) + + async def test_http_call_success_with_no_retry(self): + self.session.request.return_value.status = 200 + + policy = RetryPolicy(max_attempts=0) + response = await policy.make_http_call(self.session, self.req) + + self.session.request.assert_called_once() + self.assertEqual(200, response.status) + + async def test_http_call_fail_with_no_retry(self): + self.session.request.return_value.status = 408 + + policy = RetryPolicy(max_attempts=0) + response = await policy.make_http_call(self.session, self.req) + + self.session.request.assert_called_once() + self.assertEqual(408, response.status) + + @patch('asyncio.sleep', return_value=None) + async def test_http_call_retry_eventually_succeeds(self, _): + # Mock the request to fail twice then succeed + self.session.request.side_effect = [ + MagicMock(status=500), # First attempt fails + MagicMock(status=502), # Second attempt fails + MagicMock(status=200), # Third attempt succeeds + ] + + policy = RetryPolicy(max_attempts=3) + response = await policy.make_http_call(self.session, self.req) + + self.assertEqual(3, self.session.request.call_count) + self.assertEqual(200, response.status) + + @patch('asyncio.sleep', return_value=None) + async def test_http_call_retry_eventually_fails(self, _): + self.session.request.return_value.status = 408 + + policy = RetryPolicy(max_attempts=3) + response = await policy.make_http_call(self.session, self.req) + + self.assertEqual(3, self.session.request.call_count) + self.assertEqual(408, response.status) + + @patch('asyncio.sleep', return_value=None) + async def test_http_call_retry_fails_with_a_different_code(self, _): + # Mock the request to fail twice then succeed + self.session.request.return_value.status = 501 + + policy = RetryPolicy(max_attempts=3, retryable_http_status_codes=[500]) + response = await policy.make_http_call(self.session, self.req) + + self.session.request.assert_called_once() + self.assertEqual(response.status, 501) + + @patch('asyncio.sleep', return_value=None) + async def test_http_call_retries_exhausted(self, _): + # Mock the request to fail three times + self.session.request.return_value = MagicMock(status=500) + + policy = RetryPolicy(max_attempts=3, retryable_http_status_codes=[500]) + response = await policy.make_http_call(self.session, self.req) + + self.assertEqual(3, self.session.request.call_count) + self.assertEqual(500, response.status) + + @patch('asyncio.sleep', return_value=None) + async def test_http_call_max_backoff(self, mock_sleep): + self.session.request.return_value.status = 500 + + policy = RetryPolicy(max_attempts=4, initial_backoff=2, backoff_multiplier=2, max_backoff=3) + response = await policy.make_http_call(self.session, self.req) + + expected_sleep_calls = [ + mock.call(2.0), # First sleep call + mock.call(3.0), # Second sleep call + mock.call(3.0), # Third sleep call + ] + self.assertEqual(4, self.session.request.call_count) + mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) + self.assertEqual(500, response.status) + + @patch('asyncio.sleep', return_value=None) + async def test_http_call_infinite_retries(self, _): + retry_count = 0 + max_test_retries = 6 # Simulates "indefinite" retries for test purposes + + # Function to simulate request behavior + async def mock_request(*args, **kwargs): + nonlocal retry_count + retry_count += 1 + if retry_count < max_test_retries: + return MagicMock(status=500) # Simulate failure + else: + return MagicMock(status=200) # Simulate success to stop retrying + + self.session.request = mock_request + + policy = RetryPolicy(max_attempts=-1, retryable_http_status_codes=[500]) + response = await policy.make_http_call(self.session, self.req) + + # Assert that the retry logic was executed the expected number of times + self.assertEqual(response.status, 200) + self.assertEqual(retry_count, max_test_retries) diff --git a/tests/clients/test_retries_policy_async.py b/tests/clients/test_retries_policy_async.py index ebe6865db..44053a194 100644 --- a/tests/clients/test_retries_policy_async.py +++ b/tests/clients/test_retries_policy_async.py @@ -1,129 +1,129 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2024 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import unittest -from unittest import mock -from unittest.mock import MagicMock, patch, AsyncMock - -from grpc import StatusCode, RpcError - -from dapr.clients.retry import RetryPolicy - - -class RetryPolicyGrpcAsyncTests(unittest.IsolatedAsyncioTestCase): - async def test_run_rpc_async_with_retry_success(self): - mock_func = AsyncMock(return_value='success') - - policy = RetryPolicy(max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) - result, _ = await policy.run_rpc_async(mock_func, 'foo', arg1=1, arg2=2) - - self.assertEqual(result, 'success') - mock_func.assert_awaited_once_with('foo', arg1=1, arg2=2) - - async def test_run_rpc_async_with_retry_no_retry(self): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = AsyncMock(side_effect=mock_error) - - with self.assertRaises(RpcError): - policy = RetryPolicy(max_attempts=0) - await policy.run_rpc_async(mock_func) - mock_func.assert_awaited_once() - - @patch('asyncio.sleep', return_value=None) - async def test_run_rpc_async_with_retry_fail(self, mock_sleep): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = AsyncMock(side_effect=mock_error) - with self.assertRaises(RpcError): - policy = RetryPolicy(max_attempts=4, initial_backoff=2, backoff_multiplier=1.5) - await policy.run_rpc_async(mock_func) - - self.assertEqual(mock_func.await_count, 4) - expected_sleep_calls = [ - mock.call(2.0), # First sleep call - mock.call(3.0), # Second sleep call - mock.call(4.5), # Third sleep call - ] - mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) - - async def test_run_rpc_async_with_retry_fail_with_another_status_code(self): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.FAILED_PRECONDITION) - mock_func = AsyncMock(side_effect=mock_error) - - with self.assertRaises(RpcError): - policy = RetryPolicy( - max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE] - ) - await policy.run_rpc_async(mock_func) - - mock_func.assert_awaited_once() - - @patch('asyncio.sleep', return_value=None) - async def test_run_rpc_async_with_retry_fail_with_max_backoff(self, mock_sleep): - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = AsyncMock(side_effect=mock_error) - - with self.assertRaises(RpcError): - policy = RetryPolicy( - max_attempts=4, initial_backoff=2, backoff_multiplier=1.5, max_backoff=3 - ) - await policy.run_rpc_async(mock_func) - - self.assertEqual(mock_func.await_count, 4) - expected_sleep_calls = [ - mock.call(2.0), # First sleep call - mock.call(3.0), # Second sleep call - mock.call(3.0), # Third sleep call - ] - mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) - - @patch('asyncio.sleep', return_value=None) - async def test_run_rpc_async_with_infinite_retries(self, mock_sleep): - # Testing a function that's supposed to run forever is tricky, so we'll simulate it - # Instead of a fixed side effect, we'll create a function that's supposed to - # break out of the cycle after X calls. - # Then we assert that the function was called X times before breaking the loop - - # Configure the policy to simulate infinite retries - policy = RetryPolicy(max_attempts=-1, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) - - mock_error = RpcError() - mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) - mock_func = AsyncMock() - - # Use a side effect on the mock to count calls and eventually interrupt the loop - call_count = 0 - - def side_effect(*args, **kwargs): - nonlocal call_count - call_count += 1 - - if call_count >= 10: # Five calls before breaking the loop - raise Exception('Test interrupt') - - raise mock_error - - mock_func.side_effect = side_effect - - # Run the test, expecting the custom exception to break the loop - with self.assertRaises(Exception) as context: - await policy.run_rpc_async(mock_func) - - self.assertEqual(str(context.exception), 'Test interrupt') - - # Verify the function was retried the expected number of times before interrupting - self.assertEqual(call_count, 10) +# -*- coding: utf-8 -*- + +""" +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import unittest +from unittest import mock +from unittest.mock import MagicMock, patch, AsyncMock + +from grpc import StatusCode, RpcError + +from dapr.clients.retry import RetryPolicy + + +class RetryPolicyGrpcAsyncTests(unittest.IsolatedAsyncioTestCase): + async def test_run_rpc_async_with_retry_success(self): + mock_func = AsyncMock(return_value='success') + + policy = RetryPolicy(max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) + result, _ = await policy.run_rpc_async(mock_func, 'foo', arg1=1, arg2=2) + + self.assertEqual(result, 'success') + mock_func.assert_awaited_once_with('foo', arg1=1, arg2=2) + + async def test_run_rpc_async_with_retry_no_retry(self): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = AsyncMock(side_effect=mock_error) + + with self.assertRaises(RpcError): + policy = RetryPolicy(max_attempts=0) + await policy.run_rpc_async(mock_func) + mock_func.assert_awaited_once() + + @patch('asyncio.sleep', return_value=None) + async def test_run_rpc_async_with_retry_fail(self, mock_sleep): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = AsyncMock(side_effect=mock_error) + with self.assertRaises(RpcError): + policy = RetryPolicy(max_attempts=4, initial_backoff=2, backoff_multiplier=1.5) + await policy.run_rpc_async(mock_func) + + self.assertEqual(mock_func.await_count, 4) + expected_sleep_calls = [ + mock.call(2.0), # First sleep call + mock.call(3.0), # Second sleep call + mock.call(4.5), # Third sleep call + ] + mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) + + async def test_run_rpc_async_with_retry_fail_with_another_status_code(self): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.FAILED_PRECONDITION) + mock_func = AsyncMock(side_effect=mock_error) + + with self.assertRaises(RpcError): + policy = RetryPolicy( + max_attempts=3, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE] + ) + await policy.run_rpc_async(mock_func) + + mock_func.assert_awaited_once() + + @patch('asyncio.sleep', return_value=None) + async def test_run_rpc_async_with_retry_fail_with_max_backoff(self, mock_sleep): + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = AsyncMock(side_effect=mock_error) + + with self.assertRaises(RpcError): + policy = RetryPolicy( + max_attempts=4, initial_backoff=2, backoff_multiplier=1.5, max_backoff=3 + ) + await policy.run_rpc_async(mock_func) + + self.assertEqual(mock_func.await_count, 4) + expected_sleep_calls = [ + mock.call(2.0), # First sleep call + mock.call(3.0), # Second sleep call + mock.call(3.0), # Third sleep call + ] + mock_sleep.assert_has_calls(expected_sleep_calls, any_order=False) + + @patch('asyncio.sleep', return_value=None) + async def test_run_rpc_async_with_infinite_retries(self, mock_sleep): + # Testing a function that's supposed to run forever is tricky, so we'll simulate it + # Instead of a fixed side effect, we'll create a function that's supposed to + # break out of the cycle after X calls. + # Then we assert that the function was called X times before breaking the loop + + # Configure the policy to simulate infinite retries + policy = RetryPolicy(max_attempts=-1, retryable_grpc_status_codes=[StatusCode.UNAVAILABLE]) + + mock_error = RpcError() + mock_error.code = MagicMock(return_value=StatusCode.UNAVAILABLE) + mock_func = AsyncMock() + + # Use a side effect on the mock to count calls and eventually interrupt the loop + call_count = 0 + + def side_effect(*args, **kwargs): + nonlocal call_count + call_count += 1 + + if call_count >= 10: # Five calls before breaking the loop + raise Exception('Test interrupt') + + raise mock_error + + mock_func.side_effect = side_effect + + # Run the test, expecting the custom exception to break the loop + with self.assertRaises(Exception) as context: + await policy.run_rpc_async(mock_func) + + self.assertEqual(str(context.exception), 'Test interrupt') + + # Verify the function was retried the expected number of times before interrupting + self.assertEqual(call_count, 10) diff --git a/tests/clients/test_secure_http_service_invocation_client.py b/tests/clients/test_secure_http_service_invocation_client.py index f23bc11c1..f61de27b7 100644 --- a/tests/clients/test_secure_http_service_invocation_client.py +++ b/tests/clients/test_secure_http_service_invocation_client.py @@ -1,141 +1,141 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" -import ssl -import typing -from asyncio import TimeoutError - -from opentelemetry import trace -from opentelemetry.sdk.trace import TracerProvider -from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter -from opentelemetry.sdk.trace.sampling import ALWAYS_ON -from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator - -from dapr.clients import DaprClient, DaprGrpcClient -from dapr.clients.health import DaprHealth -from dapr.clients.http.client import DaprHttpClient -from dapr.conf import settings -from dapr.proto import common_v1 - - -from .certs import replacement_get_health_context, replacement_get_credentials_func, GrpcCerts -from .fake_http_server import FakeHttpServer -from .test_http_service_invocation_client import DaprInvocationHttpClientTests - - -def replacement_get_client_ssl_context(a): - """ - This method is used (overwritten) from tests - to return context for self-signed certificates - """ - context = ssl.create_default_context() - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - - return context - - -DaprHttpClient.get_ssl_context = replacement_get_client_ssl_context -DaprGrpcClient.get_credentials = replacement_get_credentials_func -DaprHealth.get_ssl_context = replacement_get_health_context - - -class DaprSecureInvocationHttpClientTests(DaprInvocationHttpClientTests): - server_port = 4443 - - @classmethod - def setUpClass(cls): - cls.server = FakeHttpServer(cls.server_port) - cls.server.start_secure() - - cls.app_id = 'fakeapp' - cls.method_name = 'fakemethod' - cls.invoke_url = f'/v1.0/invoke/{cls.app_id}/method/{cls.method_name}' - - # We need to set up the certificates for the gRPC server - # because the DaprGrpcClient will try to create a connection - GrpcCerts.create_certificates() - - @classmethod - def tearDownClass(cls): - GrpcCerts.delete_certificates() - cls.server.shutdown_server() - - def setUp(self): - settings.DAPR_API_TOKEN = None - settings.DAPR_HTTP_PORT = self.server_port - settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' - settings.DAPR_HTTP_ENDPOINT = 'https://127.0.0.1:{}'.format(self.server_port) - - self.server.reset() - self.client = DaprClient() - - def test_global_timeout_setting_is_honored(self): - previous_timeout = settings.DAPR_HTTP_TIMEOUT_SECONDS - settings.DAPR_HTTP_TIMEOUT_SECONDS = 1 - - new_client = DaprClient(f'https://localhost:{self.server_port}') - - self.server.set_server_delay(1.5) - with self.assertRaises(TimeoutError): - new_client.invoke_method(self.app_id, self.method_name, '') - - settings.DAPR_HTTP_TIMEOUT_SECONDS = previous_timeout - - def test_invoke_method_with_tracer(self): - # Create a tracer provider - tracer_provider = TracerProvider(sampler=ALWAYS_ON) - - # Create a span processor - span_processor = BatchSpanProcessor(ConsoleSpanExporter()) - - # Add the span processor to the tracer provider - tracer_provider.add_span_processor(span_processor) - - # Set the tracer provider - trace.set_tracer_provider(tracer_provider) - - # Get the tracer - tracer = trace.get_tracer(__name__) - - def trace_injector() -> typing.Dict[str, str]: - headers: typing.Dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier=headers) - return headers - - self.client = DaprClient( - f'https://localhost:{self.server_port}', - headers_callback=trace_injector, - ) - self.server.set_response(b'FOO') - - with tracer.start_as_current_span(name='test'): - req = common_v1.StateItem(key='test') - resp = self.client.invoke_method( - self.app_id, - self.method_name, - http_verb='PUT', - data=req, - ) - - request_headers: typing.Dict[str, str] = self.server.get_request_headers() - - self.assertIn('Traceparent', request_headers) - self.assertEqual(b'FOO', resp.data) - - def test_timeout_exception_thrown_when_timeout_reached(self): - new_client = DaprClient(f'https://localhost:{self.server_port}', http_timeout_seconds=1) - self.server.set_server_delay(1.5) - with self.assertRaises(TimeoutError): - new_client.invoke_method(self.app_id, self.method_name, '') +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +import ssl +import typing +from asyncio import TimeoutError + +from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter +from opentelemetry.sdk.trace.sampling import ALWAYS_ON +from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + +from dapr.clients import DaprClient, DaprGrpcClient +from dapr.clients.health import DaprHealth +from dapr.clients.http.client import DaprHttpClient +from dapr.conf import settings +from dapr.proto import common_v1 + + +from .certs import replacement_get_health_context, replacement_get_credentials_func, GrpcCerts +from .fake_http_server import FakeHttpServer +from .test_http_service_invocation_client import DaprInvocationHttpClientTests + + +def replacement_get_client_ssl_context(a): + """ + This method is used (overwritten) from tests + to return context for self-signed certificates + """ + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + return context + + +DaprHttpClient.get_ssl_context = replacement_get_client_ssl_context +DaprGrpcClient.get_credentials = replacement_get_credentials_func +DaprHealth.get_ssl_context = replacement_get_health_context + + +class DaprSecureInvocationHttpClientTests(DaprInvocationHttpClientTests): + server_port = 4443 + + @classmethod + def setUpClass(cls): + cls.server = FakeHttpServer(cls.server_port) + cls.server.start_secure() + + cls.app_id = 'fakeapp' + cls.method_name = 'fakemethod' + cls.invoke_url = f'/v1.0/invoke/{cls.app_id}/method/{cls.method_name}' + + # We need to set up the certificates for the gRPC server + # because the DaprGrpcClient will try to create a connection + GrpcCerts.create_certificates() + + @classmethod + def tearDownClass(cls): + GrpcCerts.delete_certificates() + cls.server.shutdown_server() + + def setUp(self): + settings.DAPR_API_TOKEN = None + settings.DAPR_HTTP_PORT = self.server_port + settings.DAPR_API_METHOD_INVOCATION_PROTOCOL = 'http' + settings.DAPR_HTTP_ENDPOINT = 'https://127.0.0.1:{}'.format(self.server_port) + + self.server.reset() + self.client = DaprClient() + + def test_global_timeout_setting_is_honored(self): + previous_timeout = settings.DAPR_HTTP_TIMEOUT_SECONDS + settings.DAPR_HTTP_TIMEOUT_SECONDS = 1 + + new_client = DaprClient(f'https://localhost:{self.server_port}') + + self.server.set_server_delay(1.5) + with self.assertRaises(TimeoutError): + new_client.invoke_method(self.app_id, self.method_name, '') + + settings.DAPR_HTTP_TIMEOUT_SECONDS = previous_timeout + + def test_invoke_method_with_tracer(self): + # Create a tracer provider + tracer_provider = TracerProvider(sampler=ALWAYS_ON) + + # Create a span processor + span_processor = BatchSpanProcessor(ConsoleSpanExporter()) + + # Add the span processor to the tracer provider + tracer_provider.add_span_processor(span_processor) + + # Set the tracer provider + trace.set_tracer_provider(tracer_provider) + + # Get the tracer + tracer = trace.get_tracer(__name__) + + def trace_injector() -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier=headers) + return headers + + self.client = DaprClient( + f'https://localhost:{self.server_port}', + headers_callback=trace_injector, + ) + self.server.set_response(b'FOO') + + with tracer.start_as_current_span(name='test'): + req = common_v1.StateItem(key='test') + resp = self.client.invoke_method( + self.app_id, + self.method_name, + http_verb='PUT', + data=req, + ) + + request_headers: typing.Dict[str, str] = self.server.get_request_headers() + + self.assertIn('Traceparent', request_headers) + self.assertEqual(b'FOO', resp.data) + + def test_timeout_exception_thrown_when_timeout_reached(self): + new_client = DaprClient(f'https://localhost:{self.server_port}', http_timeout_seconds=1) + self.server.set_server_delay(1.5) + with self.assertRaises(TimeoutError): + new_client.invoke_method(self.app_id, self.method_name, '') diff --git a/tests/clients/test_subscription.py b/tests/clients/test_subscription.py index ed2eae3fa..6f74b1dc9 100644 --- a/tests/clients/test_subscription.py +++ b/tests/clients/test_subscription.py @@ -1,109 +1,109 @@ -from dapr.clients.grpc.subscription import SubscriptionMessage -from dapr.proto.runtime.v1.appcallback_pb2 import TopicEventRequest -from google.protobuf.struct_pb2 import Struct - -import unittest - - -class SubscriptionMessageTests(unittest.TestCase): - def test_subscription_message_init_raw_text(self): - extensions = Struct() - extensions['field1'] = 'value1' - extensions['field2'] = 42 - extensions['field3'] = True - - msg = TopicEventRequest( - id='id', - data=b'hello', - data_content_type='text/plain', - topic='topicA', - pubsub_name='pubsub_name', - source='source', - type='type', - spec_version='spec_version', - path='path', - extensions=extensions, - ) - subscription_message = SubscriptionMessage(msg=msg) - - self.assertEqual('id', subscription_message.id()) - self.assertEqual('source', subscription_message.source()) - self.assertEqual('type', subscription_message.type()) - self.assertEqual('spec_version', subscription_message.spec_version()) - self.assertEqual('text/plain', subscription_message.data_content_type()) - self.assertEqual('topicA', subscription_message.topic()) - self.assertEqual('pubsub_name', subscription_message.pubsub_name()) - self.assertEqual(b'hello', subscription_message.raw_data()) - self.assertEqual('hello', subscription_message.data()) - self.assertEqual( - {'field1': 'value1', 'field2': 42, 'field3': True}, subscription_message.extensions() - ) - - def test_subscription_message_init_raw_text_non_utf(self): - msg = TopicEventRequest( - id='id', - data=b'\x80\x81\x82', - data_content_type='text/plain', - topic='topicA', - pubsub_name='pubsub_name', - source='source', - type='type', - spec_version='spec_version', - path='path', - ) - subscription_message = SubscriptionMessage(msg=msg) - - self.assertEqual(b'\x80\x81\x82', subscription_message.raw_data()) - self.assertIsNone(subscription_message.data()) - - def test_subscription_message_init_json(self): - msg = TopicEventRequest( - id='id', - data=b'{"a": 1}', - data_content_type='application/json', - topic='topicA', - pubsub_name='pubsub_name', - source='source', - type='type', - spec_version='spec_version', - path='path', - ) - subscription_message = SubscriptionMessage(msg=msg) - - self.assertEqual(b'{"a": 1}', subscription_message.raw_data()) - self.assertEqual({'a': 1}, subscription_message.data()) - print(subscription_message.data()['a']) - - def test_subscription_message_init_json_faimly(self): - msg = TopicEventRequest( - id='id', - data=b'{"a": 1}', - data_content_type='application/vnd.api+json', - topic='topicA', - pubsub_name='pubsub_name', - source='source', - type='type', - spec_version='spec_version', - path='path', - ) - subscription_message = SubscriptionMessage(msg=msg) - - self.assertEqual(b'{"a": 1}', subscription_message.raw_data()) - self.assertEqual({'a': 1}, subscription_message.data()) - - def test_subscription_message_init_unknown_content_type(self): - msg = TopicEventRequest( - id='id', - data=b'{"a": 1}', - data_content_type='unknown/content-type', - topic='topicA', - pubsub_name='pubsub_name', - source='source', - type='type', - spec_version='spec_version', - path='path', - ) - subscription_message = SubscriptionMessage(msg=msg) - - self.assertEqual(b'{"a": 1}', subscription_message.raw_data()) - self.assertIsNone(subscription_message.data()) +from dapr.clients.grpc.subscription import SubscriptionMessage +from dapr.proto.runtime.v1.appcallback_pb2 import TopicEventRequest +from google.protobuf.struct_pb2 import Struct + +import unittest + + +class SubscriptionMessageTests(unittest.TestCase): + def test_subscription_message_init_raw_text(self): + extensions = Struct() + extensions['field1'] = 'value1' + extensions['field2'] = 42 + extensions['field3'] = True + + msg = TopicEventRequest( + id='id', + data=b'hello', + data_content_type='text/plain', + topic='topicA', + pubsub_name='pubsub_name', + source='source', + type='type', + spec_version='spec_version', + path='path', + extensions=extensions, + ) + subscription_message = SubscriptionMessage(msg=msg) + + self.assertEqual('id', subscription_message.id()) + self.assertEqual('source', subscription_message.source()) + self.assertEqual('type', subscription_message.type()) + self.assertEqual('spec_version', subscription_message.spec_version()) + self.assertEqual('text/plain', subscription_message.data_content_type()) + self.assertEqual('topicA', subscription_message.topic()) + self.assertEqual('pubsub_name', subscription_message.pubsub_name()) + self.assertEqual(b'hello', subscription_message.raw_data()) + self.assertEqual('hello', subscription_message.data()) + self.assertEqual( + {'field1': 'value1', 'field2': 42, 'field3': True}, subscription_message.extensions() + ) + + def test_subscription_message_init_raw_text_non_utf(self): + msg = TopicEventRequest( + id='id', + data=b'\x80\x81\x82', + data_content_type='text/plain', + topic='topicA', + pubsub_name='pubsub_name', + source='source', + type='type', + spec_version='spec_version', + path='path', + ) + subscription_message = SubscriptionMessage(msg=msg) + + self.assertEqual(b'\x80\x81\x82', subscription_message.raw_data()) + self.assertIsNone(subscription_message.data()) + + def test_subscription_message_init_json(self): + msg = TopicEventRequest( + id='id', + data=b'{"a": 1}', + data_content_type='application/json', + topic='topicA', + pubsub_name='pubsub_name', + source='source', + type='type', + spec_version='spec_version', + path='path', + ) + subscription_message = SubscriptionMessage(msg=msg) + + self.assertEqual(b'{"a": 1}', subscription_message.raw_data()) + self.assertEqual({'a': 1}, subscription_message.data()) + print(subscription_message.data()['a']) + + def test_subscription_message_init_json_faimly(self): + msg = TopicEventRequest( + id='id', + data=b'{"a": 1}', + data_content_type='application/vnd.api+json', + topic='topicA', + pubsub_name='pubsub_name', + source='source', + type='type', + spec_version='spec_version', + path='path', + ) + subscription_message = SubscriptionMessage(msg=msg) + + self.assertEqual(b'{"a": 1}', subscription_message.raw_data()) + self.assertEqual({'a': 1}, subscription_message.data()) + + def test_subscription_message_init_unknown_content_type(self): + msg = TopicEventRequest( + id='id', + data=b'{"a": 1}', + data_content_type='unknown/content-type', + topic='topicA', + pubsub_name='pubsub_name', + source='source', + type='type', + spec_version='spec_version', + path='path', + ) + subscription_message = SubscriptionMessage(msg=msg) + + self.assertEqual(b'{"a": 1}', subscription_message.raw_data()) + self.assertIsNone(subscription_message.data()) diff --git a/tests/clients/test_timeout_interceptor.py b/tests/clients/test_timeout_interceptor.py index 79859b2e5..7d98d6882 100644 --- a/tests/clients/test_timeout_interceptor.py +++ b/tests/clients/test_timeout_interceptor.py @@ -1,55 +1,55 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2024 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -from unittest.mock import Mock, patch -from dapr.clients.grpc.interceptors import DaprClientTimeoutInterceptor -from dapr.conf import settings - - -class DaprClientTimeoutInterceptorTests(unittest.TestCase): - def test_intercept_unary_unary_with_timeout(self): - continuation = Mock() - request = Mock() - client_call_details = Mock() - client_call_details.method = 'method' - client_call_details.timeout = 10 - client_call_details.metadata = 'metadata' - client_call_details.credentials = 'credentials' - client_call_details.wait_for_ready = 'wait_for_ready' - client_call_details.compression = 'compression' - - DaprClientTimeoutInterceptor().intercept_unary_unary( - continuation, client_call_details, request - ) - continuation.assert_called_once_with(client_call_details, request) - - @patch.object(settings, 'DAPR_API_TIMEOUT_SECONDS', 7) - def test_intercept_unary_unary_without_timeout(self): - continuation = Mock() - request = Mock() - client_call_details = Mock() - client_call_details.method = 'method' - client_call_details.timeout = None - client_call_details.metadata = 'metadata' - client_call_details.credentials = 'credentials' - client_call_details.wait_for_ready = 'wait_for_ready' - client_call_details.compression = 'compression' - - DaprClientTimeoutInterceptor().intercept_unary_unary( - continuation, client_call_details, request - ) - called_client_call_details = continuation.call_args[0][0] - self.assertEqual(7, called_client_call_details.timeout) +# -*- coding: utf-8 -*- + +""" +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +from unittest.mock import Mock, patch +from dapr.clients.grpc.interceptors import DaprClientTimeoutInterceptor +from dapr.conf import settings + + +class DaprClientTimeoutInterceptorTests(unittest.TestCase): + def test_intercept_unary_unary_with_timeout(self): + continuation = Mock() + request = Mock() + client_call_details = Mock() + client_call_details.method = 'method' + client_call_details.timeout = 10 + client_call_details.metadata = 'metadata' + client_call_details.credentials = 'credentials' + client_call_details.wait_for_ready = 'wait_for_ready' + client_call_details.compression = 'compression' + + DaprClientTimeoutInterceptor().intercept_unary_unary( + continuation, client_call_details, request + ) + continuation.assert_called_once_with(client_call_details, request) + + @patch.object(settings, 'DAPR_API_TIMEOUT_SECONDS', 7) + def test_intercept_unary_unary_without_timeout(self): + continuation = Mock() + request = Mock() + client_call_details = Mock() + client_call_details.method = 'method' + client_call_details.timeout = None + client_call_details.metadata = 'metadata' + client_call_details.credentials = 'credentials' + client_call_details.wait_for_ready = 'wait_for_ready' + client_call_details.compression = 'compression' + + DaprClientTimeoutInterceptor().intercept_unary_unary( + continuation, client_call_details, request + ) + called_client_call_details = continuation.call_args[0][0] + self.assertEqual(7, called_client_call_details.timeout) diff --git a/tests/clients/test_timeout_interceptor_async.py b/tests/clients/test_timeout_interceptor_async.py index d057df9fc..f217bdadb 100644 --- a/tests/clients/test_timeout_interceptor_async.py +++ b/tests/clients/test_timeout_interceptor_async.py @@ -1,53 +1,53 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2024 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -from unittest.mock import Mock, patch -from dapr.aio.clients.grpc.interceptors import DaprClientTimeoutInterceptorAsync -from dapr.conf import settings - - -class DaprClientTimeoutInterceptorAsyncTests(unittest.TestCase): - def test_intercept_unary_unary_with_timeout(self): - continuation = Mock() - request = Mock() - client_call_details = Mock() - client_call_details.method = 'method' - client_call_details.timeout = 10 - client_call_details.metadata = 'metadata' - client_call_details.credentials = 'credentials' - client_call_details.wait_for_ready = 'wait_for_ready' - - DaprClientTimeoutInterceptorAsync().intercept_unary_unary( - continuation, client_call_details, request - ) - continuation.assert_called_once_with(client_call_details, request) - - @patch.object(settings, 'DAPR_API_TIMEOUT_SECONDS', 7) - def test_intercept_unary_unary_without_timeout(self): - continuation = Mock() - request = Mock() - client_call_details = Mock() - client_call_details.method = 'method' - client_call_details.timeout = None - client_call_details.metadata = 'metadata' - client_call_details.credentials = 'credentials' - client_call_details.wait_for_ready = 'wait_for_ready' - - DaprClientTimeoutInterceptorAsync().intercept_unary_unary( - continuation, client_call_details, request - ) - called_client_call_details = continuation.call_args[0][0] - self.assertEqual(7, called_client_call_details.timeout) +# -*- coding: utf-8 -*- + +""" +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +from unittest.mock import Mock, patch +from dapr.aio.clients.grpc.interceptors import DaprClientTimeoutInterceptorAsync +from dapr.conf import settings + + +class DaprClientTimeoutInterceptorAsyncTests(unittest.TestCase): + def test_intercept_unary_unary_with_timeout(self): + continuation = Mock() + request = Mock() + client_call_details = Mock() + client_call_details.method = 'method' + client_call_details.timeout = 10 + client_call_details.metadata = 'metadata' + client_call_details.credentials = 'credentials' + client_call_details.wait_for_ready = 'wait_for_ready' + + DaprClientTimeoutInterceptorAsync().intercept_unary_unary( + continuation, client_call_details, request + ) + continuation.assert_called_once_with(client_call_details, request) + + @patch.object(settings, 'DAPR_API_TIMEOUT_SECONDS', 7) + def test_intercept_unary_unary_without_timeout(self): + continuation = Mock() + request = Mock() + client_call_details = Mock() + client_call_details.method = 'method' + client_call_details.timeout = None + client_call_details.metadata = 'metadata' + client_call_details.credentials = 'credentials' + client_call_details.wait_for_ready = 'wait_for_ready' + + DaprClientTimeoutInterceptorAsync().intercept_unary_unary( + continuation, client_call_details, request + ) + called_client_call_details = continuation.call_args[0][0] + self.assertEqual(7, called_client_call_details.timeout) diff --git a/tests/conf/helpers_test.py b/tests/conf/helpers_test.py index d09eb3d9d..ddfa1fde4 100644 --- a/tests/conf/helpers_test.py +++ b/tests/conf/helpers_test.py @@ -1,361 +1,361 @@ -import unittest - -from dapr.conf.helpers import GrpcEndpoint - - -class DaprClientHelpersTests(unittest.TestCase): - def test_parse_grpc_endpoint(self): - testcases = [ - # Port only - { - 'url': ':5000', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'localhost', - 'port': 5000, - 'endpoint': 'dns:localhost:5000', - }, - { - 'url': ':5000?tls=false', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'localhost', - 'port': 5000, - 'endpoint': 'dns:localhost:5000', - }, - { - 'url': ':5000?tls=true', - 'error': False, - 'secure': True, - 'scheme': '', - 'host': 'localhost', - 'port': 5000, - 'endpoint': 'dns:localhost:5000', - }, - # Host only - { - 'url': 'myhost', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - { - 'url': 'myhost?tls=false', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - { - 'url': 'myhost?tls=true', - 'error': False, - 'secure': True, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - # Host and port - { - 'url': 'myhost:443', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - { - 'url': 'myhost:443?tls=false', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - { - 'url': 'myhost:443?tls=true', - 'error': False, - 'secure': True, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - # Scheme, host and port - { - 'url': 'http://myhost', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - {'url': 'http://myhost?tls=false', 'error': True}, - # We can't have both http/https and the tls query parameter - {'url': 'http://myhost?tls=true', 'error': True}, - # We can't have both http/https and the tls query parameter - { - 'url': 'http://myhost:443', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - {'url': 'http://myhost:443?tls=false', 'error': True}, - # We can't have both http/https and the tls query parameter - {'url': 'http://myhost:443?tls=true', 'error': True}, - # We can't have both http/https and the tls query parameter - { - 'url': 'http://myhost:5000', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': 'myhost', - 'port': 5000, - 'endpoint': 'dns:myhost:5000', - }, - {'url': 'http://myhost:5000?tls=false', 'error': True}, - # We can't have both http/https and the tls query parameter - {'url': 'http://myhost:5000?tls=true', 'error': True}, - # We can't have both http/https and the tls query parameter - { - 'url': 'https://myhost:443', - 'error': False, - 'secure': True, - 'scheme': '', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - {'url': 'https://myhost:443?tls=false', 'error': True}, - {'url': 'https://myhost:443?tls=true', 'error': True}, - # Scheme = dns - { - 'url': 'dns:myhost', - 'error': False, - 'secure': False, - 'scheme': 'dns', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - { - 'url': 'dns:myhost?tls=false', - 'error': False, - 'secure': False, - 'scheme': 'dns', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - { - 'url': 'dns:myhost?tls=true', - 'error': False, - 'secure': True, - 'scheme': 'dns', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns:myhost:443', - }, - # Scheme = dns with authority - { - 'url': 'dns://myauthority:53/myhost', - 'error': False, - 'secure': False, - 'scheme': 'dns', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns://myauthority:53/myhost:443', - }, - { - 'url': 'dns://myauthority:53/myhost?tls=false', - 'error': False, - 'secure': False, - 'scheme': 'dns', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns://myauthority:53/myhost:443', - }, - { - 'url': 'dns://myauthority:53/myhost?tls=true', - 'error': False, - 'secure': True, - 'scheme': 'dns', - 'host': 'myhost', - 'port': 443, - 'endpoint': 'dns://myauthority:53/myhost:443', - }, - {'url': 'dns://myhost', 'error': True}, - # Unix sockets - { - 'url': 'unix:my.sock', - 'error': False, - 'secure': False, - 'scheme': 'unix', - 'host': 'my.sock', - 'port': '', - 'endpoint': 'unix:my.sock', - }, - { - 'url': 'unix:my.sock?tls=true', - 'error': False, - 'secure': True, - 'scheme': 'unix', - 'host': 'my.sock', - 'port': '', - 'endpoint': 'unix:my.sock', - }, - # Unix sockets with absolute path - { - 'url': 'unix://my.sock', - 'error': False, - 'secure': False, - 'scheme': 'unix', - 'host': 'my.sock', - 'port': '', - 'endpoint': 'unix://my.sock', - }, - { - 'url': 'unix://my.sock?tls=true', - 'error': False, - 'secure': True, - 'scheme': 'unix', - 'host': 'my.sock', - 'port': '', - 'endpoint': 'unix://my.sock', - }, - # Unix abstract sockets - { - 'url': 'unix-abstract:my.sock', - 'error': False, - 'secure': False, - 'scheme': 'unix', - 'host': 'my.sock', - 'port': '', - 'endpoint': 'unix-abstract:my.sock', - }, - { - 'url': 'unix-abstract:my.sock?tls=true', - 'error': False, - 'secure': True, - 'scheme': 'unix', - 'host': 'my.sock', - 'port': '', - 'endpoint': 'unix-abstract:my.sock', - }, - # Vsock - { - 'url': 'vsock:mycid', - 'error': False, - 'secure': False, - 'scheme': 'vsock', - 'host': 'mycid', - 'port': '443', - 'endpoint': 'vsock:mycid:443', - }, - { - 'url': 'vsock:mycid:5000', - 'error': False, - 'secure': False, - 'scheme': 'vsock', - 'host': 'mycid', - 'port': 5000, - 'endpoint': 'vsock:mycid:5000', - }, - { - 'url': 'vsock:mycid:5000?tls=true', - 'error': False, - 'secure': True, - 'scheme': 'vsock', - 'host': 'mycid', - 'port': 5000, - 'endpoint': 'vsock:mycid:5000', - }, - # IPv6 addresses with dns scheme - { - 'url': '[2001:db8:1f70::999:de8:7648:6e8]', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': '[2001:db8:1f70::999:de8:7648:6e8]', - 'port': 443, - 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:443', - }, - { - 'url': 'dns:[2001:db8:1f70::999:de8:7648:6e8]', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': '[2001:db8:1f70::999:de8:7648:6e8]', - 'port': 443, - 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:443', - }, - { - 'url': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000', - 'error': False, - 'secure': False, - 'scheme': '', - 'host': '[2001:db8:1f70::999:de8:7648:6e8]', - 'port': 5000, - 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000', - }, - {'url': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000?abc=[]', 'error': True}, - # IPv6 addresses with dns scheme and authority - { - 'url': 'dns://myauthority:53/[2001:db8:1f70::999:de8:7648:6e8]', - 'error': False, - 'secure': False, - 'scheme': 'dns', - 'host': '[2001:db8:1f70::999:de8:7648:6e8]', - 'port': 443, - 'endpoint': 'dns://myauthority:53/[2001:db8:1f70::999:de8:7648:6e8]:443', - }, - # IPv6 addresses with https scheme - { - 'url': 'https://[2001:db8:1f70::999:de8:7648:6e8]', - 'error': False, - 'secure': True, - 'scheme': '', - 'host': '[2001:db8:1f70::999:de8:7648:6e8]', - 'port': 443, - 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:443', - }, - { - 'url': 'https://[2001:db8:1f70::999:de8:7648:6e8]:5000', - 'error': False, - 'secure': True, - 'scheme': '', - 'host': '[2001:db8:1f70::999:de8:7648:6e8]', - 'port': 5000, - 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000', - }, - # Invalid addresses (with path and queries) - {'url': 'host:5000/v1/dapr', 'error': True}, # Paths are not allowed in grpc endpoints - {'url': 'host:5000/?a=1', 'error': True}, # Query params not allowed in grpc endpoints - # Invalid scheme - {'url': 'inv-scheme://myhost', 'error': True}, - {'url': 'inv-scheme:myhost:5000', 'error': True}, - ] - - for testcase in testcases: - if testcase['error']: - with self.assertRaises(ValueError): - GrpcEndpoint(testcase['url']) - else: - url = GrpcEndpoint(testcase['url']) - assert url.endpoint == testcase['endpoint'] - assert url.tls == testcase['secure'] - assert url.hostname == testcase['host'] - assert url.port == str(testcase['port']) +import unittest + +from dapr.conf.helpers import GrpcEndpoint + + +class DaprClientHelpersTests(unittest.TestCase): + def test_parse_grpc_endpoint(self): + testcases = [ + # Port only + { + 'url': ':5000', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'localhost', + 'port': 5000, + 'endpoint': 'dns:localhost:5000', + }, + { + 'url': ':5000?tls=false', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'localhost', + 'port': 5000, + 'endpoint': 'dns:localhost:5000', + }, + { + 'url': ':5000?tls=true', + 'error': False, + 'secure': True, + 'scheme': '', + 'host': 'localhost', + 'port': 5000, + 'endpoint': 'dns:localhost:5000', + }, + # Host only + { + 'url': 'myhost', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + { + 'url': 'myhost?tls=false', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + { + 'url': 'myhost?tls=true', + 'error': False, + 'secure': True, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + # Host and port + { + 'url': 'myhost:443', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + { + 'url': 'myhost:443?tls=false', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + { + 'url': 'myhost:443?tls=true', + 'error': False, + 'secure': True, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + # Scheme, host and port + { + 'url': 'http://myhost', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + {'url': 'http://myhost?tls=false', 'error': True}, + # We can't have both http/https and the tls query parameter + {'url': 'http://myhost?tls=true', 'error': True}, + # We can't have both http/https and the tls query parameter + { + 'url': 'http://myhost:443', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + {'url': 'http://myhost:443?tls=false', 'error': True}, + # We can't have both http/https and the tls query parameter + {'url': 'http://myhost:443?tls=true', 'error': True}, + # We can't have both http/https and the tls query parameter + { + 'url': 'http://myhost:5000', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': 'myhost', + 'port': 5000, + 'endpoint': 'dns:myhost:5000', + }, + {'url': 'http://myhost:5000?tls=false', 'error': True}, + # We can't have both http/https and the tls query parameter + {'url': 'http://myhost:5000?tls=true', 'error': True}, + # We can't have both http/https and the tls query parameter + { + 'url': 'https://myhost:443', + 'error': False, + 'secure': True, + 'scheme': '', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + {'url': 'https://myhost:443?tls=false', 'error': True}, + {'url': 'https://myhost:443?tls=true', 'error': True}, + # Scheme = dns + { + 'url': 'dns:myhost', + 'error': False, + 'secure': False, + 'scheme': 'dns', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + { + 'url': 'dns:myhost?tls=false', + 'error': False, + 'secure': False, + 'scheme': 'dns', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + { + 'url': 'dns:myhost?tls=true', + 'error': False, + 'secure': True, + 'scheme': 'dns', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns:myhost:443', + }, + # Scheme = dns with authority + { + 'url': 'dns://myauthority:53/myhost', + 'error': False, + 'secure': False, + 'scheme': 'dns', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns://myauthority:53/myhost:443', + }, + { + 'url': 'dns://myauthority:53/myhost?tls=false', + 'error': False, + 'secure': False, + 'scheme': 'dns', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns://myauthority:53/myhost:443', + }, + { + 'url': 'dns://myauthority:53/myhost?tls=true', + 'error': False, + 'secure': True, + 'scheme': 'dns', + 'host': 'myhost', + 'port': 443, + 'endpoint': 'dns://myauthority:53/myhost:443', + }, + {'url': 'dns://myhost', 'error': True}, + # Unix sockets + { + 'url': 'unix:my.sock', + 'error': False, + 'secure': False, + 'scheme': 'unix', + 'host': 'my.sock', + 'port': '', + 'endpoint': 'unix:my.sock', + }, + { + 'url': 'unix:my.sock?tls=true', + 'error': False, + 'secure': True, + 'scheme': 'unix', + 'host': 'my.sock', + 'port': '', + 'endpoint': 'unix:my.sock', + }, + # Unix sockets with absolute path + { + 'url': 'unix://my.sock', + 'error': False, + 'secure': False, + 'scheme': 'unix', + 'host': 'my.sock', + 'port': '', + 'endpoint': 'unix://my.sock', + }, + { + 'url': 'unix://my.sock?tls=true', + 'error': False, + 'secure': True, + 'scheme': 'unix', + 'host': 'my.sock', + 'port': '', + 'endpoint': 'unix://my.sock', + }, + # Unix abstract sockets + { + 'url': 'unix-abstract:my.sock', + 'error': False, + 'secure': False, + 'scheme': 'unix', + 'host': 'my.sock', + 'port': '', + 'endpoint': 'unix-abstract:my.sock', + }, + { + 'url': 'unix-abstract:my.sock?tls=true', + 'error': False, + 'secure': True, + 'scheme': 'unix', + 'host': 'my.sock', + 'port': '', + 'endpoint': 'unix-abstract:my.sock', + }, + # Vsock + { + 'url': 'vsock:mycid', + 'error': False, + 'secure': False, + 'scheme': 'vsock', + 'host': 'mycid', + 'port': '443', + 'endpoint': 'vsock:mycid:443', + }, + { + 'url': 'vsock:mycid:5000', + 'error': False, + 'secure': False, + 'scheme': 'vsock', + 'host': 'mycid', + 'port': 5000, + 'endpoint': 'vsock:mycid:5000', + }, + { + 'url': 'vsock:mycid:5000?tls=true', + 'error': False, + 'secure': True, + 'scheme': 'vsock', + 'host': 'mycid', + 'port': 5000, + 'endpoint': 'vsock:mycid:5000', + }, + # IPv6 addresses with dns scheme + { + 'url': '[2001:db8:1f70::999:de8:7648:6e8]', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': '[2001:db8:1f70::999:de8:7648:6e8]', + 'port': 443, + 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:443', + }, + { + 'url': 'dns:[2001:db8:1f70::999:de8:7648:6e8]', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': '[2001:db8:1f70::999:de8:7648:6e8]', + 'port': 443, + 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:443', + }, + { + 'url': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000', + 'error': False, + 'secure': False, + 'scheme': '', + 'host': '[2001:db8:1f70::999:de8:7648:6e8]', + 'port': 5000, + 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000', + }, + {'url': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000?abc=[]', 'error': True}, + # IPv6 addresses with dns scheme and authority + { + 'url': 'dns://myauthority:53/[2001:db8:1f70::999:de8:7648:6e8]', + 'error': False, + 'secure': False, + 'scheme': 'dns', + 'host': '[2001:db8:1f70::999:de8:7648:6e8]', + 'port': 443, + 'endpoint': 'dns://myauthority:53/[2001:db8:1f70::999:de8:7648:6e8]:443', + }, + # IPv6 addresses with https scheme + { + 'url': 'https://[2001:db8:1f70::999:de8:7648:6e8]', + 'error': False, + 'secure': True, + 'scheme': '', + 'host': '[2001:db8:1f70::999:de8:7648:6e8]', + 'port': 443, + 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:443', + }, + { + 'url': 'https://[2001:db8:1f70::999:de8:7648:6e8]:5000', + 'error': False, + 'secure': True, + 'scheme': '', + 'host': '[2001:db8:1f70::999:de8:7648:6e8]', + 'port': 5000, + 'endpoint': 'dns:[2001:db8:1f70::999:de8:7648:6e8]:5000', + }, + # Invalid addresses (with path and queries) + {'url': 'host:5000/v1/dapr', 'error': True}, # Paths are not allowed in grpc endpoints + {'url': 'host:5000/?a=1', 'error': True}, # Query params not allowed in grpc endpoints + # Invalid scheme + {'url': 'inv-scheme://myhost', 'error': True}, + {'url': 'inv-scheme:myhost:5000', 'error': True}, + ] + + for testcase in testcases: + if testcase['error']: + with self.assertRaises(ValueError): + GrpcEndpoint(testcase['url']) + else: + url = GrpcEndpoint(testcase['url']) + assert url.endpoint == testcase['endpoint'] + assert url.tls == testcase['secure'] + assert url.hostname == testcase['host'] + assert url.port == str(testcase['port']) diff --git a/tests/serializers/__init__.py b/tests/serializers/__init__.py index c9c4314fb..26b94b07d 100644 --- a/tests/serializers/__init__.py +++ b/tests/serializers/__init__.py @@ -1,14 +1,14 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" diff --git a/tests/serializers/test_default_json_serializer.py b/tests/serializers/test_default_json_serializer.py index 86e727ad0..c6a36ae18 100644 --- a/tests/serializers/test_default_json_serializer.py +++ b/tests/serializers/test_default_json_serializer.py @@ -1,82 +1,82 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -import datetime - -from dapr.serializers.json import DefaultJSONSerializer - - -class DefaultJSONSerializerTests(unittest.TestCase): - def test_serialize(self): - serializer = DefaultJSONSerializer() - fakeDateTime = datetime.datetime( - year=2020, - month=1, - day=1, - hour=1, - minute=0, - second=0, - microsecond=0, - tzinfo=datetime.timezone.utc, - ) - input_dict_obj = { - 'propertyDecimal': 10, - 'propertyStr': 'StrValue', - 'propertyDateTime': fakeDateTime, - } - serialized = serializer.serialize(input_dict_obj) - self.assertEqual( - serialized, - b'{"propertyDecimal":10,"propertyStr":"StrValue","propertyDateTime":"2020-01-01T01:00:00Z"}', - ) # noqa: E501 - - def test_serialize_bytes(self): - serializer = DefaultJSONSerializer() - - # Serialize`bytes data - serialized = serializer.serialize(b'bytes_data') - self.assertEqual(b'"Ynl0ZXNfZGF0YQ=="', serialized) - - # Serialize`bytes property - input_dict_obj = {'propertyBytes': b'bytes_property'} - serialized = serializer.serialize(input_dict_obj) - self.assertEqual(serialized, b'{"propertyBytes":"Ynl0ZXNfcHJvcGVydHk="}') - - def test_deserialize(self): - serializer = DefaultJSONSerializer() - payload = b'{"propertyDecimal":10,"propertyStr":"StrValue","propertyDateTime":"2020-01-01T01:00:00Z"}' # noqa: E501 - - obj = serializer.deserialize(payload) - self.assertEqual(obj['propertyDecimal'], 10) - self.assertEqual(obj['propertyStr'], 'StrValue') - self.assertTrue(isinstance(obj['propertyDateTime'], datetime.datetime)) - self.assertEqual( - obj['propertyDateTime'], - datetime.datetime( - year=2020, - month=1, - day=1, - hour=1, - minute=0, - second=0, - microsecond=0, - tzinfo=datetime.timezone.utc, - ), - ) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +import datetime + +from dapr.serializers.json import DefaultJSONSerializer + + +class DefaultJSONSerializerTests(unittest.TestCase): + def test_serialize(self): + serializer = DefaultJSONSerializer() + fakeDateTime = datetime.datetime( + year=2020, + month=1, + day=1, + hour=1, + minute=0, + second=0, + microsecond=0, + tzinfo=datetime.timezone.utc, + ) + input_dict_obj = { + 'propertyDecimal': 10, + 'propertyStr': 'StrValue', + 'propertyDateTime': fakeDateTime, + } + serialized = serializer.serialize(input_dict_obj) + self.assertEqual( + serialized, + b'{"propertyDecimal":10,"propertyStr":"StrValue","propertyDateTime":"2020-01-01T01:00:00Z"}', + ) # noqa: E501 + + def test_serialize_bytes(self): + serializer = DefaultJSONSerializer() + + # Serialize`bytes data + serialized = serializer.serialize(b'bytes_data') + self.assertEqual(b'"Ynl0ZXNfZGF0YQ=="', serialized) + + # Serialize`bytes property + input_dict_obj = {'propertyBytes': b'bytes_property'} + serialized = serializer.serialize(input_dict_obj) + self.assertEqual(serialized, b'{"propertyBytes":"Ynl0ZXNfcHJvcGVydHk="}') + + def test_deserialize(self): + serializer = DefaultJSONSerializer() + payload = b'{"propertyDecimal":10,"propertyStr":"StrValue","propertyDateTime":"2020-01-01T01:00:00Z"}' # noqa: E501 + + obj = serializer.deserialize(payload) + self.assertEqual(obj['propertyDecimal'], 10) + self.assertEqual(obj['propertyStr'], 'StrValue') + self.assertTrue(isinstance(obj['propertyDateTime'], datetime.datetime)) + self.assertEqual( + obj['propertyDateTime'], + datetime.datetime( + year=2020, + month=1, + day=1, + hour=1, + minute=0, + second=0, + microsecond=0, + tzinfo=datetime.timezone.utc, + ), + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/serializers/test_util.py b/tests/serializers/test_util.py index 9f3b9e026..86e87b4a2 100644 --- a/tests/serializers/test_util.py +++ b/tests/serializers/test_util.py @@ -1,79 +1,79 @@ -# -*- coding: utf-8 -*- - -""" -Copyright 2021 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -""" - -import unittest -import json -from datetime import timedelta - -from dapr.serializers.util import convert_from_dapr_duration, convert_to_dapr_duration -from dapr.serializers.json import DaprJSONDecoder - - -class UtilTests(unittest.TestCase): - def setUp(self): - pass - - def test_convert_hour_mins_secs(self): - delta = convert_from_dapr_duration('4h15m40s') - self.assertEqual(delta.total_seconds(), 15340.0) - - def test_convert_mins_secs(self): - delta = convert_from_dapr_duration('15m40s') - self.assertEqual(delta.total_seconds(), 940.0) - - def test_convert_secs(self): - delta = convert_from_dapr_duration('40s') - self.assertEqual(delta.total_seconds(), 40.0) - - def test_convert_millisecs(self): - delta = convert_from_dapr_duration('123ms') - self.assertEqual(delta.total_seconds(), 0.123) - - def test_convert_microsecs_μs(self): - delta = convert_from_dapr_duration('123μs') - self.assertEqual(delta.microseconds, 123) - - def test_convert_microsecs_us(self): - delta = convert_from_dapr_duration('345us') - self.assertEqual(delta.microseconds, 345) - - def test_convert_invalid_duration(self): - with self.assertRaises(ValueError) as exeception_context: - convert_from_dapr_duration('invalid') - self.assertEqual( - exeception_context.exception.args[0], - "Invalid Dapr Duration format: '{}'".format('invalid'), - ) - - def test_convert_timedelta_to_dapr_duration(self): - duration = convert_to_dapr_duration( - timedelta(hours=4, minutes=15, seconds=40, milliseconds=123, microseconds=35) - ) - self.assertEqual(duration, '4h15m40s123ms35μs') - - def test_convert_invalid_duration_string(self): - TESTSTRING = '4h15m40s123ms35μshello' - with self.assertRaises(ValueError) as exeception_context: - convert_from_dapr_duration(TESTSTRING) - self.assertEqual( - exeception_context.exception.args[0], - "Invalid Dapr Duration format: '{}'".format(TESTSTRING), - ) - decoded = json.loads(json.dumps({'somevar': TESTSTRING}), cls=DaprJSONDecoder) - self.assertEqual(decoded['somevar'], TESTSTRING) - - -if __name__ == '__main__': - unittest.main() +# -*- coding: utf-8 -*- + +""" +Copyright 2021 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import unittest +import json +from datetime import timedelta + +from dapr.serializers.util import convert_from_dapr_duration, convert_to_dapr_duration +from dapr.serializers.json import DaprJSONDecoder + + +class UtilTests(unittest.TestCase): + def setUp(self): + pass + + def test_convert_hour_mins_secs(self): + delta = convert_from_dapr_duration('4h15m40s') + self.assertEqual(delta.total_seconds(), 15340.0) + + def test_convert_mins_secs(self): + delta = convert_from_dapr_duration('15m40s') + self.assertEqual(delta.total_seconds(), 940.0) + + def test_convert_secs(self): + delta = convert_from_dapr_duration('40s') + self.assertEqual(delta.total_seconds(), 40.0) + + def test_convert_millisecs(self): + delta = convert_from_dapr_duration('123ms') + self.assertEqual(delta.total_seconds(), 0.123) + + def test_convert_microsecs_μs(self): + delta = convert_from_dapr_duration('123μs') + self.assertEqual(delta.microseconds, 123) + + def test_convert_microsecs_us(self): + delta = convert_from_dapr_duration('345us') + self.assertEqual(delta.microseconds, 345) + + def test_convert_invalid_duration(self): + with self.assertRaises(ValueError) as exeception_context: + convert_from_dapr_duration('invalid') + self.assertEqual( + exeception_context.exception.args[0], + "Invalid Dapr Duration format: '{}'".format('invalid'), + ) + + def test_convert_timedelta_to_dapr_duration(self): + duration = convert_to_dapr_duration( + timedelta(hours=4, minutes=15, seconds=40, milliseconds=123, microseconds=35) + ) + self.assertEqual(duration, '4h15m40s123ms35μs') + + def test_convert_invalid_duration_string(self): + TESTSTRING = '4h15m40s123ms35μshello' + with self.assertRaises(ValueError) as exeception_context: + convert_from_dapr_duration(TESTSTRING) + self.assertEqual( + exeception_context.exception.args[0], + "Invalid Dapr Duration format: '{}'".format(TESTSTRING), + ) + decoded = json.loads(json.dumps({'somevar': TESTSTRING}), cls=DaprJSONDecoder) + self.assertEqual(decoded['somevar'], TESTSTRING) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/regen_grpcclient.sh b/tools/regen_grpcclient.sh index 5b5816b0d..3b05de669 100755 --- a/tools/regen_grpcclient.sh +++ b/tools/regen_grpcclient.sh @@ -1,108 +1,108 @@ -#!/bin/bash - -# ------------------------------------------------------------ -# Copyright 2021 The Dapr Authors -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ------------------------------------------------------------ - -# Path to store output -PROTO_PATH="dapr/proto" -SRC=. - -# Http request CLI -HTTP_REQUEST_CLI=curl - - -checkHttpRequestCLI() { - if type "curl" > /dev/null; then - HTTP_REQUEST_CLI=curl - elif type "wget" > /dev/null; then - HTTP_REQUEST_CLI=wget - else - echo "Either curl or wget is required" - exit 1 - fi -} - -downloadFile() { - PKG_NAME=$1 - FILE_NAME=$2 - FILE_PATH="${PROTO_PATH}/${PKG_NAME}/v1" - - # URL for proto file - PROTO_URL="https://raw.githubusercontent.com/dapr/dapr/master/dapr/proto/${PKG_NAME}/v1/${FILE_NAME}.proto" - - mkdir -p "${FILE_PATH}" - - echo "Downloading $PROTO_URL ..." - if [ "$HTTP_REQUEST_CLI" == "curl" ]; then - pushd ${FILE_PATH} - curl -SsL "$PROTO_URL" -o "${FILE_NAME}.proto" - popd - else - wget -q -P "$PROTO_URL" "${FILE_PATH}/${FILE_NAME}.proto" - fi - - if [ ! -e "${FILE_PATH}/${FILE_NAME}.proto" ]; then - echo "failed to download $PROTO_URL ..." - ret_val=$FILE_NAME - exit 1 - fi -} - -generateGrpc() { - PKG_NAME=$1 - FILE_NAME=$2 - FILE_PATH="${PROTO_PATH}/${PKG_NAME}/v1" - - python3 -m grpc_tools.protoc -I ${SRC} --python_out=${SRC} --grpc_python_out=${SRC} --mypy_out=${SRC} ${FILE_PATH}/${FILE_NAME}.proto - - if [ ! -e "${FILE_PATH}/${FILE_NAME}_pb2.py" ]; then - echo "failed to generate proto buf $FILE_NAME" - ret_val=$FILE_NAME - exit 1 - fi -} - -fail_trap() { - result=$? - if [ $result != 0 ]; then - echo "Failed to generate gRPC interface and proto buf: $ret_val" - fi - cleanup - exit $result -} - -cleanup() { - find $PROTO_PATH -type f -name '*.proto' -delete -} - -generateGrpcSuccess() { - export PYTHONPATH=`pwd`/$SRC - echo -e "\ngRPC interface and proto buf generated successfully!" -} - -# ----------------------------------------------------------------------------- -# main -# ----------------------------------------------------------------------------- -trap "fail_trap" EXIT - -checkHttpRequestCLI -downloadFile common common -generateGrpc common common -downloadFile runtime appcallback -generateGrpc runtime appcallback -downloadFile runtime dapr -generateGrpc runtime dapr -cleanup - -generateGrpcSuccess - +#!/bin/bash + +# ------------------------------------------------------------ +# Copyright 2021 The Dapr Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ------------------------------------------------------------ + +# Path to store output +PROTO_PATH="dapr/proto" +SRC=. + +# Http request CLI +HTTP_REQUEST_CLI=curl + + +checkHttpRequestCLI() { + if type "curl" > /dev/null; then + HTTP_REQUEST_CLI=curl + elif type "wget" > /dev/null; then + HTTP_REQUEST_CLI=wget + else + echo "Either curl or wget is required" + exit 1 + fi +} + +downloadFile() { + PKG_NAME=$1 + FILE_NAME=$2 + FILE_PATH="${PROTO_PATH}/${PKG_NAME}/v1" + + # URL for proto file + PROTO_URL="https://raw.githubusercontent.com/dapr/dapr/master/dapr/proto/${PKG_NAME}/v1/${FILE_NAME}.proto" + + mkdir -p "${FILE_PATH}" + + echo "Downloading $PROTO_URL ..." + if [ "$HTTP_REQUEST_CLI" == "curl" ]; then + pushd ${FILE_PATH} + curl -SsL "$PROTO_URL" -o "${FILE_NAME}.proto" + popd + else + wget -q -P "$PROTO_URL" "${FILE_PATH}/${FILE_NAME}.proto" + fi + + if [ ! -e "${FILE_PATH}/${FILE_NAME}.proto" ]; then + echo "failed to download $PROTO_URL ..." + ret_val=$FILE_NAME + exit 1 + fi +} + +generateGrpc() { + PKG_NAME=$1 + FILE_NAME=$2 + FILE_PATH="${PROTO_PATH}/${PKG_NAME}/v1" + + python3 -m grpc_tools.protoc -I ${SRC} --python_out=${SRC} --grpc_python_out=${SRC} --mypy_out=${SRC} ${FILE_PATH}/${FILE_NAME}.proto + + if [ ! -e "${FILE_PATH}/${FILE_NAME}_pb2.py" ]; then + echo "failed to generate proto buf $FILE_NAME" + ret_val=$FILE_NAME + exit 1 + fi +} + +fail_trap() { + result=$? + if [ $result != 0 ]; then + echo "Failed to generate gRPC interface and proto buf: $ret_val" + fi + cleanup + exit $result +} + +cleanup() { + find $PROTO_PATH -type f -name '*.proto' -delete +} + +generateGrpcSuccess() { + export PYTHONPATH=`pwd`/$SRC + echo -e "\ngRPC interface and proto buf generated successfully!" +} + +# ----------------------------------------------------------------------------- +# main +# ----------------------------------------------------------------------------- +trap "fail_trap" EXIT + +checkHttpRequestCLI +downloadFile common common +generateGrpc common common +downloadFile runtime appcallback +generateGrpc runtime appcallback +downloadFile runtime dapr +generateGrpc runtime dapr +cleanup + +generateGrpcSuccess + diff --git a/tools/requirements.txt b/tools/requirements.txt index d3d503c91..aa1477b2a 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1 +1 @@ -grpcio-tools>=1.57.0 +grpcio-tools>=1.57.0 diff --git a/tox.ini b/tox.ini index 78f23086a..5e3f9a12c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,98 +1,98 @@ -[tox] -skipsdist = True -minversion = 3.8.0 -envlist = - py{38,39,310,311,312} - flake8, - ruff, - mypy, - -[testenv] -setenv = - PYTHONDONTWRITEBYTECODE=1 -deps = -rdev-requirements.txt -commands = - coverage run -m unittest discover -v ./tests - coverage run -a -m unittest discover -v ./ext/dapr-ext-workflow/tests - coverage run -a -m unittest discover -v ./ext/dapr-ext-grpc/tests - coverage run -a -m unittest discover -v ./ext/dapr-ext-fastapi/tests - coverage run -a -m unittest discover -v ./ext/flask_dapr/tests - coverage xml -commands_pre = - pip3 install -e {toxinidir}/ - pip3 install -e {toxinidir}/ext/dapr-ext-workflow/ - pip3 install -e {toxinidir}/ext/dapr-ext-grpc/ - pip3 install -e {toxinidir}/ext/dapr-ext-fastapi/ - pip3 install -e {toxinidir}/ext/flask_dapr/ - -[testenv:flake8] -basepython = python3 -usedevelop = False -deps = flake8 -commands = - flake8 . - -[testenv:ruff] -basepython = python3 -usedevelop = False -deps = ruff==0.2.2 -commands = - ruff format - -[testenv:examples] -passenv = HOME -basepython = python3 -changedir = ./examples/ -deps = - mechanical-markdown -commands = - ./validate.sh crypto - ./validate.sh metadata - ./validate.sh error_handling - ./validate.sh pubsub-simple - ./validate.sh pubsub-streaming - ./validate.sh pubsub-streaming-async - ./validate.sh state_store - ./validate.sh state_store_query - ./validate.sh secret_store - ./validate.sh invoke-simple - ./validate.sh invoke-custom-data - ./validate.sh demo_actor - ./validate.sh invoke-binding - ./validate.sh grpc_proxying - ./validate.sh w3c-tracing - ./validate.sh distributed_lock - ./validate.sh configuration - ./validate.sh demo_workflow - ./validate.sh workflow - ./validate.sh ../ -commands_pre = - pip3 install -e {toxinidir}/ - pip3 install -e {toxinidir}/ext/dapr-ext-workflow/ - pip3 install -e {toxinidir}/ext/dapr-ext-grpc/ - pip3 install -e {toxinidir}/ext/dapr-ext-fastapi/ -allowlist_externals=* - -[testenv:type] -basepython = python3 -usedevelop = False -deps = -rdev-requirements.txt -commands = - mypy --config-file mypy.ini -commands_pre = - pip3 install -e {toxinidir}/ - pip3 install -e {toxinidir}/ext/dapr-ext-workflow/ - pip3 install -e {toxinidir}/ext/dapr-ext-grpc/ - pip3 install -e {toxinidir}/ext/dapr-ext-fastapi/ - -[testenv:doc] -basepython = python3 -usedevelop = False -whitelist_externals = make -deps = sphinx -commands = - sphinx-apidoc -E -o docs/actor dapr/actor - sphinx-apidoc -E -o docs/clients dapr/clients - sphinx-apidoc -E -o docs/proto dapr/proto - sphinx-apidoc -E -o docs/serializers dapr/serializers - make html -C docs +[tox] +skipsdist = True +minversion = 3.8.0 +envlist = + py{38,39,310,311,312} + flake8, + ruff, + mypy, + +[testenv] +setenv = + PYTHONDONTWRITEBYTECODE=1 +deps = -rdev-requirements.txt +commands = + coverage run -m unittest discover -v ./tests + coverage run -a -m unittest discover -v ./ext/dapr-ext-workflow/tests + coverage run -a -m unittest discover -v ./ext/dapr-ext-grpc/tests + coverage run -a -m unittest discover -v ./ext/dapr-ext-fastapi/tests + coverage run -a -m unittest discover -v ./ext/flask_dapr/tests + coverage xml +commands_pre = + pip3 install -e {toxinidir}/ + pip3 install -e {toxinidir}/ext/dapr-ext-workflow/ + pip3 install -e {toxinidir}/ext/dapr-ext-grpc/ + pip3 install -e {toxinidir}/ext/dapr-ext-fastapi/ + pip3 install -e {toxinidir}/ext/flask_dapr/ + +[testenv:flake8] +basepython = python3 +usedevelop = False +deps = flake8 +commands = + flake8 . + +[testenv:ruff] +basepython = python3 +usedevelop = False +deps = ruff==0.2.2 +commands = + ruff format + +[testenv:examples] +passenv = HOME +basepython = python3 +changedir = ./examples/ +deps = + mechanical-markdown +commands = + ./validate.sh crypto + ./validate.sh metadata + ./validate.sh error_handling + ./validate.sh pubsub-simple + ./validate.sh pubsub-streaming + ./validate.sh pubsub-streaming-async + ./validate.sh state_store + ./validate.sh state_store_query + ./validate.sh secret_store + ./validate.sh invoke-simple + ./validate.sh invoke-custom-data + ./validate.sh demo_actor + ./validate.sh invoke-binding + ./validate.sh grpc_proxying + ./validate.sh w3c-tracing + ./validate.sh distributed_lock + ./validate.sh configuration + ./validate.sh demo_workflow + ./validate.sh workflow + ./validate.sh ../ +commands_pre = + pip3 install -e {toxinidir}/ + pip3 install -e {toxinidir}/ext/dapr-ext-workflow/ + pip3 install -e {toxinidir}/ext/dapr-ext-grpc/ + pip3 install -e {toxinidir}/ext/dapr-ext-fastapi/ +allowlist_externals=* + +[testenv:type] +basepython = python3 +usedevelop = False +deps = -rdev-requirements.txt +commands = + mypy --config-file mypy.ini +commands_pre = + pip3 install -e {toxinidir}/ + pip3 install -e {toxinidir}/ext/dapr-ext-workflow/ + pip3 install -e {toxinidir}/ext/dapr-ext-grpc/ + pip3 install -e {toxinidir}/ext/dapr-ext-fastapi/ + +[testenv:doc] +basepython = python3 +usedevelop = False +whitelist_externals = make +deps = sphinx +commands = + sphinx-apidoc -E -o docs/actor dapr/actor + sphinx-apidoc -E -o docs/clients dapr/clients + sphinx-apidoc -E -o docs/proto dapr/proto + sphinx-apidoc -E -o docs/serializers dapr/serializers + make html -C docs