JustinTX commited on
Commit
17150df
·
verified ·
1 Parent(s): 17afa07

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. py311/lib/python3.11/site-packages/aiofiles-25.1.0.dist-info/licenses/LICENSE +202 -0
  2. py311/lib/python3.11/site-packages/aiofiles-25.1.0.dist-info/licenses/NOTICE +2 -0
  3. py311/lib/python3.11/site-packages/aiofiles/tempfile/__init__.py +357 -0
  4. py311/lib/python3.11/site-packages/aiofiles/tempfile/temptypes.py +70 -0
  5. py311/lib/python3.11/site-packages/aiofiles/threadpool/__init__.py +141 -0
  6. py311/lib/python3.11/site-packages/aiofiles/threadpool/binary.py +104 -0
  7. py311/lib/python3.11/site-packages/aiofiles/threadpool/text.py +64 -0
  8. py311/lib/python3.11/site-packages/aiofiles/threadpool/utils.py +71 -0
  9. py311/lib/python3.11/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE +20 -0
  10. py311/lib/python3.11/site-packages/blib2to3/pgen2/__init__.py +4 -0
  11. py311/lib/python3.11/site-packages/blib2to3/pgen2/conv.cpython-311-x86_64-linux-gnu.so +0 -0
  12. py311/lib/python3.11/site-packages/blib2to3/pgen2/conv.py +256 -0
  13. py311/lib/python3.11/site-packages/blib2to3/pgen2/driver.cpython-311-x86_64-linux-gnu.so +0 -0
  14. py311/lib/python3.11/site-packages/blib2to3/pgen2/driver.py +313 -0
  15. py311/lib/python3.11/site-packages/blib2to3/pgen2/grammar.cpython-311-x86_64-linux-gnu.so +0 -0
  16. py311/lib/python3.11/site-packages/blib2to3/pgen2/grammar.py +228 -0
  17. py311/lib/python3.11/site-packages/blib2to3/pgen2/literals.cpython-311-x86_64-linux-gnu.so +0 -0
  18. py311/lib/python3.11/site-packages/blib2to3/pgen2/literals.py +65 -0
  19. py311/lib/python3.11/site-packages/blib2to3/pgen2/parse.cpython-311-x86_64-linux-gnu.so +0 -0
  20. py311/lib/python3.11/site-packages/blib2to3/pgen2/parse.py +395 -0
  21. py311/lib/python3.11/site-packages/blib2to3/pgen2/pgen.cpython-311-x86_64-linux-gnu.so +0 -0
  22. py311/lib/python3.11/site-packages/blib2to3/pgen2/pgen.py +411 -0
  23. py311/lib/python3.11/site-packages/blib2to3/pgen2/token.cpython-311-x86_64-linux-gnu.so +0 -0
  24. py311/lib/python3.11/site-packages/blib2to3/pgen2/token.py +95 -0
  25. py311/lib/python3.11/site-packages/blib2to3/pgen2/tokenize.cpython-311-x86_64-linux-gnu.so +0 -0
  26. py311/lib/python3.11/site-packages/blib2to3/pgen2/tokenize.py +226 -0
  27. py311/lib/python3.11/site-packages/google/api_core/__init__.py +41 -0
  28. py311/lib/python3.11/site-packages/google/api_core/_python_package_support.py +234 -0
  29. py311/lib/python3.11/site-packages/google/api_core/_python_version_support.py +278 -0
  30. py311/lib/python3.11/site-packages/google/api_core/_rest_streaming_base.py +118 -0
  31. py311/lib/python3.11/site-packages/google/api_core/bidi.py +735 -0
  32. py311/lib/python3.11/site-packages/google/api_core/bidi_async.py +244 -0
  33. py311/lib/python3.11/site-packages/google/api_core/bidi_base.py +88 -0
  34. py311/lib/python3.11/site-packages/google/api_core/client_info.py +114 -0
  35. py311/lib/python3.11/site-packages/google/api_core/client_logging.py +144 -0
  36. py311/lib/python3.11/site-packages/google/api_core/client_options.py +160 -0
  37. py311/lib/python3.11/site-packages/google/api_core/datetime_helpers.py +298 -0
  38. py311/lib/python3.11/site-packages/google/api_core/exceptions.py +670 -0
  39. py311/lib/python3.11/site-packages/google/api_core/extended_operation.py +225 -0
  40. py311/lib/python3.11/site-packages/google/api_core/general_helpers.py +52 -0
  41. py311/lib/python3.11/site-packages/google/api_core/grpc_helpers.py +649 -0
  42. py311/lib/python3.11/site-packages/google/api_core/grpc_helpers_async.py +348 -0
  43. py311/lib/python3.11/site-packages/google/api_core/iam.py +427 -0
  44. py311/lib/python3.11/site-packages/google/api_core/operation.py +365 -0
  45. py311/lib/python3.11/site-packages/google/api_core/operation_async.py +225 -0
  46. py311/lib/python3.11/site-packages/google/api_core/page_iterator.py +571 -0
  47. py311/lib/python3.11/site-packages/google/api_core/page_iterator_async.py +285 -0
  48. py311/lib/python3.11/site-packages/google/api_core/path_template.py +346 -0
  49. py311/lib/python3.11/site-packages/google/api_core/protobuf_helpers.py +371 -0
  50. py311/lib/python3.11/site-packages/google/api_core/py.typed +2 -0
py311/lib/python3.11/site-packages/aiofiles-25.1.0.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
py311/lib/python3.11/site-packages/aiofiles-25.1.0.dist-info/licenses/NOTICE ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Asyncio support for files
2
+ Copyright 2016 Tin Tvrtkovic
py311/lib/python3.11/site-packages/aiofiles/tempfile/__init__.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import sys
3
+ from functools import partial, singledispatch
4
+ from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOBase
5
+ from tempfile import NamedTemporaryFile as syncNamedTemporaryFile
6
+ from tempfile import SpooledTemporaryFile as syncSpooledTemporaryFile
7
+ from tempfile import TemporaryDirectory as syncTemporaryDirectory
8
+ from tempfile import TemporaryFile as syncTemporaryFile
9
+ from tempfile import _TemporaryFileWrapper as syncTemporaryFileWrapper
10
+
11
+ from ..base import AiofilesContextManager
12
+ from ..threadpool.binary import AsyncBufferedIOBase, AsyncBufferedReader, AsyncFileIO
13
+ from ..threadpool.text import AsyncTextIOWrapper
14
+ from .temptypes import AsyncSpooledTemporaryFile, AsyncTemporaryDirectory
15
+
16
+ __all__ = [
17
+ "NamedTemporaryFile",
18
+ "TemporaryFile",
19
+ "SpooledTemporaryFile",
20
+ "TemporaryDirectory",
21
+ ]
22
+
23
+
24
+ # ================================================================
25
+ # Public methods for async open and return of temp file/directory
26
+ # objects with async interface
27
+ # ================================================================
28
+ if sys.version_info >= (3, 12):
29
+
30
+ def NamedTemporaryFile(
31
+ mode="w+b",
32
+ buffering=-1,
33
+ encoding=None,
34
+ newline=None,
35
+ suffix=None,
36
+ prefix=None,
37
+ dir=None,
38
+ delete=True,
39
+ delete_on_close=True,
40
+ loop=None,
41
+ executor=None,
42
+ ):
43
+ """Async open a named temporary file"""
44
+ return AiofilesContextManager(
45
+ _temporary_file(
46
+ named=True,
47
+ mode=mode,
48
+ buffering=buffering,
49
+ encoding=encoding,
50
+ newline=newline,
51
+ suffix=suffix,
52
+ prefix=prefix,
53
+ dir=dir,
54
+ delete=delete,
55
+ delete_on_close=delete_on_close,
56
+ loop=loop,
57
+ executor=executor,
58
+ )
59
+ )
60
+
61
+ else:
62
+
63
+ def NamedTemporaryFile(
64
+ mode="w+b",
65
+ buffering=-1,
66
+ encoding=None,
67
+ newline=None,
68
+ suffix=None,
69
+ prefix=None,
70
+ dir=None,
71
+ delete=True,
72
+ loop=None,
73
+ executor=None,
74
+ ):
75
+ """Async open a named temporary file"""
76
+ return AiofilesContextManager(
77
+ _temporary_file(
78
+ named=True,
79
+ mode=mode,
80
+ buffering=buffering,
81
+ encoding=encoding,
82
+ newline=newline,
83
+ suffix=suffix,
84
+ prefix=prefix,
85
+ dir=dir,
86
+ delete=delete,
87
+ loop=loop,
88
+ executor=executor,
89
+ )
90
+ )
91
+
92
+
93
+ def TemporaryFile(
94
+ mode="w+b",
95
+ buffering=-1,
96
+ encoding=None,
97
+ newline=None,
98
+ suffix=None,
99
+ prefix=None,
100
+ dir=None,
101
+ loop=None,
102
+ executor=None,
103
+ ):
104
+ """Async open an unnamed temporary file"""
105
+ return AiofilesContextManager(
106
+ _temporary_file(
107
+ named=False,
108
+ mode=mode,
109
+ buffering=buffering,
110
+ encoding=encoding,
111
+ newline=newline,
112
+ suffix=suffix,
113
+ prefix=prefix,
114
+ dir=dir,
115
+ loop=loop,
116
+ executor=executor,
117
+ )
118
+ )
119
+
120
+
121
+ def SpooledTemporaryFile(
122
+ max_size=0,
123
+ mode="w+b",
124
+ buffering=-1,
125
+ encoding=None,
126
+ newline=None,
127
+ suffix=None,
128
+ prefix=None,
129
+ dir=None,
130
+ loop=None,
131
+ executor=None,
132
+ ):
133
+ """Async open a spooled temporary file"""
134
+ return AiofilesContextManager(
135
+ _spooled_temporary_file(
136
+ max_size=max_size,
137
+ mode=mode,
138
+ buffering=buffering,
139
+ encoding=encoding,
140
+ newline=newline,
141
+ suffix=suffix,
142
+ prefix=prefix,
143
+ dir=dir,
144
+ loop=loop,
145
+ executor=executor,
146
+ )
147
+ )
148
+
149
+
150
+ def TemporaryDirectory(suffix=None, prefix=None, dir=None, loop=None, executor=None):
151
+ """Async open a temporary directory"""
152
+ return AiofilesContextManagerTempDir(
153
+ _temporary_directory(
154
+ suffix=suffix, prefix=prefix, dir=dir, loop=loop, executor=executor
155
+ )
156
+ )
157
+
158
+
159
+ # =========================================================
160
+ # Internal coroutines to open new temp files/directories
161
+ # =========================================================
162
+ if sys.version_info >= (3, 12):
163
+
164
+ async def _temporary_file(
165
+ named=True,
166
+ mode="w+b",
167
+ buffering=-1,
168
+ encoding=None,
169
+ newline=None,
170
+ suffix=None,
171
+ prefix=None,
172
+ dir=None,
173
+ delete=True,
174
+ delete_on_close=True,
175
+ loop=None,
176
+ executor=None,
177
+ max_size=0,
178
+ ):
179
+ """Async method to open a temporary file with async interface"""
180
+ if loop is None:
181
+ loop = asyncio.get_running_loop()
182
+
183
+ if named:
184
+ cb = partial(
185
+ syncNamedTemporaryFile,
186
+ mode=mode,
187
+ buffering=buffering,
188
+ encoding=encoding,
189
+ newline=newline,
190
+ suffix=suffix,
191
+ prefix=prefix,
192
+ dir=dir,
193
+ delete=delete,
194
+ delete_on_close=delete_on_close,
195
+ )
196
+ else:
197
+ cb = partial(
198
+ syncTemporaryFile,
199
+ mode=mode,
200
+ buffering=buffering,
201
+ encoding=encoding,
202
+ newline=newline,
203
+ suffix=suffix,
204
+ prefix=prefix,
205
+ dir=dir,
206
+ )
207
+
208
+ f = await loop.run_in_executor(executor, cb)
209
+
210
+ # Wrap based on type of underlying IO object
211
+ if type(f) is syncTemporaryFileWrapper:
212
+ # _TemporaryFileWrapper was used (named files)
213
+ result = wrap(f.file, f, loop=loop, executor=executor)
214
+ result._closer = f._closer
215
+ return result
216
+ # IO object was returned directly without wrapper
217
+ return wrap(f, f, loop=loop, executor=executor)
218
+
219
+ else:
220
+
221
+ async def _temporary_file(
222
+ named=True,
223
+ mode="w+b",
224
+ buffering=-1,
225
+ encoding=None,
226
+ newline=None,
227
+ suffix=None,
228
+ prefix=None,
229
+ dir=None,
230
+ delete=True,
231
+ loop=None,
232
+ executor=None,
233
+ max_size=0,
234
+ ):
235
+ """Async method to open a temporary file with async interface"""
236
+ if loop is None:
237
+ loop = asyncio.get_running_loop()
238
+
239
+ if named:
240
+ cb = partial(
241
+ syncNamedTemporaryFile,
242
+ mode=mode,
243
+ buffering=buffering,
244
+ encoding=encoding,
245
+ newline=newline,
246
+ suffix=suffix,
247
+ prefix=prefix,
248
+ dir=dir,
249
+ delete=delete,
250
+ )
251
+ else:
252
+ cb = partial(
253
+ syncTemporaryFile,
254
+ mode=mode,
255
+ buffering=buffering,
256
+ encoding=encoding,
257
+ newline=newline,
258
+ suffix=suffix,
259
+ prefix=prefix,
260
+ dir=dir,
261
+ )
262
+
263
+ f = await loop.run_in_executor(executor, cb)
264
+
265
+ # Wrap based on type of underlying IO object
266
+ if type(f) is syncTemporaryFileWrapper:
267
+ # _TemporaryFileWrapper was used (named files)
268
+ result = wrap(f.file, f, loop=loop, executor=executor)
269
+ # add delete property
270
+ result.delete = f.delete
271
+ return result
272
+ # IO object was returned directly without wrapper
273
+ return wrap(f, f, loop=loop, executor=executor)
274
+
275
+
276
+ async def _spooled_temporary_file(
277
+ max_size=0,
278
+ mode="w+b",
279
+ buffering=-1,
280
+ encoding=None,
281
+ newline=None,
282
+ suffix=None,
283
+ prefix=None,
284
+ dir=None,
285
+ loop=None,
286
+ executor=None,
287
+ ):
288
+ """Open a spooled temporary file with async interface"""
289
+ if loop is None:
290
+ loop = asyncio.get_running_loop()
291
+
292
+ cb = partial(
293
+ syncSpooledTemporaryFile,
294
+ max_size=max_size,
295
+ mode=mode,
296
+ buffering=buffering,
297
+ encoding=encoding,
298
+ newline=newline,
299
+ suffix=suffix,
300
+ prefix=prefix,
301
+ dir=dir,
302
+ )
303
+
304
+ f = await loop.run_in_executor(executor, cb)
305
+
306
+ # Single interface provided by SpooledTemporaryFile for all modes
307
+ return AsyncSpooledTemporaryFile(f, loop=loop, executor=executor)
308
+
309
+
310
+ async def _temporary_directory(
311
+ suffix=None, prefix=None, dir=None, loop=None, executor=None
312
+ ):
313
+ """Async method to open a temporary directory with async interface"""
314
+ if loop is None:
315
+ loop = asyncio.get_running_loop()
316
+
317
+ cb = partial(syncTemporaryDirectory, suffix, prefix, dir)
318
+ f = await loop.run_in_executor(executor, cb)
319
+
320
+ return AsyncTemporaryDirectory(f, loop=loop, executor=executor)
321
+
322
+
323
+ class AiofilesContextManagerTempDir(AiofilesContextManager):
324
+ """With returns the directory location, not the object (matching sync lib)"""
325
+
326
+ async def __aenter__(self):
327
+ self._obj = await self._coro
328
+ return self._obj.name
329
+
330
+
331
+ @singledispatch
332
+ def wrap(base_io_obj, file, *, loop=None, executor=None):
333
+ """Wrap the object with interface based on type of underlying IO"""
334
+
335
+ msg = f"Unsupported IO type: {base_io_obj}"
336
+ raise TypeError(msg)
337
+
338
+
339
+ @wrap.register(TextIOBase)
340
+ def _(base_io_obj, file, *, loop=None, executor=None):
341
+ return AsyncTextIOWrapper(file, loop=loop, executor=executor)
342
+
343
+
344
+ @wrap.register(BufferedWriter)
345
+ def _(base_io_obj, file, *, loop=None, executor=None):
346
+ return AsyncBufferedIOBase(file, loop=loop, executor=executor)
347
+
348
+
349
+ @wrap.register(BufferedReader)
350
+ @wrap.register(BufferedRandom)
351
+ def _(base_io_obj, file, *, loop=None, executor=None):
352
+ return AsyncBufferedReader(file, loop=loop, executor=executor)
353
+
354
+
355
+ @wrap.register(FileIO)
356
+ def _(base_io_obj, file, *, loop=None, executor=None):
357
+ return AsyncFileIO(file, loop=loop, executor=executor)
py311/lib/python3.11/site-packages/aiofiles/tempfile/temptypes.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Async wrappers for spooled temp files and temp directory objects"""
2
+
3
+ from functools import partial
4
+
5
+ from ..base import AsyncBase
6
+ from ..threadpool.utils import (
7
+ cond_delegate_to_executor,
8
+ delegate_to_executor,
9
+ proxy_property_directly,
10
+ )
11
+
12
+
13
+ @delegate_to_executor("fileno", "rollover")
14
+ @cond_delegate_to_executor(
15
+ "close",
16
+ "flush",
17
+ "isatty",
18
+ "read",
19
+ "readline",
20
+ "readlines",
21
+ "seek",
22
+ "tell",
23
+ "truncate",
24
+ )
25
+ @proxy_property_directly("closed", "encoding", "mode", "name", "newlines")
26
+ class AsyncSpooledTemporaryFile(AsyncBase):
27
+ """Async wrapper for SpooledTemporaryFile class"""
28
+
29
+ async def _check(self):
30
+ if self._file._rolled:
31
+ return
32
+ max_size = self._file._max_size
33
+ if max_size and self._file.tell() > max_size:
34
+ await self.rollover()
35
+
36
+ async def write(self, s):
37
+ """Implementation to anticipate rollover"""
38
+ if self._file._rolled:
39
+ cb = partial(self._file.write, s)
40
+ return await self._loop.run_in_executor(self._executor, cb)
41
+
42
+ file = self._file._file # reference underlying base IO object
43
+ rv = file.write(s)
44
+ await self._check()
45
+ return rv
46
+
47
+ async def writelines(self, iterable):
48
+ """Implementation to anticipate rollover"""
49
+ if self._file._rolled:
50
+ cb = partial(self._file.writelines, iterable)
51
+ return await self._loop.run_in_executor(self._executor, cb)
52
+
53
+ file = self._file._file # reference underlying base IO object
54
+ rv = file.writelines(iterable)
55
+ await self._check()
56
+ return rv
57
+
58
+
59
+ @delegate_to_executor("cleanup")
60
+ @proxy_property_directly("name")
61
+ class AsyncTemporaryDirectory:
62
+ """Async wrapper for TemporaryDirectory class"""
63
+
64
+ def __init__(self, file, loop, executor):
65
+ self._file = file
66
+ self._loop = loop
67
+ self._executor = executor
68
+
69
+ async def close(self):
70
+ await self.cleanup()
py311/lib/python3.11/site-packages/aiofiles/threadpool/__init__.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Handle files using a thread pool executor."""
2
+
3
+ import asyncio
4
+ import sys
5
+ from functools import partial, singledispatch
6
+ from io import (
7
+ BufferedIOBase,
8
+ BufferedRandom,
9
+ BufferedReader,
10
+ BufferedWriter,
11
+ FileIO,
12
+ TextIOBase,
13
+ )
14
+
15
+ from ..base import AiofilesContextManager
16
+ from .binary import (
17
+ AsyncBufferedIOBase,
18
+ AsyncBufferedReader,
19
+ AsyncFileIO,
20
+ AsyncIndirectBufferedIOBase,
21
+ )
22
+ from .text import AsyncTextIndirectIOWrapper, AsyncTextIOWrapper
23
+
24
+ sync_open = open
25
+
26
+ __all__ = (
27
+ "open",
28
+ "stdin",
29
+ "stdout",
30
+ "stderr",
31
+ "stdin_bytes",
32
+ "stdout_bytes",
33
+ "stderr_bytes",
34
+ )
35
+
36
+
37
+ def open(
38
+ file,
39
+ mode="r",
40
+ buffering=-1,
41
+ encoding=None,
42
+ errors=None,
43
+ newline=None,
44
+ closefd=True,
45
+ opener=None,
46
+ *,
47
+ loop=None,
48
+ executor=None,
49
+ ):
50
+ return AiofilesContextManager(
51
+ _open(
52
+ file,
53
+ mode=mode,
54
+ buffering=buffering,
55
+ encoding=encoding,
56
+ errors=errors,
57
+ newline=newline,
58
+ closefd=closefd,
59
+ opener=opener,
60
+ loop=loop,
61
+ executor=executor,
62
+ )
63
+ )
64
+
65
+
66
+ async def _open(
67
+ file,
68
+ mode="r",
69
+ buffering=-1,
70
+ encoding=None,
71
+ errors=None,
72
+ newline=None,
73
+ closefd=True,
74
+ opener=None,
75
+ *,
76
+ loop=None,
77
+ executor=None,
78
+ ):
79
+ """Open an asyncio file."""
80
+ if loop is None:
81
+ loop = asyncio.get_running_loop()
82
+ cb = partial(
83
+ sync_open,
84
+ file,
85
+ mode=mode,
86
+ buffering=buffering,
87
+ encoding=encoding,
88
+ errors=errors,
89
+ newline=newline,
90
+ closefd=closefd,
91
+ opener=opener,
92
+ )
93
+ f = await loop.run_in_executor(executor, cb)
94
+
95
+ return wrap(f, loop=loop, executor=executor)
96
+
97
+
98
+ @singledispatch
99
+ def wrap(file, *, loop=None, executor=None):
100
+ msg = f"Unsupported io type: {file}."
101
+ raise TypeError(msg)
102
+
103
+
104
+ @wrap.register(TextIOBase)
105
+ def _(file, *, loop=None, executor=None):
106
+ return AsyncTextIOWrapper(file, loop=loop, executor=executor)
107
+
108
+
109
+ @wrap.register(BufferedWriter)
110
+ @wrap.register(BufferedIOBase)
111
+ def _(file, *, loop=None, executor=None):
112
+ return AsyncBufferedIOBase(file, loop=loop, executor=executor)
113
+
114
+
115
+ @wrap.register(BufferedReader)
116
+ @wrap.register(BufferedRandom)
117
+ def _(file, *, loop=None, executor=None):
118
+ return AsyncBufferedReader(file, loop=loop, executor=executor)
119
+
120
+
121
+ @wrap.register(FileIO)
122
+ def _(file, *, loop=None, executor=None):
123
+ return AsyncFileIO(file, loop=loop, executor=executor)
124
+
125
+
126
+ stdin = AsyncTextIndirectIOWrapper("sys.stdin", None, None, indirect=lambda: sys.stdin)
127
+ stdout = AsyncTextIndirectIOWrapper(
128
+ "sys.stdout", None, None, indirect=lambda: sys.stdout
129
+ )
130
+ stderr = AsyncTextIndirectIOWrapper(
131
+ "sys.stderr", None, None, indirect=lambda: sys.stderr
132
+ )
133
+ stdin_bytes = AsyncIndirectBufferedIOBase(
134
+ "sys.stdin.buffer", None, None, indirect=lambda: sys.stdin.buffer
135
+ )
136
+ stdout_bytes = AsyncIndirectBufferedIOBase(
137
+ "sys.stdout.buffer", None, None, indirect=lambda: sys.stdout.buffer
138
+ )
139
+ stderr_bytes = AsyncIndirectBufferedIOBase(
140
+ "sys.stderr.buffer", None, None, indirect=lambda: sys.stderr.buffer
141
+ )
py311/lib/python3.11/site-packages/aiofiles/threadpool/binary.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ..base import AsyncBase, AsyncIndirectBase
2
+ from .utils import delegate_to_executor, proxy_method_directly, proxy_property_directly
3
+
4
+
5
+ @delegate_to_executor(
6
+ "close",
7
+ "flush",
8
+ "isatty",
9
+ "read",
10
+ "read1",
11
+ "readinto",
12
+ "readline",
13
+ "readlines",
14
+ "seek",
15
+ "seekable",
16
+ "tell",
17
+ "truncate",
18
+ "writable",
19
+ "write",
20
+ "writelines",
21
+ )
22
+ @proxy_method_directly("detach", "fileno", "readable")
23
+ @proxy_property_directly("closed", "raw", "name", "mode")
24
+ class AsyncBufferedIOBase(AsyncBase):
25
+ """The asyncio executor version of io.BufferedWriter and BufferedIOBase."""
26
+
27
+
28
+ @delegate_to_executor("peek")
29
+ class AsyncBufferedReader(AsyncBufferedIOBase):
30
+ """The asyncio executor version of io.BufferedReader and Random."""
31
+
32
+
33
+ @delegate_to_executor(
34
+ "close",
35
+ "flush",
36
+ "isatty",
37
+ "read",
38
+ "readall",
39
+ "readinto",
40
+ "readline",
41
+ "readlines",
42
+ "seek",
43
+ "seekable",
44
+ "tell",
45
+ "truncate",
46
+ "writable",
47
+ "write",
48
+ "writelines",
49
+ )
50
+ @proxy_method_directly("fileno", "readable")
51
+ @proxy_property_directly("closed", "name", "mode")
52
+ class AsyncFileIO(AsyncBase):
53
+ """The asyncio executor version of io.FileIO."""
54
+
55
+
56
+ @delegate_to_executor(
57
+ "close",
58
+ "flush",
59
+ "isatty",
60
+ "read",
61
+ "read1",
62
+ "readinto",
63
+ "readline",
64
+ "readlines",
65
+ "seek",
66
+ "seekable",
67
+ "tell",
68
+ "truncate",
69
+ "writable",
70
+ "write",
71
+ "writelines",
72
+ )
73
+ @proxy_method_directly("detach", "fileno", "readable")
74
+ @proxy_property_directly("closed", "raw", "name", "mode")
75
+ class AsyncIndirectBufferedIOBase(AsyncIndirectBase):
76
+ """The indirect asyncio executor version of io.BufferedWriter and BufferedIOBase."""
77
+
78
+
79
+ @delegate_to_executor("peek")
80
+ class AsyncIndirectBufferedReader(AsyncIndirectBufferedIOBase):
81
+ """The indirect asyncio executor version of io.BufferedReader and Random."""
82
+
83
+
84
+ @delegate_to_executor(
85
+ "close",
86
+ "flush",
87
+ "isatty",
88
+ "read",
89
+ "readall",
90
+ "readinto",
91
+ "readline",
92
+ "readlines",
93
+ "seek",
94
+ "seekable",
95
+ "tell",
96
+ "truncate",
97
+ "writable",
98
+ "write",
99
+ "writelines",
100
+ )
101
+ @proxy_method_directly("fileno", "readable")
102
+ @proxy_property_directly("closed", "name", "mode")
103
+ class AsyncIndirectFileIO(AsyncIndirectBase):
104
+ """The indirect asyncio executor version of io.FileIO."""
py311/lib/python3.11/site-packages/aiofiles/threadpool/text.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ..base import AsyncBase, AsyncIndirectBase
2
+ from .utils import delegate_to_executor, proxy_method_directly, proxy_property_directly
3
+
4
+
5
+ @delegate_to_executor(
6
+ "close",
7
+ "flush",
8
+ "isatty",
9
+ "read",
10
+ "readable",
11
+ "readline",
12
+ "readlines",
13
+ "seek",
14
+ "seekable",
15
+ "tell",
16
+ "truncate",
17
+ "write",
18
+ "writable",
19
+ "writelines",
20
+ )
21
+ @proxy_method_directly("detach", "fileno", "readable")
22
+ @proxy_property_directly(
23
+ "buffer",
24
+ "closed",
25
+ "encoding",
26
+ "errors",
27
+ "line_buffering",
28
+ "newlines",
29
+ "name",
30
+ "mode",
31
+ )
32
+ class AsyncTextIOWrapper(AsyncBase):
33
+ """The asyncio executor version of io.TextIOWrapper."""
34
+
35
+
36
+ @delegate_to_executor(
37
+ "close",
38
+ "flush",
39
+ "isatty",
40
+ "read",
41
+ "readable",
42
+ "readline",
43
+ "readlines",
44
+ "seek",
45
+ "seekable",
46
+ "tell",
47
+ "truncate",
48
+ "write",
49
+ "writable",
50
+ "writelines",
51
+ )
52
+ @proxy_method_directly("detach", "fileno", "readable")
53
+ @proxy_property_directly(
54
+ "buffer",
55
+ "closed",
56
+ "encoding",
57
+ "errors",
58
+ "line_buffering",
59
+ "newlines",
60
+ "name",
61
+ "mode",
62
+ )
63
+ class AsyncTextIndirectIOWrapper(AsyncIndirectBase):
64
+ """The indirect asyncio executor version of io.TextIOWrapper."""
py311/lib/python3.11/site-packages/aiofiles/threadpool/utils.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import functools
2
+
3
+
4
+ def delegate_to_executor(*attrs):
5
+ def cls_builder(cls):
6
+ for attr_name in attrs:
7
+ setattr(cls, attr_name, _make_delegate_method(attr_name))
8
+ return cls
9
+
10
+ return cls_builder
11
+
12
+
13
+ def proxy_method_directly(*attrs):
14
+ def cls_builder(cls):
15
+ for attr_name in attrs:
16
+ setattr(cls, attr_name, _make_proxy_method(attr_name))
17
+ return cls
18
+
19
+ return cls_builder
20
+
21
+
22
+ def proxy_property_directly(*attrs):
23
+ def cls_builder(cls):
24
+ for attr_name in attrs:
25
+ setattr(cls, attr_name, _make_proxy_property(attr_name))
26
+ return cls
27
+
28
+ return cls_builder
29
+
30
+
31
+ def cond_delegate_to_executor(*attrs):
32
+ def cls_builder(cls):
33
+ for attr_name in attrs:
34
+ setattr(cls, attr_name, _make_cond_delegate_method(attr_name))
35
+ return cls
36
+
37
+ return cls_builder
38
+
39
+
40
+ def _make_delegate_method(attr_name):
41
+ async def method(self, *args, **kwargs):
42
+ cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
43
+ return await self._loop.run_in_executor(self._executor, cb)
44
+
45
+ return method
46
+
47
+
48
+ def _make_proxy_method(attr_name):
49
+ def method(self, *args, **kwargs):
50
+ return getattr(self._file, attr_name)(*args, **kwargs)
51
+
52
+ return method
53
+
54
+
55
+ def _make_proxy_property(attr_name):
56
+ def proxy_property(self):
57
+ return getattr(self._file, attr_name)
58
+
59
+ return property(proxy_property)
60
+
61
+
62
+ def _make_cond_delegate_method(attr_name):
63
+ """For spooled temp files, delegate only if rolled to file object"""
64
+
65
+ async def method(self, *args, **kwargs):
66
+ if self._file._rolled:
67
+ cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
68
+ return await self._loop.run_in_executor(self._executor, cb)
69
+ return getattr(self._file, attr_name)(*args, **kwargs)
70
+
71
+ return method
py311/lib/python3.11/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Alex Grönholm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
py311/lib/python3.11/site-packages/blib2to3/pgen2/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ """The pgen2 package."""
py311/lib/python3.11/site-packages/blib2to3/pgen2/conv.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/conv.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ # mypy: ignore-errors
5
+
6
+ """Convert graminit.[ch] spit out by pgen to Python code.
7
+
8
+ Pgen is the Python parser generator. It is useful to quickly create a
9
+ parser from a grammar file in Python's grammar notation. But I don't
10
+ want my parsers to be written in C (yet), so I'm translating the
11
+ parsing tables to Python data structures and writing a Python parse
12
+ engine.
13
+
14
+ Note that the token numbers are constants determined by the standard
15
+ Python tokenizer. The standard token module defines these numbers and
16
+ their names (the names are not used much). The token numbers are
17
+ hardcoded into the Python tokenizer and into pgen. A Python
18
+ implementation of the Python tokenizer is also available, in the
19
+ standard tokenize module.
20
+
21
+ On the other hand, symbol numbers (representing the grammar's
22
+ non-terminals) are assigned by pgen based on the actual grammar
23
+ input.
24
+
25
+ Note: this module is pretty much obsolete; the pgen module generates
26
+ equivalent grammar tables directly from the Grammar.txt input file
27
+ without having to invoke the Python pgen C program.
28
+
29
+ """
30
+
31
+ # Python imports
32
+ import re
33
+
34
+ # Local imports
35
+ from blib2to3.pgen2 import grammar, token
36
+
37
+
38
+ class Converter(grammar.Grammar):
39
+ """Grammar subclass that reads classic pgen output files.
40
+
41
+ The run() method reads the tables as produced by the pgen parser
42
+ generator, typically contained in two C files, graminit.h and
43
+ graminit.c. The other methods are for internal use only.
44
+
45
+ See the base class for more documentation.
46
+
47
+ """
48
+
49
+ def run(self, graminit_h, graminit_c):
50
+ """Load the grammar tables from the text files written by pgen."""
51
+ self.parse_graminit_h(graminit_h)
52
+ self.parse_graminit_c(graminit_c)
53
+ self.finish_off()
54
+
55
+ def parse_graminit_h(self, filename):
56
+ """Parse the .h file written by pgen. (Internal)
57
+
58
+ This file is a sequence of #define statements defining the
59
+ nonterminals of the grammar as numbers. We build two tables
60
+ mapping the numbers to names and back.
61
+
62
+ """
63
+ try:
64
+ f = open(filename)
65
+ except OSError as err:
66
+ print(f"Can't open {filename}: {err}")
67
+ return False
68
+ self.symbol2number = {}
69
+ self.number2symbol = {}
70
+ lineno = 0
71
+ for line in f:
72
+ lineno += 1
73
+ mo = re.match(r"^#define\s+(\w+)\s+(\d+)$", line)
74
+ if not mo and line.strip():
75
+ print(f"{filename}({lineno}): can't parse {line.strip()}")
76
+ else:
77
+ symbol, number = mo.groups()
78
+ number = int(number)
79
+ assert symbol not in self.symbol2number
80
+ assert number not in self.number2symbol
81
+ self.symbol2number[symbol] = number
82
+ self.number2symbol[number] = symbol
83
+ return True
84
+
85
+ def parse_graminit_c(self, filename):
86
+ """Parse the .c file written by pgen. (Internal)
87
+
88
+ The file looks as follows. The first two lines are always this:
89
+
90
+ #include "pgenheaders.h"
91
+ #include "grammar.h"
92
+
93
+ After that come four blocks:
94
+
95
+ 1) one or more state definitions
96
+ 2) a table defining dfas
97
+ 3) a table defining labels
98
+ 4) a struct defining the grammar
99
+
100
+ A state definition has the following form:
101
+ - one or more arc arrays, each of the form:
102
+ static arc arcs_<n>_<m>[<k>] = {
103
+ {<i>, <j>},
104
+ ...
105
+ };
106
+ - followed by a state array, of the form:
107
+ static state states_<s>[<t>] = {
108
+ {<k>, arcs_<n>_<m>},
109
+ ...
110
+ };
111
+
112
+ """
113
+ try:
114
+ f = open(filename)
115
+ except OSError as err:
116
+ print(f"Can't open {filename}: {err}")
117
+ return False
118
+ # The code below essentially uses f's iterator-ness!
119
+ lineno = 0
120
+
121
+ # Expect the two #include lines
122
+ lineno, line = lineno + 1, next(f)
123
+ assert line == '#include "pgenheaders.h"\n', (lineno, line)
124
+ lineno, line = lineno + 1, next(f)
125
+ assert line == '#include "grammar.h"\n', (lineno, line)
126
+
127
+ # Parse the state definitions
128
+ lineno, line = lineno + 1, next(f)
129
+ allarcs = {}
130
+ states = []
131
+ while line.startswith("static arc "):
132
+ while line.startswith("static arc "):
133
+ mo = re.match(r"static arc arcs_(\d+)_(\d+)\[(\d+)\] = {$", line)
134
+ assert mo, (lineno, line)
135
+ n, m, k = list(map(int, mo.groups()))
136
+ arcs = []
137
+ for _ in range(k):
138
+ lineno, line = lineno + 1, next(f)
139
+ mo = re.match(r"\s+{(\d+), (\d+)},$", line)
140
+ assert mo, (lineno, line)
141
+ i, j = list(map(int, mo.groups()))
142
+ arcs.append((i, j))
143
+ lineno, line = lineno + 1, next(f)
144
+ assert line == "};\n", (lineno, line)
145
+ allarcs[(n, m)] = arcs
146
+ lineno, line = lineno + 1, next(f)
147
+ mo = re.match(r"static state states_(\d+)\[(\d+)\] = {$", line)
148
+ assert mo, (lineno, line)
149
+ s, t = list(map(int, mo.groups()))
150
+ assert s == len(states), (lineno, line)
151
+ state = []
152
+ for _ in range(t):
153
+ lineno, line = lineno + 1, next(f)
154
+ mo = re.match(r"\s+{(\d+), arcs_(\d+)_(\d+)},$", line)
155
+ assert mo, (lineno, line)
156
+ k, n, m = list(map(int, mo.groups()))
157
+ arcs = allarcs[n, m]
158
+ assert k == len(arcs), (lineno, line)
159
+ state.append(arcs)
160
+ states.append(state)
161
+ lineno, line = lineno + 1, next(f)
162
+ assert line == "};\n", (lineno, line)
163
+ lineno, line = lineno + 1, next(f)
164
+ self.states = states
165
+
166
+ # Parse the dfas
167
+ dfas = {}
168
+ mo = re.match(r"static dfa dfas\[(\d+)\] = {$", line)
169
+ assert mo, (lineno, line)
170
+ ndfas = int(mo.group(1))
171
+ for i in range(ndfas):
172
+ lineno, line = lineno + 1, next(f)
173
+ mo = re.match(r'\s+{(\d+), "(\w+)", (\d+), (\d+), states_(\d+),$', line)
174
+ assert mo, (lineno, line)
175
+ symbol = mo.group(2)
176
+ number, x, y, z = list(map(int, mo.group(1, 3, 4, 5)))
177
+ assert self.symbol2number[symbol] == number, (lineno, line)
178
+ assert self.number2symbol[number] == symbol, (lineno, line)
179
+ assert x == 0, (lineno, line)
180
+ state = states[z]
181
+ assert y == len(state), (lineno, line)
182
+ lineno, line = lineno + 1, next(f)
183
+ mo = re.match(r'\s+("(?:\\\d\d\d)*")},$', line)
184
+ assert mo, (lineno, line)
185
+ first = {}
186
+ rawbitset = eval(mo.group(1))
187
+ for i, c in enumerate(rawbitset):
188
+ byte = ord(c)
189
+ for j in range(8):
190
+ if byte & (1 << j):
191
+ first[i * 8 + j] = 1
192
+ dfas[number] = (state, first)
193
+ lineno, line = lineno + 1, next(f)
194
+ assert line == "};\n", (lineno, line)
195
+ self.dfas = dfas
196
+
197
+ # Parse the labels
198
+ labels = []
199
+ lineno, line = lineno + 1, next(f)
200
+ mo = re.match(r"static label labels\[(\d+)\] = {$", line)
201
+ assert mo, (lineno, line)
202
+ nlabels = int(mo.group(1))
203
+ for i in range(nlabels):
204
+ lineno, line = lineno + 1, next(f)
205
+ mo = re.match(r'\s+{(\d+), (0|"\w+")},$', line)
206
+ assert mo, (lineno, line)
207
+ x, y = mo.groups()
208
+ x = int(x)
209
+ if y == "0":
210
+ y = None
211
+ else:
212
+ y = eval(y)
213
+ labels.append((x, y))
214
+ lineno, line = lineno + 1, next(f)
215
+ assert line == "};\n", (lineno, line)
216
+ self.labels = labels
217
+
218
+ # Parse the grammar struct
219
+ lineno, line = lineno + 1, next(f)
220
+ assert line == "grammar _PyParser_Grammar = {\n", (lineno, line)
221
+ lineno, line = lineno + 1, next(f)
222
+ mo = re.match(r"\s+(\d+),$", line)
223
+ assert mo, (lineno, line)
224
+ ndfas = int(mo.group(1))
225
+ assert ndfas == len(self.dfas)
226
+ lineno, line = lineno + 1, next(f)
227
+ assert line == "\tdfas,\n", (lineno, line)
228
+ lineno, line = lineno + 1, next(f)
229
+ mo = re.match(r"\s+{(\d+), labels},$", line)
230
+ assert mo, (lineno, line)
231
+ nlabels = int(mo.group(1))
232
+ assert nlabels == len(self.labels), (lineno, line)
233
+ lineno, line = lineno + 1, next(f)
234
+ mo = re.match(r"\s+(\d+)$", line)
235
+ assert mo, (lineno, line)
236
+ start = int(mo.group(1))
237
+ assert start in self.number2symbol, (lineno, line)
238
+ self.start = start
239
+ lineno, line = lineno + 1, next(f)
240
+ assert line == "};\n", (lineno, line)
241
+ try:
242
+ lineno, line = lineno + 1, next(f)
243
+ except StopIteration:
244
+ pass
245
+ else:
246
+ assert 0, (lineno, line)
247
+
248
+ def finish_off(self):
249
+ """Create additional useful structures. (Internal)."""
250
+ self.keywords = {} # map from keyword strings to arc labels
251
+ self.tokens = {} # map from numeric token values to arc labels
252
+ for ilabel, (type, value) in enumerate(self.labels):
253
+ if type == token.NAME and value is not None:
254
+ self.keywords[value] = ilabel
255
+ elif value is None:
256
+ self.tokens[type] = ilabel
py311/lib/python3.11/site-packages/blib2to3/pgen2/driver.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/driver.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ # Modifications:
5
+ # Copyright 2006 Google, Inc. All Rights Reserved.
6
+ # Licensed to PSF under a Contributor Agreement.
7
+
8
+ """Parser driver.
9
+
10
+ This provides a high-level interface to parse a file into a syntax tree.
11
+
12
+ """
13
+
14
+ __author__ = "Guido van Rossum <guido@python.org>"
15
+
16
+ __all__ = ["Driver", "load_grammar"]
17
+
18
+ # Python imports
19
+ import io
20
+ import logging
21
+ import os
22
+ import pkgutil
23
+ import sys
24
+ from collections.abc import Iterable, Iterator
25
+ from contextlib import contextmanager
26
+ from dataclasses import dataclass, field
27
+ from logging import Logger
28
+ from typing import Any, Union, cast
29
+
30
+ from blib2to3.pgen2.grammar import Grammar
31
+ from blib2to3.pgen2.tokenize import TokenInfo
32
+ from blib2to3.pytree import NL
33
+
34
+ # Pgen imports
35
+ from . import grammar, parse, pgen, token, tokenize
36
+
37
+ Path = Union[str, "os.PathLike[str]"]
38
+
39
+
40
+ @dataclass
41
+ class ReleaseRange:
42
+ start: int
43
+ end: int | None = None
44
+ tokens: list[Any] = field(default_factory=list)
45
+
46
+ def lock(self) -> None:
47
+ total_eaten = len(self.tokens)
48
+ self.end = self.start + total_eaten
49
+
50
+
51
+ class TokenProxy:
52
+ def __init__(self, generator: Any) -> None:
53
+ self._tokens = generator
54
+ self._counter = 0
55
+ self._release_ranges: list[ReleaseRange] = []
56
+
57
+ @contextmanager
58
+ def release(self) -> Iterator["TokenProxy"]:
59
+ release_range = ReleaseRange(self._counter)
60
+ self._release_ranges.append(release_range)
61
+ try:
62
+ yield self
63
+ finally:
64
+ # Lock the last release range to the final position that
65
+ # has been eaten.
66
+ release_range.lock()
67
+
68
+ def eat(self, point: int) -> Any:
69
+ eaten_tokens = self._release_ranges[-1].tokens
70
+ if point < len(eaten_tokens):
71
+ return eaten_tokens[point]
72
+ else:
73
+ while point >= len(eaten_tokens):
74
+ token = next(self._tokens)
75
+ eaten_tokens.append(token)
76
+ return token
77
+
78
+ def __iter__(self) -> "TokenProxy":
79
+ return self
80
+
81
+ def __next__(self) -> Any:
82
+ # If the current position is already compromised (looked up)
83
+ # return the eaten token, if not just go further on the given
84
+ # token producer.
85
+ for release_range in self._release_ranges:
86
+ assert release_range.end is not None
87
+
88
+ start, end = release_range.start, release_range.end
89
+ if start <= self._counter < end:
90
+ token = release_range.tokens[self._counter - start]
91
+ break
92
+ else:
93
+ token = next(self._tokens)
94
+ self._counter += 1
95
+ return token
96
+
97
+ def can_advance(self, to: int) -> bool:
98
+ # Try to eat, fail if it can't. The eat operation is cached
99
+ # so there won't be any additional cost of eating here
100
+ try:
101
+ self.eat(to)
102
+ except StopIteration:
103
+ return False
104
+ else:
105
+ return True
106
+
107
+
108
+ class Driver:
109
+ def __init__(self, grammar: Grammar, logger: Logger | None = None) -> None:
110
+ self.grammar = grammar
111
+ if logger is None:
112
+ logger = logging.getLogger(__name__)
113
+ self.logger = logger
114
+
115
+ def parse_tokens(self, tokens: Iterable[TokenInfo], debug: bool = False) -> NL:
116
+ """Parse a series of tokens and return the syntax tree."""
117
+ # XXX Move the prefix computation into a wrapper around tokenize.
118
+ proxy = TokenProxy(tokens)
119
+
120
+ p = parse.Parser(self.grammar)
121
+ p.setup(proxy=proxy)
122
+
123
+ lineno = 1
124
+ column = 0
125
+ indent_columns: list[int] = []
126
+ type = value = start = end = line_text = None
127
+ prefix = ""
128
+
129
+ for quintuple in proxy:
130
+ type, value, start, end, line_text = quintuple
131
+ if start != (lineno, column):
132
+ assert (lineno, column) <= start, ((lineno, column), start)
133
+ s_lineno, s_column = start
134
+ if lineno < s_lineno:
135
+ prefix += "\n" * (s_lineno - lineno)
136
+ lineno = s_lineno
137
+ column = 0
138
+ if column < s_column:
139
+ prefix += line_text[column:s_column]
140
+ column = s_column
141
+ if type in (tokenize.COMMENT, tokenize.NL):
142
+ prefix += value
143
+ lineno, column = end
144
+ if value.endswith("\n"):
145
+ lineno += 1
146
+ column = 0
147
+ continue
148
+ if type == token.OP:
149
+ type = grammar.opmap[value]
150
+ if debug:
151
+ assert type is not None
152
+ self.logger.debug(
153
+ "%s %r (prefix=%r)", token.tok_name[type], value, prefix
154
+ )
155
+ if type == token.INDENT:
156
+ indent_columns.append(len(value))
157
+ _prefix = prefix + value
158
+ prefix = ""
159
+ value = ""
160
+ elif type == token.DEDENT:
161
+ _indent_col = indent_columns.pop()
162
+ prefix, _prefix = self._partially_consume_prefix(prefix, _indent_col)
163
+ if p.addtoken(cast(int, type), value, (prefix, start)):
164
+ if debug:
165
+ self.logger.debug("Stop.")
166
+ break
167
+ prefix = ""
168
+ if type in {token.INDENT, token.DEDENT}:
169
+ prefix = _prefix
170
+ lineno, column = end
171
+ # FSTRING_MIDDLE and TSTRING_MIDDLE are the only token that can end with a
172
+ # newline, and `end` will point to the next line. For that case, don't
173
+ # increment lineno.
174
+ if value.endswith("\n") and type not in (
175
+ token.FSTRING_MIDDLE,
176
+ token.TSTRING_MIDDLE,
177
+ ):
178
+ lineno += 1
179
+ column = 0
180
+ else:
181
+ # We never broke out -- EOF is too soon (how can this happen???)
182
+ assert start is not None
183
+ raise parse.ParseError("incomplete input", type, value, (prefix, start))
184
+ assert p.rootnode is not None
185
+ return p.rootnode
186
+
187
+ def parse_file(
188
+ self, filename: Path, encoding: str | None = None, debug: bool = False
189
+ ) -> NL:
190
+ """Parse a file and return the syntax tree."""
191
+ with open(filename, encoding=encoding) as stream:
192
+ text = stream.read()
193
+ return self.parse_string(text, debug)
194
+
195
+ def parse_string(self, text: str, debug: bool = False) -> NL:
196
+ """Parse a string and return the syntax tree."""
197
+ tokens = tokenize.tokenize(text, grammar=self.grammar)
198
+ return self.parse_tokens(tokens, debug)
199
+
200
+ def _partially_consume_prefix(self, prefix: str, column: int) -> tuple[str, str]:
201
+ lines: list[str] = []
202
+ current_line = ""
203
+ current_column = 0
204
+ wait_for_nl = False
205
+ for char in prefix:
206
+ current_line += char
207
+ if wait_for_nl:
208
+ if char == "\n":
209
+ if current_line.strip() and current_column < column:
210
+ res = "".join(lines)
211
+ return res, prefix[len(res) :]
212
+
213
+ lines.append(current_line)
214
+ current_line = ""
215
+ current_column = 0
216
+ wait_for_nl = False
217
+ elif char in " \t":
218
+ current_column += 1
219
+ elif char == "\n":
220
+ # unexpected empty line
221
+ current_column = 0
222
+ elif char == "\f":
223
+ current_column = 0
224
+ else:
225
+ # indent is finished
226
+ wait_for_nl = True
227
+ return "".join(lines), current_line
228
+
229
+
230
+ def _generate_pickle_name(gt: Path, cache_dir: Path | None = None) -> str:
231
+ head, tail = os.path.splitext(gt)
232
+ if tail == ".txt":
233
+ tail = ""
234
+ name = head + tail + ".".join(map(str, sys.version_info)) + ".pickle"
235
+ if cache_dir:
236
+ return os.path.join(cache_dir, os.path.basename(name))
237
+ else:
238
+ return name
239
+
240
+
241
+ def load_grammar(
242
+ gt: str = "Grammar.txt",
243
+ gp: str | None = None,
244
+ save: bool = True,
245
+ force: bool = False,
246
+ logger: Logger | None = None,
247
+ ) -> Grammar:
248
+ """Load the grammar (maybe from a pickle)."""
249
+ if logger is None:
250
+ logger = logging.getLogger(__name__)
251
+ gp = _generate_pickle_name(gt) if gp is None else gp
252
+ if force or not _newer(gp, gt):
253
+ g: grammar.Grammar = pgen.generate_grammar(gt)
254
+ if save:
255
+ try:
256
+ g.dump(gp)
257
+ except OSError:
258
+ # Ignore error, caching is not vital.
259
+ pass
260
+ else:
261
+ g = grammar.Grammar()
262
+ g.load(gp)
263
+ return g
264
+
265
+
266
+ def _newer(a: str, b: str) -> bool:
267
+ """Inquire whether file a was written since file b."""
268
+ if not os.path.exists(a):
269
+ return False
270
+ if not os.path.exists(b):
271
+ return True
272
+ return os.path.getmtime(a) >= os.path.getmtime(b)
273
+
274
+
275
+ def load_packaged_grammar(
276
+ package: str, grammar_source: str, cache_dir: Path | None = None
277
+ ) -> grammar.Grammar:
278
+ """Normally, loads a pickled grammar by doing
279
+ pkgutil.get_data(package, pickled_grammar)
280
+ where *pickled_grammar* is computed from *grammar_source* by adding the
281
+ Python version and using a ``.pickle`` extension.
282
+
283
+ However, if *grammar_source* is an extant file, load_grammar(grammar_source)
284
+ is called instead. This facilitates using a packaged grammar file when needed
285
+ but preserves load_grammar's automatic regeneration behavior when possible.
286
+
287
+ """
288
+ if os.path.isfile(grammar_source):
289
+ gp = _generate_pickle_name(grammar_source, cache_dir) if cache_dir else None
290
+ return load_grammar(grammar_source, gp=gp)
291
+ pickled_name = _generate_pickle_name(os.path.basename(grammar_source), cache_dir)
292
+ data = pkgutil.get_data(package, pickled_name)
293
+ assert data is not None
294
+ g = grammar.Grammar()
295
+ g.loads(data)
296
+ return g
297
+
298
+
299
+ def main(*args: str) -> bool:
300
+ """Main program, when run as a script: produce grammar pickle files.
301
+
302
+ Calls load_grammar for each argument, a path to a grammar text file.
303
+ """
304
+ if not args:
305
+ args = tuple(sys.argv[1:])
306
+ logging.basicConfig(level=logging.INFO, stream=sys.stdout, format="%(message)s")
307
+ for gt in args:
308
+ load_grammar(gt, save=True, force=True)
309
+ return True
310
+
311
+
312
+ if __name__ == "__main__":
313
+ sys.exit(int(not main()))
py311/lib/python3.11/site-packages/blib2to3/pgen2/grammar.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/grammar.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ """This module defines the data structures used to represent a grammar.
5
+
6
+ These are a bit arcane because they are derived from the data
7
+ structures used by Python's 'pgen' parser generator.
8
+
9
+ There's also a table here mapping operators to their names in the
10
+ token module; the Python tokenize module reports all operators as the
11
+ fallback token code OP, but the parser needs the actual token code.
12
+
13
+ """
14
+
15
+ # Python imports
16
+ import os
17
+ import pickle
18
+ import tempfile
19
+ from typing import Any, Optional, TypeVar, Union
20
+
21
+ # Local imports
22
+ from . import token
23
+
24
+ _P = TypeVar("_P", bound="Grammar")
25
+ Label = tuple[int, Optional[str]]
26
+ DFA = list[list[tuple[int, int]]]
27
+ DFAS = tuple[DFA, dict[int, int]]
28
+ Path = Union[str, "os.PathLike[str]"]
29
+
30
+
31
+ class Grammar:
32
+ """Pgen parsing tables conversion class.
33
+
34
+ Once initialized, this class supplies the grammar tables for the
35
+ parsing engine implemented by parse.py. The parsing engine
36
+ accesses the instance variables directly. The class here does not
37
+ provide initialization of the tables; several subclasses exist to
38
+ do this (see the conv and pgen modules).
39
+
40
+ The load() method reads the tables from a pickle file, which is
41
+ much faster than the other ways offered by subclasses. The pickle
42
+ file is written by calling dump() (after loading the grammar
43
+ tables using a subclass). The report() method prints a readable
44
+ representation of the tables to stdout, for debugging.
45
+
46
+ The instance variables are as follows:
47
+
48
+ symbol2number -- a dict mapping symbol names to numbers. Symbol
49
+ numbers are always 256 or higher, to distinguish
50
+ them from token numbers, which are between 0 and
51
+ 255 (inclusive).
52
+
53
+ number2symbol -- a dict mapping numbers to symbol names;
54
+ these two are each other's inverse.
55
+
56
+ states -- a list of DFAs, where each DFA is a list of
57
+ states, each state is a list of arcs, and each
58
+ arc is a (i, j) pair where i is a label and j is
59
+ a state number. The DFA number is the index into
60
+ this list. (This name is slightly confusing.)
61
+ Final states are represented by a special arc of
62
+ the form (0, j) where j is its own state number.
63
+
64
+ dfas -- a dict mapping symbol numbers to (DFA, first)
65
+ pairs, where DFA is an item from the states list
66
+ above, and first is a set of tokens that can
67
+ begin this grammar rule (represented by a dict
68
+ whose values are always 1).
69
+
70
+ labels -- a list of (x, y) pairs where x is either a token
71
+ number or a symbol number, and y is either None
72
+ or a string; the strings are keywords. The label
73
+ number is the index in this list; label numbers
74
+ are used to mark state transitions (arcs) in the
75
+ DFAs.
76
+
77
+ start -- the number of the grammar's start symbol.
78
+
79
+ keywords -- a dict mapping keyword strings to arc labels.
80
+
81
+ tokens -- a dict mapping token numbers to arc labels.
82
+
83
+ """
84
+
85
+ def __init__(self) -> None:
86
+ self.symbol2number: dict[str, int] = {}
87
+ self.number2symbol: dict[int, str] = {}
88
+ self.states: list[DFA] = []
89
+ self.dfas: dict[int, DFAS] = {}
90
+ self.labels: list[Label] = [(0, "EMPTY")]
91
+ self.keywords: dict[str, int] = {}
92
+ self.soft_keywords: dict[str, int] = {}
93
+ self.tokens: dict[int, int] = {}
94
+ self.symbol2label: dict[str, int] = {}
95
+ self.version: tuple[int, int] = (0, 0)
96
+ self.start = 256
97
+ # Python 3.7+ parses async as a keyword, not an identifier
98
+ self.async_keywords = False
99
+
100
+ def dump(self, filename: Path) -> None:
101
+ """Dump the grammar tables to a pickle file."""
102
+
103
+ # mypyc generates objects that don't have a __dict__, but they
104
+ # do have __getstate__ methods that will return an equivalent
105
+ # dictionary
106
+ if hasattr(self, "__dict__"):
107
+ d = self.__dict__
108
+ else:
109
+ d = self.__getstate__() # type: ignore
110
+
111
+ with tempfile.NamedTemporaryFile(
112
+ dir=os.path.dirname(filename), delete=False
113
+ ) as f:
114
+ pickle.dump(d, f, pickle.HIGHEST_PROTOCOL)
115
+ os.replace(f.name, filename)
116
+
117
+ def _update(self, attrs: dict[str, Any]) -> None:
118
+ for k, v in attrs.items():
119
+ setattr(self, k, v)
120
+
121
+ def load(self, filename: Path) -> None:
122
+ """Load the grammar tables from a pickle file."""
123
+ with open(filename, "rb") as f:
124
+ d = pickle.load(f)
125
+ self._update(d)
126
+
127
+ def loads(self, pkl: bytes) -> None:
128
+ """Load the grammar tables from a pickle bytes object."""
129
+ self._update(pickle.loads(pkl))
130
+
131
+ def copy(self: _P) -> _P:
132
+ """
133
+ Copy the grammar.
134
+ """
135
+ new = self.__class__()
136
+ for dict_attr in (
137
+ "symbol2number",
138
+ "number2symbol",
139
+ "dfas",
140
+ "keywords",
141
+ "soft_keywords",
142
+ "tokens",
143
+ "symbol2label",
144
+ ):
145
+ setattr(new, dict_attr, getattr(self, dict_attr).copy())
146
+ new.labels = self.labels[:]
147
+ new.states = self.states[:]
148
+ new.start = self.start
149
+ new.version = self.version
150
+ new.async_keywords = self.async_keywords
151
+ return new
152
+
153
+ def report(self) -> None:
154
+ """Dump the grammar tables to standard output, for debugging."""
155
+ from pprint import pprint
156
+
157
+ print("s2n")
158
+ pprint(self.symbol2number)
159
+ print("n2s")
160
+ pprint(self.number2symbol)
161
+ print("states")
162
+ pprint(self.states)
163
+ print("dfas")
164
+ pprint(self.dfas)
165
+ print("labels")
166
+ pprint(self.labels)
167
+ print("start", self.start)
168
+
169
+
170
+ # Map from operator to number (since tokenize doesn't do this)
171
+
172
+ opmap_raw = """
173
+ ( LPAR
174
+ ) RPAR
175
+ [ LSQB
176
+ ] RSQB
177
+ : COLON
178
+ , COMMA
179
+ ; SEMI
180
+ + PLUS
181
+ - MINUS
182
+ * STAR
183
+ / SLASH
184
+ | VBAR
185
+ & AMPER
186
+ < LESS
187
+ > GREATER
188
+ = EQUAL
189
+ . DOT
190
+ % PERCENT
191
+ ` BACKQUOTE
192
+ { LBRACE
193
+ } RBRACE
194
+ @ AT
195
+ @= ATEQUAL
196
+ == EQEQUAL
197
+ != NOTEQUAL
198
+ <> NOTEQUAL
199
+ <= LESSEQUAL
200
+ >= GREATEREQUAL
201
+ ~ TILDE
202
+ ^ CIRCUMFLEX
203
+ << LEFTSHIFT
204
+ >> RIGHTSHIFT
205
+ ** DOUBLESTAR
206
+ += PLUSEQUAL
207
+ -= MINEQUAL
208
+ *= STAREQUAL
209
+ /= SLASHEQUAL
210
+ %= PERCENTEQUAL
211
+ &= AMPEREQUAL
212
+ |= VBAREQUAL
213
+ ^= CIRCUMFLEXEQUAL
214
+ <<= LEFTSHIFTEQUAL
215
+ >>= RIGHTSHIFTEQUAL
216
+ **= DOUBLESTAREQUAL
217
+ // DOUBLESLASH
218
+ //= DOUBLESLASHEQUAL
219
+ -> RARROW
220
+ := COLONEQUAL
221
+ ! BANG
222
+ """
223
+
224
+ opmap = {}
225
+ for line in opmap_raw.splitlines():
226
+ if line:
227
+ op, name = line.split()
228
+ opmap[op] = getattr(token, name)
py311/lib/python3.11/site-packages/blib2to3/pgen2/literals.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/literals.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ """Safely evaluate Python string literals without using eval()."""
5
+
6
+ import re
7
+
8
+ simple_escapes: dict[str, str] = {
9
+ "a": "\a",
10
+ "b": "\b",
11
+ "f": "\f",
12
+ "n": "\n",
13
+ "r": "\r",
14
+ "t": "\t",
15
+ "v": "\v",
16
+ "'": "'",
17
+ '"': '"',
18
+ "\\": "\\",
19
+ }
20
+
21
+
22
+ def escape(m: re.Match[str]) -> str:
23
+ all, tail = m.group(0, 1)
24
+ assert all.startswith("\\")
25
+ esc = simple_escapes.get(tail)
26
+ if esc is not None:
27
+ return esc
28
+ if tail.startswith("x"):
29
+ hexes = tail[1:]
30
+ if len(hexes) < 2:
31
+ raise ValueError(f"invalid hex string escape ('\\{tail}')")
32
+ try:
33
+ i = int(hexes, 16)
34
+ except ValueError:
35
+ raise ValueError(f"invalid hex string escape ('\\{tail}')") from None
36
+ else:
37
+ try:
38
+ i = int(tail, 8)
39
+ except ValueError:
40
+ raise ValueError(f"invalid octal string escape ('\\{tail}')") from None
41
+ return chr(i)
42
+
43
+
44
+ def evalString(s: str) -> str:
45
+ assert s.startswith("'") or s.startswith('"'), repr(s[:1])
46
+ q = s[0]
47
+ if s[:3] == q * 3:
48
+ q = q * 3
49
+ assert s.endswith(q), repr(s[-len(q) :])
50
+ assert len(s) >= 2 * len(q)
51
+ s = s[len(q) : -len(q)]
52
+ return re.sub(r"\\(\'|\"|\\|[abfnrtv]|x.{0,2}|[0-7]{1,3})", escape, s)
53
+
54
+
55
+ def test() -> None:
56
+ for i in range(256):
57
+ c = chr(i)
58
+ s = repr(c)
59
+ e = evalString(s)
60
+ if e != c:
61
+ print(i, c, s, e)
62
+
63
+
64
+ if __name__ == "__main__":
65
+ test()
py311/lib/python3.11/site-packages/blib2to3/pgen2/parse.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/parse.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ """Parser engine for the grammar tables generated by pgen.
5
+
6
+ The grammar table must be loaded first.
7
+
8
+ See Parser/parser.c in the Python distribution for additional info on
9
+ how this parsing engine works.
10
+
11
+ """
12
+
13
+ from collections.abc import Callable, Iterator
14
+ from contextlib import contextmanager
15
+ from typing import TYPE_CHECKING, Union, cast
16
+
17
+ from blib2to3.pgen2.grammar import Grammar
18
+ from blib2to3.pytree import NL, Context, Leaf, Node, RawNode, convert
19
+
20
+ # Local imports
21
+ from . import grammar, token, tokenize
22
+
23
+ if TYPE_CHECKING:
24
+ from blib2to3.pgen2.driver import TokenProxy
25
+
26
+
27
+ Results = dict[str, NL]
28
+ Convert = Callable[[Grammar, RawNode], Union[Node, Leaf]]
29
+ DFA = list[list[tuple[int, int]]]
30
+ DFAS = tuple[DFA, dict[int, int]]
31
+
32
+
33
+ def lam_sub(grammar: Grammar, node: RawNode) -> NL:
34
+ assert node[3] is not None
35
+ return Node(type=node[0], children=node[3], context=node[2])
36
+
37
+
38
+ # A placeholder node, used when parser is backtracking.
39
+ DUMMY_NODE = (-1, None, None, None)
40
+
41
+
42
+ def stack_copy(
43
+ stack: list[tuple[DFAS, int, RawNode]],
44
+ ) -> list[tuple[DFAS, int, RawNode]]:
45
+ """Nodeless stack copy."""
46
+ return [(dfa, label, DUMMY_NODE) for dfa, label, _ in stack]
47
+
48
+
49
+ class Recorder:
50
+ def __init__(self, parser: "Parser", ilabels: list[int], context: Context) -> None:
51
+ self.parser = parser
52
+ self._ilabels = ilabels
53
+ self.context = context # not really matter
54
+
55
+ self._dead_ilabels: set[int] = set()
56
+ self._start_point = self.parser.stack
57
+ self._points = {ilabel: stack_copy(self._start_point) for ilabel in ilabels}
58
+
59
+ @property
60
+ def ilabels(self) -> set[int]:
61
+ return self._dead_ilabels.symmetric_difference(self._ilabels)
62
+
63
+ @contextmanager
64
+ def switch_to(self, ilabel: int) -> Iterator[None]:
65
+ with self.backtrack():
66
+ self.parser.stack = self._points[ilabel]
67
+ try:
68
+ yield
69
+ except ParseError:
70
+ self._dead_ilabels.add(ilabel)
71
+ finally:
72
+ self.parser.stack = self._start_point
73
+
74
+ @contextmanager
75
+ def backtrack(self) -> Iterator[None]:
76
+ """
77
+ Use the node-level invariant ones for basic parsing operations (push/pop/shift).
78
+ These still will operate on the stack; but they won't create any new nodes, or
79
+ modify the contents of any other existing nodes.
80
+
81
+ This saves us a ton of time when we are backtracking, since we
82
+ want to restore to the initial state as quick as possible, which
83
+ can only be done by having as little mutatations as possible.
84
+ """
85
+ is_backtracking = self.parser.is_backtracking
86
+ try:
87
+ self.parser.is_backtracking = True
88
+ yield
89
+ finally:
90
+ self.parser.is_backtracking = is_backtracking
91
+
92
+ def add_token(self, tok_type: int, tok_val: str, raw: bool = False) -> None:
93
+ for ilabel in self.ilabels:
94
+ with self.switch_to(ilabel):
95
+ if raw:
96
+ self.parser._addtoken(ilabel, tok_type, tok_val, self.context)
97
+ else:
98
+ self.parser.addtoken(tok_type, tok_val, self.context)
99
+
100
+ def determine_route(
101
+ self, value: str | None = None, force: bool = False
102
+ ) -> int | None:
103
+ alive_ilabels = self.ilabels
104
+ if len(alive_ilabels) == 0:
105
+ *_, most_successful_ilabel = self._dead_ilabels
106
+ raise ParseError("bad input", most_successful_ilabel, value, self.context)
107
+
108
+ ilabel, *rest = alive_ilabels
109
+ if force or not rest:
110
+ return ilabel
111
+ else:
112
+ return None
113
+
114
+
115
+ class ParseError(Exception):
116
+ """Exception to signal the parser is stuck."""
117
+
118
+ def __init__(
119
+ self, msg: str, type: int | None, value: str | None, context: Context
120
+ ) -> None:
121
+ Exception.__init__(
122
+ self, f"{msg}: type={type!r}, value={value!r}, context={context!r}"
123
+ )
124
+ self.msg = msg
125
+ self.type = type
126
+ self.value = value
127
+ self.context = context
128
+
129
+
130
+ class Parser:
131
+ """Parser engine.
132
+
133
+ The proper usage sequence is:
134
+
135
+ p = Parser(grammar, [converter]) # create instance
136
+ p.setup([start]) # prepare for parsing
137
+ <for each input token>:
138
+ if p.addtoken(...): # parse a token; may raise ParseError
139
+ break
140
+ root = p.rootnode # root of abstract syntax tree
141
+
142
+ A Parser instance may be reused by calling setup() repeatedly.
143
+
144
+ A Parser instance contains state pertaining to the current token
145
+ sequence, and should not be used concurrently by different threads
146
+ to parse separate token sequences.
147
+
148
+ See driver.py for how to get input tokens by tokenizing a file or
149
+ string.
150
+
151
+ Parsing is complete when addtoken() returns True; the root of the
152
+ abstract syntax tree can then be retrieved from the rootnode
153
+ instance variable. When a syntax error occurs, addtoken() raises
154
+ the ParseError exception. There is no error recovery; the parser
155
+ cannot be used after a syntax error was reported (but it can be
156
+ reinitialized by calling setup()).
157
+
158
+ """
159
+
160
+ def __init__(self, grammar: Grammar, convert: Convert | None = None) -> None:
161
+ """Constructor.
162
+
163
+ The grammar argument is a grammar.Grammar instance; see the
164
+ grammar module for more information.
165
+
166
+ The parser is not ready yet for parsing; you must call the
167
+ setup() method to get it started.
168
+
169
+ The optional convert argument is a function mapping concrete
170
+ syntax tree nodes to abstract syntax tree nodes. If not
171
+ given, no conversion is done and the syntax tree produced is
172
+ the concrete syntax tree. If given, it must be a function of
173
+ two arguments, the first being the grammar (a grammar.Grammar
174
+ instance), and the second being the concrete syntax tree node
175
+ to be converted. The syntax tree is converted from the bottom
176
+ up.
177
+
178
+ **post-note: the convert argument is ignored since for Black's
179
+ usage, convert will always be blib2to3.pytree.convert. Allowing
180
+ this to be dynamic hurts mypyc's ability to use early binding.
181
+ These docs are left for historical and informational value.
182
+
183
+ A concrete syntax tree node is a (type, value, context, nodes)
184
+ tuple, where type is the node type (a token or symbol number),
185
+ value is None for symbols and a string for tokens, context is
186
+ None or an opaque value used for error reporting (typically a
187
+ (lineno, offset) pair), and nodes is a list of children for
188
+ symbols, and None for tokens.
189
+
190
+ An abstract syntax tree node may be anything; this is entirely
191
+ up to the converter function.
192
+
193
+ """
194
+ self.grammar = grammar
195
+ # See note in docstring above. TL;DR this is ignored.
196
+ self.convert = convert or lam_sub
197
+ self.is_backtracking = False
198
+ self.last_token: int | None = None
199
+
200
+ def setup(self, proxy: "TokenProxy", start: int | None = None) -> None:
201
+ """Prepare for parsing.
202
+
203
+ This *must* be called before starting to parse.
204
+
205
+ The optional argument is an alternative start symbol; it
206
+ defaults to the grammar's start symbol.
207
+
208
+ You can use a Parser instance to parse any number of programs;
209
+ each time you call setup() the parser is reset to an initial
210
+ state determined by the (implicit or explicit) start symbol.
211
+
212
+ """
213
+ if start is None:
214
+ start = self.grammar.start
215
+ # Each stack entry is a tuple: (dfa, state, node).
216
+ # A node is a tuple: (type, value, context, children),
217
+ # where children is a list of nodes or None, and context may be None.
218
+ newnode: RawNode = (start, None, None, [])
219
+ stackentry = (self.grammar.dfas[start], 0, newnode)
220
+ self.stack: list[tuple[DFAS, int, RawNode]] = [stackentry]
221
+ self.rootnode: NL | None = None
222
+ self.used_names: set[str] = set()
223
+ self.proxy = proxy
224
+ self.last_token = None
225
+
226
+ def addtoken(self, type: int, value: str, context: Context) -> bool:
227
+ """Add a token; return True iff this is the end of the program."""
228
+ # Map from token to label
229
+ ilabels = self.classify(type, value, context)
230
+ assert len(ilabels) >= 1
231
+
232
+ # If we have only one state to advance, we'll directly
233
+ # take it as is.
234
+ if len(ilabels) == 1:
235
+ [ilabel] = ilabels
236
+ return self._addtoken(ilabel, type, value, context)
237
+
238
+ # If there are multiple states which we can advance (only
239
+ # happen under soft-keywords), then we will try all of them
240
+ # in parallel and as soon as one state can reach further than
241
+ # the rest, we'll choose that one. This is a pretty hacky
242
+ # and hopefully temporary algorithm.
243
+ #
244
+ # For a more detailed explanation, check out this post:
245
+ # https://tree.science/what-the-backtracking.html
246
+
247
+ with self.proxy.release() as proxy:
248
+ counter, force = 0, False
249
+ recorder = Recorder(self, ilabels, context)
250
+ recorder.add_token(type, value, raw=True)
251
+
252
+ next_token_value = value
253
+ while recorder.determine_route(next_token_value) is None:
254
+ if not proxy.can_advance(counter):
255
+ force = True
256
+ break
257
+
258
+ next_token_type, next_token_value, *_ = proxy.eat(counter)
259
+ if next_token_type in (tokenize.COMMENT, tokenize.NL):
260
+ counter += 1
261
+ continue
262
+
263
+ if next_token_type == tokenize.OP:
264
+ next_token_type = grammar.opmap[next_token_value]
265
+
266
+ recorder.add_token(next_token_type, next_token_value)
267
+ counter += 1
268
+
269
+ ilabel = cast(int, recorder.determine_route(next_token_value, force=force))
270
+ assert ilabel is not None
271
+
272
+ return self._addtoken(ilabel, type, value, context)
273
+
274
+ def _addtoken(self, ilabel: int, type: int, value: str, context: Context) -> bool:
275
+ # Loop until the token is shifted; may raise exceptions
276
+ while True:
277
+ dfa, state, node = self.stack[-1]
278
+ states, first = dfa
279
+ arcs = states[state]
280
+ # Look for a state with this label
281
+ for i, newstate in arcs:
282
+ t = self.grammar.labels[i][0]
283
+ if t >= 256:
284
+ # See if it's a symbol and if we're in its first set
285
+ itsdfa = self.grammar.dfas[t]
286
+ itsstates, itsfirst = itsdfa
287
+ if ilabel in itsfirst:
288
+ # Push a symbol
289
+ self.push(t, itsdfa, newstate, context)
290
+ break # To continue the outer while loop
291
+
292
+ elif ilabel == i:
293
+ # Look it up in the list of labels
294
+ # Shift a token; we're done with it
295
+ self.shift(type, value, newstate, context)
296
+ # Pop while we are in an accept-only state
297
+ state = newstate
298
+ while states[state] == [(0, state)]:
299
+ self.pop()
300
+ if not self.stack:
301
+ # Done parsing!
302
+ return True
303
+ dfa, state, node = self.stack[-1]
304
+ states, first = dfa
305
+ # Done with this token
306
+ self.last_token = type
307
+ return False
308
+
309
+ else:
310
+ if (0, state) in arcs:
311
+ # An accepting state, pop it and try something else
312
+ self.pop()
313
+ if not self.stack:
314
+ # Done parsing, but another token is input
315
+ raise ParseError("too much input", type, value, context)
316
+ else:
317
+ # No success finding a transition
318
+ raise ParseError("bad input", type, value, context)
319
+
320
+ def classify(self, type: int, value: str, context: Context) -> list[int]:
321
+ """Turn a token into a label. (Internal)
322
+
323
+ Depending on whether the value is a soft-keyword or not,
324
+ this function may return multiple labels to choose from."""
325
+ if type == token.NAME:
326
+ # Keep a listing of all used names
327
+ self.used_names.add(value)
328
+ # Check for reserved words
329
+ if value in self.grammar.keywords:
330
+ return [self.grammar.keywords[value]]
331
+ elif value in self.grammar.soft_keywords:
332
+ assert type in self.grammar.tokens
333
+ # Current soft keywords (match, case, type) can only appear at the
334
+ # beginning of a statement. So as a shortcut, don't try to treat them
335
+ # like keywords in any other context.
336
+ # ('_' is also a soft keyword in the real grammar, but for our grammar
337
+ # it's just an expression, so we don't need to treat it specially.)
338
+ if self.last_token not in (
339
+ None,
340
+ token.INDENT,
341
+ token.DEDENT,
342
+ token.NEWLINE,
343
+ token.SEMI,
344
+ token.COLON,
345
+ ):
346
+ return [self.grammar.tokens[type]]
347
+ return [
348
+ self.grammar.tokens[type],
349
+ self.grammar.soft_keywords[value],
350
+ ]
351
+
352
+ ilabel = self.grammar.tokens.get(type)
353
+ if ilabel is None:
354
+ raise ParseError("bad token", type, value, context)
355
+ return [ilabel]
356
+
357
+ def shift(self, type: int, value: str, newstate: int, context: Context) -> None:
358
+ """Shift a token. (Internal)"""
359
+ if self.is_backtracking:
360
+ dfa, state, _ = self.stack[-1]
361
+ self.stack[-1] = (dfa, newstate, DUMMY_NODE)
362
+ else:
363
+ dfa, state, node = self.stack[-1]
364
+ rawnode: RawNode = (type, value, context, None)
365
+ newnode = convert(self.grammar, rawnode)
366
+ assert node[-1] is not None
367
+ node[-1].append(newnode)
368
+ self.stack[-1] = (dfa, newstate, node)
369
+
370
+ def push(self, type: int, newdfa: DFAS, newstate: int, context: Context) -> None:
371
+ """Push a nonterminal. (Internal)"""
372
+ if self.is_backtracking:
373
+ dfa, state, _ = self.stack[-1]
374
+ self.stack[-1] = (dfa, newstate, DUMMY_NODE)
375
+ self.stack.append((newdfa, 0, DUMMY_NODE))
376
+ else:
377
+ dfa, state, node = self.stack[-1]
378
+ newnode: RawNode = (type, None, context, [])
379
+ self.stack[-1] = (dfa, newstate, node)
380
+ self.stack.append((newdfa, 0, newnode))
381
+
382
+ def pop(self) -> None:
383
+ """Pop a nonterminal. (Internal)"""
384
+ if self.is_backtracking:
385
+ self.stack.pop()
386
+ else:
387
+ popdfa, popstate, popnode = self.stack.pop()
388
+ newnode = convert(self.grammar, popnode)
389
+ if self.stack:
390
+ dfa, state, node = self.stack[-1]
391
+ assert node[-1] is not None
392
+ node[-1].append(newnode)
393
+ else:
394
+ self.rootnode = newnode
395
+ self.rootnode.used_names = self.used_names
py311/lib/python3.11/site-packages/blib2to3/pgen2/pgen.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/pgen.py ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
2
+ # Licensed to PSF under a Contributor Agreement.
3
+
4
+ import os
5
+ from collections.abc import Iterator, Sequence
6
+ from typing import IO, Any, NoReturn, Union
7
+
8
+ from blib2to3.pgen2 import grammar, token, tokenize
9
+ from blib2to3.pgen2.tokenize import TokenInfo
10
+
11
+ Path = Union[str, "os.PathLike[str]"]
12
+
13
+
14
+ class PgenGrammar(grammar.Grammar):
15
+ pass
16
+
17
+
18
+ class ParserGenerator:
19
+ filename: Path
20
+ stream: IO[str]
21
+ generator: Iterator[TokenInfo]
22
+ first: dict[str, dict[str, int] | None]
23
+
24
+ def __init__(self, filename: Path, stream: IO[str] | None = None) -> None:
25
+ close_stream = None
26
+ if stream is None:
27
+ stream = open(filename, encoding="utf-8")
28
+ close_stream = stream.close
29
+ self.filename = filename
30
+ self.generator = tokenize.tokenize(stream.read())
31
+ self.gettoken() # Initialize lookahead
32
+ self.dfas, self.startsymbol = self.parse()
33
+ if close_stream is not None:
34
+ close_stream()
35
+ self.first = {} # map from symbol name to set of tokens
36
+ self.addfirstsets()
37
+
38
+ def make_grammar(self) -> PgenGrammar:
39
+ c = PgenGrammar()
40
+ names = list(self.dfas.keys())
41
+ names.sort()
42
+ names.remove(self.startsymbol)
43
+ names.insert(0, self.startsymbol)
44
+ for name in names:
45
+ i = 256 + len(c.symbol2number)
46
+ c.symbol2number[name] = i
47
+ c.number2symbol[i] = name
48
+ for name in names:
49
+ dfa = self.dfas[name]
50
+ states = []
51
+ for state in dfa:
52
+ arcs = []
53
+ for label, next in sorted(state.arcs.items()):
54
+ arcs.append((self.make_label(c, label), dfa.index(next)))
55
+ if state.isfinal:
56
+ arcs.append((0, dfa.index(state)))
57
+ states.append(arcs)
58
+ c.states.append(states)
59
+ c.dfas[c.symbol2number[name]] = (states, self.make_first(c, name))
60
+ c.start = c.symbol2number[self.startsymbol]
61
+ return c
62
+
63
+ def make_first(self, c: PgenGrammar, name: str) -> dict[int, int]:
64
+ rawfirst = self.first[name]
65
+ assert rawfirst is not None
66
+ first = {}
67
+ for label in sorted(rawfirst):
68
+ ilabel = self.make_label(c, label)
69
+ ##assert ilabel not in first # XXX failed on <> ... !=
70
+ first[ilabel] = 1
71
+ return first
72
+
73
+ def make_label(self, c: PgenGrammar, label: str) -> int:
74
+ # XXX Maybe this should be a method on a subclass of converter?
75
+ ilabel = len(c.labels)
76
+ if label[0].isalpha():
77
+ # Either a symbol name or a named token
78
+ if label in c.symbol2number:
79
+ # A symbol name (a non-terminal)
80
+ if label in c.symbol2label:
81
+ return c.symbol2label[label]
82
+ else:
83
+ c.labels.append((c.symbol2number[label], None))
84
+ c.symbol2label[label] = ilabel
85
+ return ilabel
86
+ else:
87
+ # A named token (NAME, NUMBER, STRING)
88
+ itoken = getattr(token, label, None)
89
+ assert isinstance(itoken, int), label
90
+ assert itoken in token.tok_name, label
91
+ if itoken in c.tokens:
92
+ return c.tokens[itoken]
93
+ else:
94
+ c.labels.append((itoken, None))
95
+ c.tokens[itoken] = ilabel
96
+ return ilabel
97
+ else:
98
+ # Either a keyword or an operator
99
+ assert label[0] in ('"', "'"), label
100
+ value = eval(label)
101
+ if value[0].isalpha():
102
+ if label[0] == '"':
103
+ keywords = c.soft_keywords
104
+ else:
105
+ keywords = c.keywords
106
+
107
+ # A keyword
108
+ if value in keywords:
109
+ return keywords[value]
110
+ else:
111
+ c.labels.append((token.NAME, value))
112
+ keywords[value] = ilabel
113
+ return ilabel
114
+ else:
115
+ # An operator (any non-numeric token)
116
+ itoken = grammar.opmap[value] # Fails if unknown token
117
+ if itoken in c.tokens:
118
+ return c.tokens[itoken]
119
+ else:
120
+ c.labels.append((itoken, None))
121
+ c.tokens[itoken] = ilabel
122
+ return ilabel
123
+
124
+ def addfirstsets(self) -> None:
125
+ names = list(self.dfas.keys())
126
+ names.sort()
127
+ for name in names:
128
+ if name not in self.first:
129
+ self.calcfirst(name)
130
+ # print name, self.first[name].keys()
131
+
132
+ def calcfirst(self, name: str) -> None:
133
+ dfa = self.dfas[name]
134
+ self.first[name] = None # dummy to detect left recursion
135
+ state = dfa[0]
136
+ totalset: dict[str, int] = {}
137
+ overlapcheck = {}
138
+ for label in state.arcs:
139
+ if label in self.dfas:
140
+ if label in self.first:
141
+ fset = self.first[label]
142
+ if fset is None:
143
+ raise ValueError(f"recursion for rule {name!r}")
144
+ else:
145
+ self.calcfirst(label)
146
+ fset = self.first[label]
147
+ assert fset is not None
148
+ totalset.update(fset)
149
+ overlapcheck[label] = fset
150
+ else:
151
+ totalset[label] = 1
152
+ overlapcheck[label] = {label: 1}
153
+ inverse: dict[str, str] = {}
154
+ for label, itsfirst in overlapcheck.items():
155
+ for symbol in itsfirst:
156
+ if symbol in inverse:
157
+ raise ValueError(
158
+ f"rule {name} is ambiguous; {symbol} is in the first sets of"
159
+ f" {label} as well as {inverse[symbol]}"
160
+ )
161
+ inverse[symbol] = label
162
+ self.first[name] = totalset
163
+
164
+ def parse(self) -> tuple[dict[str, list["DFAState"]], str]:
165
+ dfas = {}
166
+ startsymbol: str | None = None
167
+ # MSTART: (NEWLINE | RULE)* ENDMARKER
168
+ while self.type != token.ENDMARKER:
169
+ while self.type == token.NEWLINE:
170
+ self.gettoken()
171
+ # RULE: NAME ':' RHS NEWLINE
172
+ name = self.expect(token.NAME)
173
+ self.expect(token.OP, ":")
174
+ a, z = self.parse_rhs()
175
+ self.expect(token.NEWLINE)
176
+ # self.dump_nfa(name, a, z)
177
+ dfa = self.make_dfa(a, z)
178
+ # self.dump_dfa(name, dfa)
179
+ # oldlen = len(dfa)
180
+ self.simplify_dfa(dfa)
181
+ # newlen = len(dfa)
182
+ dfas[name] = dfa
183
+ # print name, oldlen, newlen
184
+ if startsymbol is None:
185
+ startsymbol = name
186
+ assert startsymbol is not None
187
+ return dfas, startsymbol
188
+
189
+ def make_dfa(self, start: "NFAState", finish: "NFAState") -> list["DFAState"]:
190
+ # To turn an NFA into a DFA, we define the states of the DFA
191
+ # to correspond to *sets* of states of the NFA. Then do some
192
+ # state reduction. Let's represent sets as dicts with 1 for
193
+ # values.
194
+ assert isinstance(start, NFAState)
195
+ assert isinstance(finish, NFAState)
196
+
197
+ def closure(state: NFAState) -> dict[NFAState, int]:
198
+ base: dict[NFAState, int] = {}
199
+ addclosure(state, base)
200
+ return base
201
+
202
+ def addclosure(state: NFAState, base: dict[NFAState, int]) -> None:
203
+ assert isinstance(state, NFAState)
204
+ if state in base:
205
+ return
206
+ base[state] = 1
207
+ for label, next in state.arcs:
208
+ if label is None:
209
+ addclosure(next, base)
210
+
211
+ states = [DFAState(closure(start), finish)]
212
+ for state in states: # NB states grows while we're iterating
213
+ arcs: dict[str, dict[NFAState, int]] = {}
214
+ for nfastate in state.nfaset:
215
+ for label, next in nfastate.arcs:
216
+ if label is not None:
217
+ addclosure(next, arcs.setdefault(label, {}))
218
+ for label, nfaset in sorted(arcs.items()):
219
+ for st in states:
220
+ if st.nfaset == nfaset:
221
+ break
222
+ else:
223
+ st = DFAState(nfaset, finish)
224
+ states.append(st)
225
+ state.addarc(st, label)
226
+ return states # List of DFAState instances; first one is start
227
+
228
+ def dump_nfa(self, name: str, start: "NFAState", finish: "NFAState") -> None:
229
+ print("Dump of NFA for", name)
230
+ todo = [start]
231
+ for i, state in enumerate(todo):
232
+ print(" State", i, state is finish and "(final)" or "")
233
+ for label, next in state.arcs:
234
+ if next in todo:
235
+ j = todo.index(next)
236
+ else:
237
+ j = len(todo)
238
+ todo.append(next)
239
+ if label is None:
240
+ print(f" -> {j}")
241
+ else:
242
+ print(f" {label} -> {j}")
243
+
244
+ def dump_dfa(self, name: str, dfa: Sequence["DFAState"]) -> None:
245
+ print("Dump of DFA for", name)
246
+ for i, state in enumerate(dfa):
247
+ print(" State", i, state.isfinal and "(final)" or "")
248
+ for label, next in sorted(state.arcs.items()):
249
+ print(f" {label} -> {dfa.index(next)}")
250
+
251
+ def simplify_dfa(self, dfa: list["DFAState"]) -> None:
252
+ # This is not theoretically optimal, but works well enough.
253
+ # Algorithm: repeatedly look for two states that have the same
254
+ # set of arcs (same labels pointing to the same nodes) and
255
+ # unify them, until things stop changing.
256
+
257
+ # dfa is a list of DFAState instances
258
+ changes = True
259
+ while changes:
260
+ changes = False
261
+ for i, state_i in enumerate(dfa):
262
+ for j in range(i + 1, len(dfa)):
263
+ state_j = dfa[j]
264
+ if state_i == state_j:
265
+ # print " unify", i, j
266
+ del dfa[j]
267
+ for state in dfa:
268
+ state.unifystate(state_j, state_i)
269
+ changes = True
270
+ break
271
+
272
+ def parse_rhs(self) -> tuple["NFAState", "NFAState"]:
273
+ # RHS: ALT ('|' ALT)*
274
+ a, z = self.parse_alt()
275
+ if self.value != "|":
276
+ return a, z
277
+ else:
278
+ aa = NFAState()
279
+ zz = NFAState()
280
+ aa.addarc(a)
281
+ z.addarc(zz)
282
+ while self.value == "|":
283
+ self.gettoken()
284
+ a, z = self.parse_alt()
285
+ aa.addarc(a)
286
+ z.addarc(zz)
287
+ return aa, zz
288
+
289
+ def parse_alt(self) -> tuple["NFAState", "NFAState"]:
290
+ # ALT: ITEM+
291
+ a, b = self.parse_item()
292
+ while self.value in ("(", "[") or self.type in (token.NAME, token.STRING):
293
+ c, d = self.parse_item()
294
+ b.addarc(c)
295
+ b = d
296
+ return a, b
297
+
298
+ def parse_item(self) -> tuple["NFAState", "NFAState"]:
299
+ # ITEM: '[' RHS ']' | ATOM ['+' | '*']
300
+ if self.value == "[":
301
+ self.gettoken()
302
+ a, z = self.parse_rhs()
303
+ self.expect(token.OP, "]")
304
+ a.addarc(z)
305
+ return a, z
306
+ else:
307
+ a, z = self.parse_atom()
308
+ value = self.value
309
+ if value not in ("+", "*"):
310
+ return a, z
311
+ self.gettoken()
312
+ z.addarc(a)
313
+ if value == "+":
314
+ return a, z
315
+ else:
316
+ return a, a
317
+
318
+ def parse_atom(self) -> tuple["NFAState", "NFAState"]:
319
+ # ATOM: '(' RHS ')' | NAME | STRING
320
+ if self.value == "(":
321
+ self.gettoken()
322
+ a, z = self.parse_rhs()
323
+ self.expect(token.OP, ")")
324
+ return a, z
325
+ elif self.type in (token.NAME, token.STRING):
326
+ a = NFAState()
327
+ z = NFAState()
328
+ a.addarc(z, self.value)
329
+ self.gettoken()
330
+ return a, z
331
+ else:
332
+ self.raise_error(
333
+ f"expected (...) or NAME or STRING, got {self.type}/{self.value}"
334
+ )
335
+
336
+ def expect(self, type: int, value: Any | None = None) -> str:
337
+ if self.type != type or (value is not None and self.value != value):
338
+ self.raise_error(f"expected {type}/{value}, got {self.type}/{self.value}")
339
+ value = self.value
340
+ self.gettoken()
341
+ return value
342
+
343
+ def gettoken(self) -> None:
344
+ tup = next(self.generator)
345
+ while tup[0] in (tokenize.COMMENT, tokenize.NL):
346
+ tup = next(self.generator)
347
+ self.type, self.value, self.begin, self.end, self.line = tup
348
+ # print token.tok_name[self.type], repr(self.value)
349
+
350
+ def raise_error(self, msg: str) -> NoReturn:
351
+ raise SyntaxError(
352
+ msg, (str(self.filename), self.end[0], self.end[1], self.line)
353
+ )
354
+
355
+
356
+ class NFAState:
357
+ arcs: list[tuple[str | None, "NFAState"]]
358
+
359
+ def __init__(self) -> None:
360
+ self.arcs = [] # list of (label, NFAState) pairs
361
+
362
+ def addarc(self, next: "NFAState", label: str | None = None) -> None:
363
+ assert label is None or isinstance(label, str)
364
+ assert isinstance(next, NFAState)
365
+ self.arcs.append((label, next))
366
+
367
+
368
+ class DFAState:
369
+ nfaset: dict[NFAState, Any]
370
+ isfinal: bool
371
+ arcs: dict[str, "DFAState"]
372
+
373
+ def __init__(self, nfaset: dict[NFAState, Any], final: NFAState) -> None:
374
+ assert isinstance(nfaset, dict)
375
+ assert isinstance(next(iter(nfaset)), NFAState)
376
+ assert isinstance(final, NFAState)
377
+ self.nfaset = nfaset
378
+ self.isfinal = final in nfaset
379
+ self.arcs = {} # map from label to DFAState
380
+
381
+ def addarc(self, next: "DFAState", label: str) -> None:
382
+ assert isinstance(label, str)
383
+ assert label not in self.arcs
384
+ assert isinstance(next, DFAState)
385
+ self.arcs[label] = next
386
+
387
+ def unifystate(self, old: "DFAState", new: "DFAState") -> None:
388
+ for label, next in self.arcs.items():
389
+ if next is old:
390
+ self.arcs[label] = new
391
+
392
+ def __eq__(self, other: Any) -> bool:
393
+ # Equality test -- ignore the nfaset instance variable
394
+ assert isinstance(other, DFAState)
395
+ if self.isfinal != other.isfinal:
396
+ return False
397
+ # Can't just return self.arcs == other.arcs, because that
398
+ # would invoke this method recursively, with cycles...
399
+ if len(self.arcs) != len(other.arcs):
400
+ return False
401
+ for label, next in self.arcs.items():
402
+ if next is not other.arcs.get(label):
403
+ return False
404
+ return True
405
+
406
+ __hash__: Any = None # For Py3 compatibility.
407
+
408
+
409
+ def generate_grammar(filename: Path = "Grammar.txt") -> PgenGrammar:
410
+ p = ParserGenerator(filename)
411
+ return p.make_grammar()
py311/lib/python3.11/site-packages/blib2to3/pgen2/token.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/token.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Token constants (from "token.h")."""
2
+
3
+ from typing import Final
4
+
5
+ # Taken from Python (r53757) and modified to include some tokens
6
+ # originally monkeypatched in by pgen2.tokenize
7
+
8
+ # --start constants--
9
+ ENDMARKER: Final = 0
10
+ NAME: Final = 1
11
+ NUMBER: Final = 2
12
+ STRING: Final = 3
13
+ NEWLINE: Final = 4
14
+ INDENT: Final = 5
15
+ DEDENT: Final = 6
16
+ LPAR: Final = 7
17
+ RPAR: Final = 8
18
+ LSQB: Final = 9
19
+ RSQB: Final = 10
20
+ COLON: Final = 11
21
+ COMMA: Final = 12
22
+ SEMI: Final = 13
23
+ PLUS: Final = 14
24
+ MINUS: Final = 15
25
+ STAR: Final = 16
26
+ SLASH: Final = 17
27
+ VBAR: Final = 18
28
+ AMPER: Final = 19
29
+ LESS: Final = 20
30
+ GREATER: Final = 21
31
+ EQUAL: Final = 22
32
+ DOT: Final = 23
33
+ PERCENT: Final = 24
34
+ BACKQUOTE: Final = 25
35
+ LBRACE: Final = 26
36
+ RBRACE: Final = 27
37
+ EQEQUAL: Final = 28
38
+ NOTEQUAL: Final = 29
39
+ LESSEQUAL: Final = 30
40
+ GREATEREQUAL: Final = 31
41
+ TILDE: Final = 32
42
+ CIRCUMFLEX: Final = 33
43
+ LEFTSHIFT: Final = 34
44
+ RIGHTSHIFT: Final = 35
45
+ DOUBLESTAR: Final = 36
46
+ PLUSEQUAL: Final = 37
47
+ MINEQUAL: Final = 38
48
+ STAREQUAL: Final = 39
49
+ SLASHEQUAL: Final = 40
50
+ PERCENTEQUAL: Final = 41
51
+ AMPEREQUAL: Final = 42
52
+ VBAREQUAL: Final = 43
53
+ CIRCUMFLEXEQUAL: Final = 44
54
+ LEFTSHIFTEQUAL: Final = 45
55
+ RIGHTSHIFTEQUAL: Final = 46
56
+ DOUBLESTAREQUAL: Final = 47
57
+ DOUBLESLASH: Final = 48
58
+ DOUBLESLASHEQUAL: Final = 49
59
+ AT: Final = 50
60
+ ATEQUAL: Final = 51
61
+ OP: Final = 52
62
+ COMMENT: Final = 53
63
+ NL: Final = 54
64
+ RARROW: Final = 55
65
+ AWAIT: Final = 56
66
+ ASYNC: Final = 57
67
+ ERRORTOKEN: Final = 58
68
+ COLONEQUAL: Final = 59
69
+ FSTRING_START: Final = 60
70
+ FSTRING_MIDDLE: Final = 61
71
+ FSTRING_END: Final = 62
72
+ BANG: Final = 63
73
+ TSTRING_START: Final = 64
74
+ TSTRING_MIDDLE: Final = 65
75
+ TSTRING_END: Final = 66
76
+ N_TOKENS: Final = 67
77
+ NT_OFFSET: Final = 256
78
+ # --end constants--
79
+
80
+ tok_name: Final[dict[int, str]] = {}
81
+ for _name, _value in list(globals().items()):
82
+ if type(_value) is int:
83
+ tok_name[_value] = _name
84
+
85
+
86
+ def ISTERMINAL(x: int) -> bool:
87
+ return x < NT_OFFSET
88
+
89
+
90
+ def ISNONTERMINAL(x: int) -> bool:
91
+ return x >= NT_OFFSET
92
+
93
+
94
+ def ISEOF(x: int) -> bool:
95
+ return x == ENDMARKER
py311/lib/python3.11/site-packages/blib2to3/pgen2/tokenize.cpython-311-x86_64-linux-gnu.so ADDED
Binary file (16 kB). View file
 
py311/lib/python3.11/site-packages/blib2to3/pgen2/tokenize.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation.
2
+ # All rights reserved.
3
+
4
+ # mypy: allow-untyped-defs, allow-untyped-calls
5
+
6
+ """Tokenization help for Python programs.
7
+
8
+ generate_tokens(readline) is a generator that breaks a stream of
9
+ text into Python tokens. It accepts a readline-like method which is called
10
+ repeatedly to get the next line of input (or "" for EOF). It generates
11
+ 5-tuples with these members:
12
+
13
+ the token type (see token.py)
14
+ the token (a string)
15
+ the starting (row, column) indices of the token (a 2-tuple of ints)
16
+ the ending (row, column) indices of the token (a 2-tuple of ints)
17
+ the original line (string)
18
+
19
+ It is designed to match the working of the Python tokenizer exactly, except
20
+ that it produces COMMENT tokens for comments and gives type OP for all
21
+ operators
22
+
23
+ Older entry points
24
+ tokenize_loop(readline, tokeneater)
25
+ tokenize(readline, tokeneater=printtoken)
26
+ are the same, except instead of generating tokens, tokeneater is a callback
27
+ function to which the 5 fields described above are passed as 5 arguments,
28
+ each time a new token is found."""
29
+
30
+ import sys
31
+ from collections.abc import Iterator
32
+
33
+ from blib2to3.pgen2.grammar import Grammar
34
+ from blib2to3.pgen2.token import (
35
+ ASYNC,
36
+ AWAIT,
37
+ COMMENT,
38
+ DEDENT,
39
+ ENDMARKER,
40
+ FSTRING_END,
41
+ FSTRING_MIDDLE,
42
+ FSTRING_START,
43
+ INDENT,
44
+ NAME,
45
+ NEWLINE,
46
+ NL,
47
+ NUMBER,
48
+ OP,
49
+ STRING,
50
+ TSTRING_END,
51
+ TSTRING_MIDDLE,
52
+ TSTRING_START,
53
+ tok_name,
54
+ )
55
+
56
+ __author__ = "Ka-Ping Yee <ping@lfw.org>"
57
+ __credits__ = "GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, Skip Montanaro"
58
+
59
+ import pytokens
60
+ from pytokens import TokenType
61
+
62
+ from . import token as _token
63
+
64
+ __all__ = [x for x in dir(_token) if x[0] != "_"] + [
65
+ "tokenize",
66
+ "generate_tokens",
67
+ "untokenize",
68
+ ]
69
+ del _token
70
+
71
+ Coord = tuple[int, int]
72
+ TokenInfo = tuple[int, str, Coord, Coord, str]
73
+
74
+ TOKEN_TYPE_MAP = {
75
+ TokenType.indent: INDENT,
76
+ TokenType.dedent: DEDENT,
77
+ TokenType.newline: NEWLINE,
78
+ TokenType.nl: NL,
79
+ TokenType.comment: COMMENT,
80
+ TokenType.semicolon: OP,
81
+ TokenType.lparen: OP,
82
+ TokenType.rparen: OP,
83
+ TokenType.lbracket: OP,
84
+ TokenType.rbracket: OP,
85
+ TokenType.lbrace: OP,
86
+ TokenType.rbrace: OP,
87
+ TokenType.colon: OP,
88
+ TokenType.op: OP,
89
+ TokenType.identifier: NAME,
90
+ TokenType.number: NUMBER,
91
+ TokenType.string: STRING,
92
+ TokenType.fstring_start: FSTRING_START,
93
+ TokenType.fstring_middle: FSTRING_MIDDLE,
94
+ TokenType.fstring_end: FSTRING_END,
95
+ TokenType.tstring_start: TSTRING_START,
96
+ TokenType.tstring_middle: TSTRING_MIDDLE,
97
+ TokenType.tstring_end: TSTRING_END,
98
+ TokenType.endmarker: ENDMARKER,
99
+ }
100
+
101
+
102
+ class TokenError(Exception): ...
103
+
104
+
105
+ def transform_whitespace(
106
+ token: pytokens.Token, source: str, prev_token: pytokens.Token | None
107
+ ) -> pytokens.Token:
108
+ r"""
109
+ Black treats `\\\n` at the end of a line as a 'NL' token, while it
110
+ is ignored as whitespace in the regular Python parser.
111
+ But, only the first one. If there's a `\\\n` following it
112
+ (as in, a \ just by itself on a line), that is not made into NL.
113
+ """
114
+ if (
115
+ token.type == TokenType.whitespace
116
+ and prev_token is not None
117
+ and prev_token.type not in (TokenType.nl, TokenType.newline)
118
+ ):
119
+ token_str = source[token.start_index : token.end_index]
120
+ if token_str.startswith("\\\r\n"):
121
+ return pytokens.Token(
122
+ TokenType.nl,
123
+ token.start_index,
124
+ token.start_index + 3,
125
+ token.start_line,
126
+ token.start_col,
127
+ token.start_line,
128
+ token.start_col + 3,
129
+ )
130
+ elif token_str.startswith("\\\n") or token_str.startswith("\\\r"):
131
+ return pytokens.Token(
132
+ TokenType.nl,
133
+ token.start_index,
134
+ token.start_index + 2,
135
+ token.start_line,
136
+ token.start_col,
137
+ token.start_line,
138
+ token.start_col + 2,
139
+ )
140
+
141
+ return token
142
+
143
+
144
+ def tokenize(source: str, grammar: Grammar | None = None) -> Iterator[TokenInfo]:
145
+ lines = source.split("\n")
146
+ lines += [""] # For newline tokens in files that don't end in a newline
147
+ line, column = 1, 0
148
+
149
+ prev_token: pytokens.Token | None = None
150
+ try:
151
+ for token in pytokens.tokenize(source):
152
+ token = transform_whitespace(token, source, prev_token)
153
+
154
+ line, column = token.start_line, token.start_col
155
+ if token.type == TokenType.whitespace:
156
+ continue
157
+
158
+ token_str = source[token.start_index : token.end_index]
159
+
160
+ if token.type == TokenType.newline and token_str == "":
161
+ # Black doesn't yield empty newline tokens at the end of a file
162
+ # if there's no newline at the end of a file.
163
+ prev_token = token
164
+ continue
165
+
166
+ source_line = lines[token.start_line - 1]
167
+
168
+ if token.type == TokenType.identifier and token_str in ("async", "await"):
169
+ # Black uses `async` and `await` token types just for those two keywords
170
+ yield (
171
+ ASYNC if token_str == "async" else AWAIT,
172
+ token_str,
173
+ (token.start_line, token.start_col),
174
+ (token.end_line, token.end_col),
175
+ source_line,
176
+ )
177
+ elif token.type == TokenType.op and token_str == "...":
178
+ # Black doesn't have an ellipsis token yet, yield 3 DOTs instead
179
+ assert token.start_line == token.end_line
180
+ assert token.end_col == token.start_col + 3
181
+
182
+ token_str = "."
183
+ for start_col in range(token.start_col, token.start_col + 3):
184
+ end_col = start_col + 1
185
+ yield (
186
+ TOKEN_TYPE_MAP[token.type],
187
+ token_str,
188
+ (token.start_line, start_col),
189
+ (token.end_line, end_col),
190
+ source_line,
191
+ )
192
+ else:
193
+ token_type = TOKEN_TYPE_MAP.get(token.type)
194
+ if token_type is None:
195
+ raise ValueError(f"Unknown token type: {token.type!r}")
196
+ yield (
197
+ TOKEN_TYPE_MAP[token.type],
198
+ token_str,
199
+ (token.start_line, token.start_col),
200
+ (token.end_line, token.end_col),
201
+ source_line,
202
+ )
203
+ prev_token = token
204
+
205
+ except pytokens.UnexpectedEOF:
206
+ raise TokenError("Unexpected EOF in multi-line statement", (line, column))
207
+ except pytokens.TokenizeError as exc:
208
+ raise TokenError(f"Failed to parse: {type(exc).__name__}", (line, column))
209
+
210
+
211
+ def printtoken(
212
+ type: int, token: str, srow_col: Coord, erow_col: Coord, line: str
213
+ ) -> None: # for testing
214
+ srow, scol = srow_col
215
+ erow, ecol = erow_col
216
+ print(f"{srow},{scol}-{erow},{ecol}:\t{tok_name[type]}\t{token!r}")
217
+
218
+
219
+ if __name__ == "__main__": # testing
220
+ if len(sys.argv) > 1:
221
+ token_iterator = tokenize(open(sys.argv[1]).read())
222
+ else:
223
+ token_iterator = tokenize(sys.stdin.read())
224
+
225
+ for tok in token_iterator:
226
+ printtoken(*tok)
py311/lib/python3.11/site-packages/google/api_core/__init__.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Google API Core.
16
+
17
+ This package contains common code and utilities used by Google client libraries.
18
+ """
19
+
20
+ from google.api_core import _python_package_support
21
+ from google.api_core import _python_version_support
22
+ from google.api_core import version as api_core_version
23
+
24
+ __version__ = api_core_version.__version__
25
+
26
+ # NOTE: Until dependent artifacts require this version of
27
+ # google.api_core, the functionality below must be made available
28
+ # manually in those artifacts.
29
+
30
+ # expose dependency checks for external callers
31
+ check_python_version = _python_version_support.check_python_version
32
+ check_dependency_versions = _python_package_support.check_dependency_versions
33
+ parse_version_to_tuple = _python_package_support.parse_version_to_tuple
34
+ warn_deprecation_for_versions_less_than = (
35
+ _python_package_support.warn_deprecation_for_versions_less_than
36
+ )
37
+ DependencyConstraint = _python_package_support.DependencyConstraint
38
+
39
+ # perform version checks against api_core, and emit warnings if needed
40
+ check_python_version(package="google.api_core")
41
+ check_dependency_versions("google.api_core")
py311/lib/python3.11/site-packages/google/api_core/_python_package_support.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Code to check versions of dependencies used by Google Cloud Client Libraries."""
16
+
17
+ import warnings
18
+ import sys
19
+ from typing import Optional, Tuple
20
+
21
+ from collections import namedtuple
22
+
23
+ from ._python_version_support import (
24
+ _flatten_message,
25
+ _get_distribution_and_import_packages,
26
+ )
27
+
28
+ if sys.version_info >= (3, 8):
29
+ from importlib import metadata
30
+ else:
31
+ # TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
32
+ # this code path once we drop support for Python 3.7
33
+ import importlib_metadata as metadata
34
+
35
+ ParsedVersion = Tuple[int, ...]
36
+
37
+ # Here we list all the packages for which we want to issue warnings
38
+ # about deprecated and unsupported versions.
39
+ DependencyConstraint = namedtuple(
40
+ "DependencyConstraint",
41
+ ["package_name", "minimum_fully_supported_version", "recommended_version"],
42
+ )
43
+ _PACKAGE_DEPENDENCY_WARNINGS = [
44
+ DependencyConstraint(
45
+ "google.protobuf",
46
+ minimum_fully_supported_version="4.25.8",
47
+ recommended_version="6.x",
48
+ )
49
+ ]
50
+
51
+
52
+ DependencyVersion = namedtuple("DependencyVersion", ["version", "version_string"])
53
+ # Version string we provide in a DependencyVersion when we can't determine the version of a
54
+ # package.
55
+ UNKNOWN_VERSION_STRING = "--"
56
+
57
+
58
+ def parse_version_to_tuple(version_string: str) -> ParsedVersion:
59
+ """Safely converts a semantic version string to a comparable tuple of integers.
60
+
61
+ Example: "4.25.8" -> (4, 25, 8)
62
+ Ignores non-numeric parts and handles common version formats.
63
+
64
+ Args:
65
+ version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
66
+
67
+ Returns:
68
+ Tuple of integers for the parsed version string.
69
+ """
70
+ parts = []
71
+ for part in version_string.split("."):
72
+ try:
73
+ parts.append(int(part))
74
+ except ValueError:
75
+ # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
76
+ # This is a simplification compared to 'packaging.parse_version', but sufficient
77
+ # for comparing strictly numeric semantic versions.
78
+ break
79
+ return tuple(parts)
80
+
81
+
82
+ def get_dependency_version(
83
+ dependency_name: str,
84
+ ) -> DependencyVersion:
85
+ """Get the parsed version of an installed package dependency.
86
+
87
+ This function checks for an installed package and returns its version
88
+ as a comparable tuple of integers object for safe comparison. It handles
89
+ both modern (Python 3.8+) and legacy (Python 3.7) environments.
90
+
91
+ Args:
92
+ dependency_name: The distribution name of the package (e.g., 'requests').
93
+
94
+ Returns:
95
+ A DependencyVersion namedtuple with `version` (a tuple of integers) and
96
+ `version_string` attributes, or `DependencyVersion(None,
97
+ UNKNOWN_VERSION_STRING)` if the package is not found or
98
+ another error occurs during version discovery.
99
+
100
+ """
101
+ try:
102
+ version_string: str = metadata.version(dependency_name)
103
+ parsed_version = parse_version_to_tuple(version_string)
104
+ return DependencyVersion(parsed_version, version_string)
105
+ except Exception:
106
+ # Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
107
+ # or errors during parse_version_to_tuple
108
+ return DependencyVersion(None, UNKNOWN_VERSION_STRING)
109
+
110
+
111
+ def warn_deprecation_for_versions_less_than(
112
+ consumer_import_package: str,
113
+ dependency_import_package: str,
114
+ minimum_fully_supported_version: str,
115
+ recommended_version: Optional[str] = None,
116
+ message_template: Optional[str] = None,
117
+ ):
118
+ """Issue any needed deprecation warnings for `dependency_import_package`.
119
+
120
+ If `dependency_import_package` is installed at a version less than
121
+ `minimum_fully_supported_version`, this issues a warning using either a
122
+ default `message_template` or one provided by the user. The
123
+ default `message_template` informs the user that they will not receive
124
+ future updates for `consumer_import_package` if
125
+ `dependency_import_package` is somehow pinned to a version lower
126
+ than `minimum_fully_supported_version`.
127
+
128
+ Args:
129
+ consumer_import_package: The import name of the package that
130
+ needs `dependency_import_package`.
131
+ dependency_import_package: The import name of the dependency to check.
132
+ minimum_fully_supported_version: The dependency_import_package version number
133
+ below which a deprecation warning will be logged.
134
+ recommended_version: If provided, the recommended next version, which
135
+ could be higher than `minimum_fully_supported_version`.
136
+ message_template: A custom default message template to replace
137
+ the default. This `message_template` is treated as an
138
+ f-string, where the following variables are defined:
139
+ `dependency_import_package`, `consumer_import_package` and
140
+ `dependency_distribution_package` and
141
+ `consumer_distribution_package` and `dependency_package`,
142
+ `consumer_package` , which contain the import packages, the
143
+ distribution packages, and pretty string with both the
144
+ distribution and import packages for the dependency and the
145
+ consumer, respectively; and `minimum_fully_supported_version`,
146
+ `version_used`, and `version_used_string`, which refer to supported
147
+ and currently-used versions of the dependency.
148
+
149
+ """
150
+ if (
151
+ not consumer_import_package
152
+ or not dependency_import_package
153
+ or not minimum_fully_supported_version
154
+ ): # pragma: NO COVER
155
+ return
156
+
157
+ dependency_version = get_dependency_version(dependency_import_package)
158
+ if not dependency_version.version:
159
+ return
160
+
161
+ if dependency_version.version < parse_version_to_tuple(
162
+ minimum_fully_supported_version
163
+ ):
164
+ (
165
+ dependency_package,
166
+ dependency_distribution_package,
167
+ ) = _get_distribution_and_import_packages(dependency_import_package)
168
+ (
169
+ consumer_package,
170
+ consumer_distribution_package,
171
+ ) = _get_distribution_and_import_packages(consumer_import_package)
172
+
173
+ recommendation = (
174
+ " (we recommend {recommended_version})" if recommended_version else ""
175
+ )
176
+ message_template = message_template or _flatten_message(
177
+ """
178
+ DEPRECATION: Package {consumer_package} depends on
179
+ {dependency_package}, currently installed at version
180
+ {version_used_string}. Future updates to
181
+ {consumer_package} will require {dependency_package} at
182
+ version {minimum_fully_supported_version} or
183
+ higher{recommendation}. Please ensure that either (a) your
184
+ Python environment doesn't pin the version of
185
+ {dependency_package}, so that updates to
186
+ {consumer_package} can require the higher version, or (b)
187
+ you manually update your Python environment to use at
188
+ least version {minimum_fully_supported_version} of
189
+ {dependency_package}.
190
+ """
191
+ )
192
+ warnings.warn(
193
+ message_template.format(
194
+ consumer_import_package=consumer_import_package,
195
+ dependency_import_package=dependency_import_package,
196
+ consumer_distribution_package=consumer_distribution_package,
197
+ dependency_distribution_package=dependency_distribution_package,
198
+ dependency_package=dependency_package,
199
+ consumer_package=consumer_package,
200
+ minimum_fully_supported_version=minimum_fully_supported_version,
201
+ recommendation=recommendation,
202
+ version_used=dependency_version.version,
203
+ version_used_string=dependency_version.version_string,
204
+ ),
205
+ FutureWarning,
206
+ )
207
+
208
+
209
+ def check_dependency_versions(
210
+ consumer_import_package: str, *package_dependency_warnings: DependencyConstraint
211
+ ):
212
+ """Bundle checks for all package dependencies.
213
+
214
+ This function can be called by all consumers of google.api_core,
215
+ to emit needed deprecation warnings for any of their
216
+ dependencies. The dependencies to check can be passed as arguments, or if
217
+ none are provided, it will default to the list in
218
+ `_PACKAGE_DEPENDENCY_WARNINGS`.
219
+
220
+ Args:
221
+ consumer_import_package: The distribution name of the calling package, whose
222
+ dependencies we're checking.
223
+ *package_dependency_warnings: A variable number of DependencyConstraint
224
+ objects, each specifying a dependency to check.
225
+ """
226
+ if not package_dependency_warnings:
227
+ package_dependency_warnings = tuple(_PACKAGE_DEPENDENCY_WARNINGS)
228
+ for package_info in package_dependency_warnings:
229
+ warn_deprecation_for_versions_less_than(
230
+ consumer_import_package,
231
+ package_info.package_name,
232
+ package_info.minimum_fully_supported_version,
233
+ recommended_version=package_info.recommended_version,
234
+ )
py311/lib/python3.11/site-packages/google/api_core/_python_version_support.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Code to check Python versions supported by Google Cloud Client Libraries."""
16
+
17
+ import datetime
18
+ import enum
19
+ import logging
20
+ import warnings
21
+ import sys
22
+ import textwrap
23
+ from typing import Any, List, NamedTuple, Optional, Dict, Tuple
24
+
25
+
26
+ _LOGGER = logging.getLogger(__name__)
27
+
28
+
29
+ class PythonVersionStatus(enum.Enum):
30
+ """Support status of a Python version in this client library artifact release.
31
+
32
+ "Support", in this context, means that this release of a client library
33
+ artifact is configured to run on the currently configured version of
34
+ Python.
35
+ """
36
+
37
+ PYTHON_VERSION_STATUS_UNSPECIFIED = "PYTHON_VERSION_STATUS_UNSPECIFIED"
38
+
39
+ PYTHON_VERSION_SUPPORTED = "PYTHON_VERSION_SUPPORTED"
40
+ """This Python version is fully supported, so the artifact running on this
41
+ version will have all features and bug fixes."""
42
+
43
+ PYTHON_VERSION_DEPRECATED = "PYTHON_VERSION_DEPRECATED"
44
+ """This Python version is still supported, but support will end within a
45
+ year. At that time, there will be no more releases for this artifact
46
+ running under this Python version."""
47
+
48
+ PYTHON_VERSION_EOL = "PYTHON_VERSION_EOL"
49
+ """This Python version has reached its end of life in the Python community
50
+ (see https://devguide.python.org/versions/), and this artifact will cease
51
+ supporting this Python version within the next few releases."""
52
+
53
+ PYTHON_VERSION_UNSUPPORTED = "PYTHON_VERSION_UNSUPPORTED"
54
+ """This release of the client library artifact may not be the latest, since
55
+ current releases no longer support this Python version."""
56
+
57
+
58
+ class VersionInfo(NamedTuple):
59
+ """Hold release and support date information for a Python version."""
60
+
61
+ version: str
62
+ python_beta: Optional[datetime.date]
63
+ python_start: datetime.date
64
+ python_eol: datetime.date
65
+ gapic_start: Optional[datetime.date] = None # unused
66
+ gapic_deprecation: Optional[datetime.date] = None
67
+ gapic_end: Optional[datetime.date] = None
68
+ dep_unpatchable_cve: Optional[datetime.date] = None # unused
69
+
70
+
71
+ PYTHON_VERSIONS: List[VersionInfo] = [
72
+ # Refer to https://devguide.python.org/versions/ and the PEPs linked therefrom.
73
+ VersionInfo(
74
+ version="3.7",
75
+ python_beta=None,
76
+ python_start=datetime.date(2018, 6, 27),
77
+ python_eol=datetime.date(2023, 6, 27),
78
+ ),
79
+ VersionInfo(
80
+ version="3.8",
81
+ python_beta=None,
82
+ python_start=datetime.date(2019, 10, 14),
83
+ python_eol=datetime.date(2024, 10, 7),
84
+ ),
85
+ VersionInfo(
86
+ version="3.9",
87
+ python_beta=datetime.date(2020, 5, 18),
88
+ python_start=datetime.date(2020, 10, 5),
89
+ python_eol=datetime.date(2025, 10, 5),
90
+ gapic_end=datetime.date(2025, 10, 5) + datetime.timedelta(days=90),
91
+ ),
92
+ VersionInfo(
93
+ version="3.10",
94
+ python_beta=datetime.date(2021, 5, 3),
95
+ python_start=datetime.date(2021, 10, 4),
96
+ python_eol=datetime.date(2026, 10, 4), # TODO: specify day when announced
97
+ ),
98
+ VersionInfo(
99
+ version="3.11",
100
+ python_beta=datetime.date(2022, 5, 8),
101
+ python_start=datetime.date(2022, 10, 24),
102
+ python_eol=datetime.date(2027, 10, 24), # TODO: specify day when announced
103
+ ),
104
+ VersionInfo(
105
+ version="3.12",
106
+ python_beta=datetime.date(2023, 5, 22),
107
+ python_start=datetime.date(2023, 10, 2),
108
+ python_eol=datetime.date(2028, 10, 2), # TODO: specify day when announced
109
+ ),
110
+ VersionInfo(
111
+ version="3.13",
112
+ python_beta=datetime.date(2024, 5, 8),
113
+ python_start=datetime.date(2024, 10, 7),
114
+ python_eol=datetime.date(2029, 10, 7), # TODO: specify day when announced
115
+ ),
116
+ VersionInfo(
117
+ version="3.14",
118
+ python_beta=datetime.date(2025, 5, 7),
119
+ python_start=datetime.date(2025, 10, 7),
120
+ python_eol=datetime.date(2030, 10, 7), # TODO: specify day when announced
121
+ ),
122
+ ]
123
+
124
+ PYTHON_VERSION_INFO: Dict[Tuple[int, int], VersionInfo] = {}
125
+ for info in PYTHON_VERSIONS:
126
+ major, minor = map(int, info.version.split("."))
127
+ PYTHON_VERSION_INFO[(major, minor)] = info
128
+
129
+
130
+ LOWEST_TRACKED_VERSION = min(PYTHON_VERSION_INFO.keys())
131
+ _FAKE_PAST_DATE = datetime.date.min + datetime.timedelta(days=900)
132
+ _FAKE_PAST_VERSION = VersionInfo(
133
+ version="0.0",
134
+ python_beta=_FAKE_PAST_DATE,
135
+ python_start=_FAKE_PAST_DATE,
136
+ python_eol=_FAKE_PAST_DATE,
137
+ )
138
+ _FAKE_FUTURE_DATE = datetime.date.max - datetime.timedelta(days=900)
139
+ _FAKE_FUTURE_VERSION = VersionInfo(
140
+ version="999.0",
141
+ python_beta=_FAKE_FUTURE_DATE,
142
+ python_start=_FAKE_FUTURE_DATE,
143
+ python_eol=_FAKE_FUTURE_DATE,
144
+ )
145
+ DEPRECATION_WARNING_PERIOD = datetime.timedelta(days=365)
146
+ EOL_GRACE_PERIOD = datetime.timedelta(weeks=1)
147
+
148
+
149
+ def _flatten_message(text: str) -> str:
150
+ """Dedent a multi-line string and flatten it into a single line."""
151
+ return " ".join(textwrap.dedent(text).strip().split())
152
+
153
+
154
+ # TODO(https://github.com/googleapis/python-api-core/issues/835):
155
+ # Remove once we no longer support Python 3.9.
156
+ # `importlib.metadata.packages_distributions()` is only supported in Python 3.10 and newer
157
+ # https://docs.python.org/3/library/importlib.metadata.html#importlib.metadata.packages_distributions
158
+ if sys.version_info < (3, 10):
159
+
160
+ def _get_pypi_package_name(module_name): # pragma: NO COVER
161
+ """Determine the PyPI package name for a given module name."""
162
+ return None
163
+
164
+ else:
165
+ from importlib import metadata
166
+
167
+ def _get_pypi_package_name(module_name):
168
+ """Determine the PyPI package name for a given module name."""
169
+ try:
170
+ # Get the mapping of modules to distributions
171
+ module_to_distributions = metadata.packages_distributions()
172
+
173
+ # Check if the module is found in the mapping
174
+ if module_name in module_to_distributions: # pragma: NO COVER
175
+ # The value is a list of distribution names, take the first one
176
+ return module_to_distributions[module_name][0]
177
+ except Exception as e: # pragma: NO COVER
178
+ _LOGGER.info(
179
+ "An error occurred while determining PyPI package name for %s: %s",
180
+ module_name,
181
+ e,
182
+ )
183
+
184
+ return None
185
+
186
+
187
+ def _get_distribution_and_import_packages(import_package: str) -> Tuple[str, Any]:
188
+ """Return a pretty string with distribution & import package names."""
189
+ distribution_package = _get_pypi_package_name(import_package)
190
+ dependency_distribution_and_import_packages = (
191
+ f"package {distribution_package} ({import_package})"
192
+ if distribution_package
193
+ else import_package
194
+ )
195
+ return dependency_distribution_and_import_packages, distribution_package
196
+
197
+
198
+ def check_python_version(
199
+ package: str = "this package", today: Optional[datetime.date] = None
200
+ ) -> PythonVersionStatus:
201
+ """Check the running Python version and issue a support warning if needed.
202
+
203
+ Args:
204
+ today: The date to check against. Defaults to the current date.
205
+
206
+ Returns:
207
+ The support status of the current Python version.
208
+ """
209
+ today = today or datetime.date.today()
210
+ package_label, _ = _get_distribution_and_import_packages(package)
211
+
212
+ python_version = sys.version_info
213
+ version_tuple = (python_version.major, python_version.minor)
214
+ py_version_str = sys.version.split()[0]
215
+
216
+ version_info = PYTHON_VERSION_INFO.get(version_tuple)
217
+
218
+ if not version_info:
219
+ if version_tuple < LOWEST_TRACKED_VERSION:
220
+ version_info = _FAKE_PAST_VERSION
221
+ else:
222
+ version_info = _FAKE_FUTURE_VERSION
223
+
224
+ gapic_deprecation = version_info.gapic_deprecation or (
225
+ version_info.python_eol - DEPRECATION_WARNING_PERIOD
226
+ )
227
+ gapic_end = version_info.gapic_end or (version_info.python_eol + EOL_GRACE_PERIOD)
228
+
229
+ def min_python(date: datetime.date) -> str:
230
+ """Find the minimum supported Python version for a given date."""
231
+ for version, info in sorted(PYTHON_VERSION_INFO.items()):
232
+ if info.python_start <= date < info.python_eol:
233
+ return f"{version[0]}.{version[1]}"
234
+ return "at a currently supported version [https://devguide.python.org/versions]"
235
+
236
+ if gapic_end < today:
237
+ message = _flatten_message(
238
+ f"""
239
+ You are using a non-supported Python version ({py_version_str}).
240
+ Google will not post any further updates to {package_label}
241
+ supporting this Python version. Please upgrade to the latest Python
242
+ version, or at least Python {min_python(today)}, and then update
243
+ {package_label}.
244
+ """
245
+ )
246
+ warnings.warn(message, FutureWarning)
247
+ return PythonVersionStatus.PYTHON_VERSION_UNSUPPORTED
248
+
249
+ eol_date = version_info.python_eol + EOL_GRACE_PERIOD
250
+ if eol_date <= today <= gapic_end:
251
+ message = _flatten_message(
252
+ f"""
253
+ You are using a Python version ({py_version_str})
254
+ past its end of life. Google will update {package_label}
255
+ with critical bug fixes on a best-effort basis, but not
256
+ with any other fixes or features. Please upgrade
257
+ to the latest Python version, or at least Python
258
+ {min_python(today)}, and then update {package_label}.
259
+ """
260
+ )
261
+ warnings.warn(message, FutureWarning)
262
+ return PythonVersionStatus.PYTHON_VERSION_EOL
263
+
264
+ if gapic_deprecation <= today <= gapic_end:
265
+ message = _flatten_message(
266
+ f"""
267
+ You are using a Python version ({py_version_str}) which Google will
268
+ stop supporting in new releases of {package_label} once it reaches
269
+ its end of life ({version_info.python_eol}). Please upgrade to the
270
+ latest Python version, or at least Python
271
+ {min_python(version_info.python_eol)}, to continue receiving updates
272
+ for {package_label} past that date.
273
+ """
274
+ )
275
+ warnings.warn(message, FutureWarning)
276
+ return PythonVersionStatus.PYTHON_VERSION_DEPRECATED
277
+
278
+ return PythonVersionStatus.PYTHON_VERSION_SUPPORTED
py311/lib/python3.11/site-packages/google/api_core/_rest_streaming_base.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for server-side streaming in REST."""
16
+
17
+ from collections import deque
18
+ import string
19
+ from typing import Deque, Union
20
+ import types
21
+
22
+ import proto
23
+ import google.protobuf.message
24
+ from google.protobuf.json_format import Parse
25
+
26
+
27
+ class BaseResponseIterator:
28
+ """Base Iterator over REST API responses. This class should not be used directly.
29
+
30
+ Args:
31
+ response_message_cls (Union[proto.Message, google.protobuf.message.Message]): A response
32
+ class expected to be returned from an API.
33
+
34
+ Raises:
35
+ ValueError: If `response_message_cls` is not a subclass of `proto.Message` or `google.protobuf.message.Message`.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ response_message_cls: Union[proto.Message, google.protobuf.message.Message],
41
+ ):
42
+ self._response_message_cls = response_message_cls
43
+ # Contains a list of JSON responses ready to be sent to user.
44
+ self._ready_objs: Deque[str] = deque()
45
+ # Current JSON response being built.
46
+ self._obj = ""
47
+ # Keeps track of the nesting level within a JSON object.
48
+ self._level = 0
49
+ # Keeps track whether HTTP response is currently sending values
50
+ # inside of a string value.
51
+ self._in_string = False
52
+ # Whether an escape symbol "\" was encountered.
53
+ self._escape_next = False
54
+
55
+ self._grab = types.MethodType(self._create_grab(), self)
56
+
57
+ def _process_chunk(self, chunk: str):
58
+ if self._level == 0:
59
+ if chunk[0] != "[":
60
+ raise ValueError(
61
+ "Can only parse array of JSON objects, instead got %s" % chunk
62
+ )
63
+ for char in chunk:
64
+ if char == "{":
65
+ if self._level == 1:
66
+ # Level 1 corresponds to the outermost JSON object
67
+ # (i.e. the one we care about).
68
+ self._obj = ""
69
+ if not self._in_string:
70
+ self._level += 1
71
+ self._obj += char
72
+ elif char == "}":
73
+ self._obj += char
74
+ if not self._in_string:
75
+ self._level -= 1
76
+ if not self._in_string and self._level == 1:
77
+ self._ready_objs.append(self._obj)
78
+ elif char == '"':
79
+ # Helps to deal with an escaped quotes inside of a string.
80
+ if not self._escape_next:
81
+ self._in_string = not self._in_string
82
+ self._obj += char
83
+ elif char in string.whitespace:
84
+ if self._in_string:
85
+ self._obj += char
86
+ elif char == "[":
87
+ if self._level == 0:
88
+ self._level += 1
89
+ else:
90
+ self._obj += char
91
+ elif char == "]":
92
+ if self._level == 1:
93
+ self._level -= 1
94
+ else:
95
+ self._obj += char
96
+ else:
97
+ self._obj += char
98
+ self._escape_next = not self._escape_next if char == "\\" else False
99
+
100
+ def _create_grab(self):
101
+ if issubclass(self._response_message_cls, proto.Message):
102
+
103
+ def grab(this):
104
+ return this._response_message_cls.from_json(
105
+ this._ready_objs.popleft(), ignore_unknown_fields=True
106
+ )
107
+
108
+ return grab
109
+ elif issubclass(self._response_message_cls, google.protobuf.message.Message):
110
+
111
+ def grab(this):
112
+ return Parse(this._ready_objs.popleft(), this._response_message_cls())
113
+
114
+ return grab
115
+ else:
116
+ raise ValueError(
117
+ "Response message class must be a subclass of proto.Message or google.protobuf.message.Message."
118
+ )
py311/lib/python3.11/site-packages/google/api_core/bidi.py ADDED
@@ -0,0 +1,735 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017, Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for synchronous bidirectional streaming RPCs."""
16
+
17
+ import collections
18
+ import datetime
19
+ import logging
20
+ import queue as queue_module
21
+ import threading
22
+ import time
23
+
24
+ from google.api_core import exceptions
25
+ from google.api_core.bidi_base import BidiRpcBase
26
+
27
+ _LOGGER = logging.getLogger(__name__)
28
+ _BIDIRECTIONAL_CONSUMER_NAME = "Thread-ConsumeBidirectionalStream"
29
+
30
+
31
+ class _RequestQueueGenerator(object):
32
+ """A helper for sending requests to a gRPC stream from a Queue.
33
+
34
+ This generator takes requests off a given queue and yields them to gRPC.
35
+
36
+ This helper is useful when you have an indeterminate, indefinite, or
37
+ otherwise open-ended set of requests to send through a request-streaming
38
+ (or bidirectional) RPC.
39
+
40
+
41
+ Example::
42
+
43
+ requests = request_queue_generator(q)
44
+ call = stub.StreamingRequest(iter(requests))
45
+ requests.call = call
46
+
47
+ for response in call:
48
+ print(response)
49
+ q.put(...)
50
+
51
+
52
+ Args:
53
+ queue (queue_module.Queue): The request queue.
54
+ period (float): The number of seconds to wait for items from the queue
55
+ before checking if the RPC is cancelled. In practice, this
56
+ determines the maximum amount of time the request consumption
57
+ thread will live after the RPC is cancelled.
58
+ initial_request (Union[protobuf.Message,
59
+ Callable[None, protobuf.Message]]): The initial request to
60
+ yield. This is done independently of the request queue to allow fo
61
+ easily restarting streams that require some initial configuration
62
+ request.
63
+ """
64
+
65
+ def __init__(self, queue, period=1, initial_request=None):
66
+ self._queue = queue
67
+ self._period = period
68
+ self._initial_request = initial_request
69
+ self.call = None
70
+
71
+ def _is_active(self):
72
+ # Note: there is a possibility that this starts *before* the call
73
+ # property is set. So we have to check if self.call is set before
74
+ # seeing if it's active. We need to return True if self.call is None.
75
+ # See https://github.com/googleapis/python-api-core/issues/560.
76
+ return self.call is None or self.call.is_active()
77
+
78
+ def __iter__(self):
79
+ # The reason this is necessary is because gRPC takes an iterator as the
80
+ # request for request-streaming RPCs. gRPC consumes this iterator in
81
+ # another thread to allow it to block while generating requests for
82
+ # the stream. However, if the generator blocks indefinitely gRPC will
83
+ # not be able to clean up the thread as it'll be blocked on
84
+ # `next(iterator)` and not be able to check the channel status to stop
85
+ # iterating. This helper mitigates that by waiting on the queue with
86
+ # a timeout and checking the RPC state before yielding.
87
+ #
88
+ # Finally, it allows for retrying without swapping queues because if
89
+ # it does pull an item off the queue when the RPC is inactive, it'll
90
+ # immediately put it back and then exit. This is necessary because
91
+ # yielding the item in this case will cause gRPC to discard it. In
92
+ # practice, this means that the order of messages is not guaranteed.
93
+ # If such a thing is necessary it would be easy to use a priority
94
+ # queue.
95
+ #
96
+ # Note that it is possible to accomplish this behavior without
97
+ # "spinning" (using a queue timeout). One possible way would be to use
98
+ # more threads to multiplex the grpc end event with the queue, another
99
+ # possible way is to use selectors and a custom event/queue object.
100
+ # Both of these approaches are significant from an engineering
101
+ # perspective for small benefit - the CPU consumed by spinning is
102
+ # pretty minuscule.
103
+
104
+ if self._initial_request is not None:
105
+ if callable(self._initial_request):
106
+ yield self._initial_request()
107
+ else:
108
+ yield self._initial_request
109
+
110
+ while True:
111
+ try:
112
+ item = self._queue.get(timeout=self._period)
113
+ except queue_module.Empty:
114
+ if not self._is_active():
115
+ _LOGGER.debug(
116
+ "Empty queue and inactive call, exiting request " "generator."
117
+ )
118
+ return
119
+ else:
120
+ # call is still active, keep waiting for queue items.
121
+ continue
122
+
123
+ # The consumer explicitly sent "None", indicating that the request
124
+ # should end.
125
+ if item is None:
126
+ _LOGGER.debug("Cleanly exiting request generator.")
127
+ return
128
+
129
+ if not self._is_active():
130
+ # We have an item, but the call is closed. We should put the
131
+ # item back on the queue so that the next call can consume it.
132
+ self._queue.put(item)
133
+ _LOGGER.debug(
134
+ "Inactive call, replacing item on queue and exiting "
135
+ "request generator."
136
+ )
137
+ return
138
+
139
+ yield item
140
+
141
+
142
+ class _Throttle(object):
143
+ """A context manager limiting the total entries in a sliding time window.
144
+
145
+ If more than ``access_limit`` attempts are made to enter the context manager
146
+ instance in the last ``time window`` interval, the exceeding requests block
147
+ until enough time elapses.
148
+
149
+ The context manager instances are thread-safe and can be shared between
150
+ multiple threads. If multiple requests are blocked and waiting to enter,
151
+ the exact order in which they are allowed to proceed is not determined.
152
+
153
+ Example::
154
+
155
+ max_three_per_second = _Throttle(
156
+ access_limit=3, time_window=datetime.timedelta(seconds=1)
157
+ )
158
+
159
+ for i in range(5):
160
+ with max_three_per_second as time_waited:
161
+ print("{}: Waited {} seconds to enter".format(i, time_waited))
162
+
163
+ Args:
164
+ access_limit (int): the maximum number of entries allowed in the time window
165
+ time_window (datetime.timedelta): the width of the sliding time window
166
+ """
167
+
168
+ def __init__(self, access_limit, time_window):
169
+ if access_limit < 1:
170
+ raise ValueError("access_limit argument must be positive")
171
+
172
+ if time_window <= datetime.timedelta(0):
173
+ raise ValueError("time_window argument must be a positive timedelta")
174
+
175
+ self._time_window = time_window
176
+ self._access_limit = access_limit
177
+ self._past_entries = collections.deque(
178
+ maxlen=access_limit
179
+ ) # least recent first
180
+ self._entry_lock = threading.Lock()
181
+
182
+ def __enter__(self):
183
+ with self._entry_lock:
184
+ cutoff_time = datetime.datetime.now() - self._time_window
185
+
186
+ # drop the entries that are too old, as they are no longer relevant
187
+ while self._past_entries and self._past_entries[0] < cutoff_time:
188
+ self._past_entries.popleft()
189
+
190
+ if len(self._past_entries) < self._access_limit:
191
+ self._past_entries.append(datetime.datetime.now())
192
+ return 0.0 # no waiting was needed
193
+
194
+ to_wait = (self._past_entries[0] - cutoff_time).total_seconds()
195
+ time.sleep(to_wait)
196
+
197
+ self._past_entries.append(datetime.datetime.now())
198
+ return to_wait
199
+
200
+ def __exit__(self, *_):
201
+ pass
202
+
203
+ def __repr__(self):
204
+ return "{}(access_limit={}, time_window={})".format(
205
+ self.__class__.__name__, self._access_limit, repr(self._time_window)
206
+ )
207
+
208
+
209
+ class BidiRpc(BidiRpcBase):
210
+ """A helper for consuming a bi-directional streaming RPC.
211
+
212
+ This maps gRPC's built-in interface which uses a request iterator and a
213
+ response iterator into a socket-like :func:`send` and :func:`recv`. This
214
+ is a more useful pattern for long-running or asymmetric streams (streams
215
+ where there is not a direct correlation between the requests and
216
+ responses).
217
+
218
+ Example::
219
+
220
+ initial_request = example_pb2.StreamingRpcRequest(
221
+ setting='example')
222
+ rpc = BidiRpc(
223
+ stub.StreamingRpc,
224
+ initial_request=initial_request,
225
+ metadata=[('name', 'value')]
226
+ )
227
+
228
+ rpc.open()
229
+
230
+ while rpc.is_active():
231
+ print(rpc.recv())
232
+ rpc.send(example_pb2.StreamingRpcRequest(
233
+ data='example'))
234
+
235
+ rpc.close()
236
+
237
+ This does *not* retry the stream on errors. See :class:`ResumableBidiRpc`.
238
+
239
+ Args:
240
+ start_rpc (grpc.StreamStreamMultiCallable): The gRPC method used to
241
+ start the RPC.
242
+ initial_request (Union[protobuf.Message,
243
+ Callable[None, protobuf.Message]]): The initial request to
244
+ yield. This is useful if an initial request is needed to start the
245
+ stream.
246
+ metadata (Sequence[Tuple(str, str)]): RPC metadata to include in
247
+ the request.
248
+ """
249
+
250
+ def _create_queue(self):
251
+ """Create a queue for requests."""
252
+ return queue_module.Queue()
253
+
254
+ def open(self):
255
+ """Opens the stream."""
256
+ if self.is_active:
257
+ raise ValueError("Cannot open an already open stream.")
258
+
259
+ request_generator = _RequestQueueGenerator(
260
+ self._request_queue, initial_request=self._initial_request
261
+ )
262
+ try:
263
+ call = self._start_rpc(iter(request_generator), metadata=self._rpc_metadata)
264
+ except exceptions.GoogleAPICallError as exc:
265
+ # The original `grpc.RpcError` (which is usually also a `grpc.Call`) is
266
+ # available from the ``response`` property on the mapped exception.
267
+ self._on_call_done(exc.response)
268
+ raise
269
+
270
+ request_generator.call = call
271
+
272
+ # TODO: api_core should expose the future interface for wrapped
273
+ # callables as well.
274
+ if hasattr(call, "_wrapped"): # pragma: NO COVER
275
+ call._wrapped.add_done_callback(self._on_call_done)
276
+ else:
277
+ call.add_done_callback(self._on_call_done)
278
+
279
+ self._request_generator = request_generator
280
+ self.call = call
281
+
282
+ def close(self):
283
+ """Closes the stream."""
284
+ if self.call is not None:
285
+ self.call.cancel()
286
+
287
+ # Put None in request queue to signal termination.
288
+ self._request_queue.put(None)
289
+ self._request_generator = None
290
+ self._initial_request = None
291
+ self._callbacks = []
292
+ # Don't set self.call to None. Keep it around so that send/recv can
293
+ # raise the error.
294
+
295
+ def send(self, request):
296
+ """Queue a message to be sent on the stream.
297
+
298
+ Send is non-blocking.
299
+
300
+ If the underlying RPC has been closed, this will raise.
301
+
302
+ Args:
303
+ request (protobuf.Message): The request to send.
304
+ """
305
+ if self.call is None:
306
+ raise ValueError("Cannot send on an RPC stream that has never been opened.")
307
+
308
+ # Don't use self.is_active(), as ResumableBidiRpc will overload it
309
+ # to mean something semantically different.
310
+ if self.call.is_active():
311
+ self._request_queue.put(request)
312
+ else:
313
+ # calling next should cause the call to raise.
314
+ next(self.call)
315
+
316
+ def recv(self):
317
+ """Wait for a message to be returned from the stream.
318
+
319
+ Recv is blocking.
320
+
321
+ If the underlying RPC has been closed, this will raise.
322
+
323
+ Returns:
324
+ protobuf.Message: The received message.
325
+ """
326
+ if self.call is None:
327
+ raise ValueError("Cannot recv on an RPC stream that has never been opened.")
328
+
329
+ return next(self.call)
330
+
331
+ @property
332
+ def is_active(self):
333
+ """True if this stream is currently open and active."""
334
+ return self.call is not None and self.call.is_active()
335
+
336
+
337
+ def _never_terminate(future_or_error):
338
+ """By default, no errors cause BiDi termination."""
339
+ return False
340
+
341
+
342
+ class ResumableBidiRpc(BidiRpc):
343
+ """A :class:`BidiRpc` that can automatically resume the stream on errors.
344
+
345
+ It uses the ``should_recover`` arg to determine if it should re-establish
346
+ the stream on error.
347
+
348
+ Example::
349
+
350
+ def should_recover(exc):
351
+ return (
352
+ isinstance(exc, grpc.RpcError) and
353
+ exc.code() == grpc.StatusCode.UNAVAILABLE)
354
+
355
+ initial_request = example_pb2.StreamingRpcRequest(
356
+ setting='example')
357
+
358
+ metadata = [('header_name', 'value')]
359
+
360
+ rpc = ResumableBidiRpc(
361
+ stub.StreamingRpc,
362
+ should_recover=should_recover,
363
+ initial_request=initial_request,
364
+ metadata=metadata
365
+ )
366
+
367
+ rpc.open()
368
+
369
+ while rpc.is_active():
370
+ print(rpc.recv())
371
+ rpc.send(example_pb2.StreamingRpcRequest(
372
+ data='example'))
373
+
374
+ Args:
375
+ start_rpc (grpc.StreamStreamMultiCallable): The gRPC method used to
376
+ start the RPC.
377
+ initial_request (Union[protobuf.Message,
378
+ Callable[None, protobuf.Message]]): The initial request to
379
+ yield. This is useful if an initial request is needed to start the
380
+ stream.
381
+ should_recover (Callable[[Exception], bool]): A function that returns
382
+ True if the stream should be recovered. This will be called
383
+ whenever an error is encountered on the stream.
384
+ should_terminate (Callable[[Exception], bool]): A function that returns
385
+ True if the stream should be terminated. This will be called
386
+ whenever an error is encountered on the stream.
387
+ metadata Sequence[Tuple(str, str)]: RPC metadata to include in
388
+ the request.
389
+ throttle_reopen (bool): If ``True``, throttling will be applied to
390
+ stream reopen calls. Defaults to ``False``.
391
+ """
392
+
393
+ def __init__(
394
+ self,
395
+ start_rpc,
396
+ should_recover,
397
+ should_terminate=_never_terminate,
398
+ initial_request=None,
399
+ metadata=None,
400
+ throttle_reopen=False,
401
+ ):
402
+ super(ResumableBidiRpc, self).__init__(start_rpc, initial_request, metadata)
403
+ self._should_recover = should_recover
404
+ self._should_terminate = should_terminate
405
+ self._operational_lock = threading.RLock()
406
+ self._finalized = False
407
+ self._finalize_lock = threading.Lock()
408
+
409
+ if throttle_reopen:
410
+ self._reopen_throttle = _Throttle(
411
+ access_limit=5, time_window=datetime.timedelta(seconds=10)
412
+ )
413
+ else:
414
+ self._reopen_throttle = None
415
+
416
+ def _finalize(self, result):
417
+ with self._finalize_lock:
418
+ if self._finalized:
419
+ return
420
+
421
+ for callback in self._callbacks:
422
+ callback(result)
423
+
424
+ self._finalized = True
425
+
426
+ def _on_call_done(self, future):
427
+ # Unlike the base class, we only execute the callbacks on a terminal
428
+ # error, not for errors that we can recover from. Note that grpc's
429
+ # "future" here is also a grpc.RpcError.
430
+ with self._operational_lock:
431
+ if self._should_terminate(future):
432
+ self._finalize(future)
433
+ elif not self._should_recover(future):
434
+ self._finalize(future)
435
+ else:
436
+ _LOGGER.debug("Re-opening stream from gRPC callback.")
437
+ self._reopen()
438
+
439
+ def _reopen(self):
440
+ with self._operational_lock:
441
+ # Another thread already managed to re-open this stream.
442
+ if self.call is not None and self.call.is_active():
443
+ _LOGGER.debug("Stream was already re-established.")
444
+ return
445
+
446
+ self.call = None
447
+ # Request generator should exit cleanly since the RPC its bound to
448
+ # has exited.
449
+ self._request_generator = None
450
+
451
+ # Note: we do not currently do any sort of backoff here. The
452
+ # assumption is that re-establishing the stream under normal
453
+ # circumstances will happen in intervals greater than 60s.
454
+ # However, it is possible in a degenerative case that the server
455
+ # closes the stream rapidly which would lead to thrashing here,
456
+ # but hopefully in those cases the server would return a non-
457
+ # retryable error.
458
+
459
+ try:
460
+ if self._reopen_throttle:
461
+ with self._reopen_throttle:
462
+ self.open()
463
+ else:
464
+ self.open()
465
+ # If re-opening or re-calling the method fails for any reason,
466
+ # consider it a terminal error and finalize the stream.
467
+ except Exception as exc:
468
+ _LOGGER.debug("Failed to re-open stream due to %s", exc)
469
+ self._finalize(exc)
470
+ raise
471
+
472
+ _LOGGER.info("Re-established stream")
473
+
474
+ def _recoverable(self, method, *args, **kwargs):
475
+ """Wraps a method to recover the stream and retry on error.
476
+
477
+ If a retryable error occurs while making the call, then the stream will
478
+ be re-opened and the method will be retried. This happens indefinitely
479
+ so long as the error is a retryable one. If an error occurs while
480
+ re-opening the stream, then this method will raise immediately and
481
+ trigger finalization of this object.
482
+
483
+ Args:
484
+ method (Callable[..., Any]): The method to call.
485
+ args: The args to pass to the method.
486
+ kwargs: The kwargs to pass to the method.
487
+ """
488
+ while True:
489
+ try:
490
+ return method(*args, **kwargs)
491
+
492
+ except Exception as exc:
493
+ with self._operational_lock:
494
+ _LOGGER.debug("Call to retryable %r caused %s.", method, exc)
495
+
496
+ if self._should_terminate(exc):
497
+ self.close()
498
+ _LOGGER.debug("Terminating %r due to %s.", method, exc)
499
+ self._finalize(exc)
500
+ break
501
+
502
+ if not self._should_recover(exc):
503
+ self.close()
504
+ _LOGGER.debug("Not retrying %r due to %s.", method, exc)
505
+ self._finalize(exc)
506
+ raise exc
507
+
508
+ _LOGGER.debug("Re-opening stream from retryable %r.", method)
509
+ self._reopen()
510
+
511
+ def _send(self, request):
512
+ # Grab a reference to the RPC call. Because another thread (notably
513
+ # the gRPC error thread) can modify self.call (by invoking reopen),
514
+ # we should ensure our reference can not change underneath us.
515
+ # If self.call is modified (such as replaced with a new RPC call) then
516
+ # this will use the "old" RPC, which should result in the same
517
+ # exception passed into gRPC's error handler being raised here, which
518
+ # will be handled by the usual error handling in retryable.
519
+ with self._operational_lock:
520
+ call = self.call
521
+
522
+ if call is None:
523
+ raise ValueError("Cannot send on an RPC that has never been opened.")
524
+
525
+ # Don't use self.is_active(), as ResumableBidiRpc will overload it
526
+ # to mean something semantically different.
527
+ if call.is_active():
528
+ self._request_queue.put(request)
529
+ pass
530
+ else:
531
+ # calling next should cause the call to raise.
532
+ next(call)
533
+
534
+ def send(self, request):
535
+ return self._recoverable(self._send, request)
536
+
537
+ def _recv(self):
538
+ with self._operational_lock:
539
+ call = self.call
540
+
541
+ if call is None:
542
+ raise ValueError("Cannot recv on an RPC that has never been opened.")
543
+
544
+ return next(call)
545
+
546
+ def recv(self):
547
+ return self._recoverable(self._recv)
548
+
549
+ def close(self):
550
+ self._finalize(None)
551
+ super(ResumableBidiRpc, self).close()
552
+
553
+ @property
554
+ def is_active(self):
555
+ """bool: True if this stream is currently open and active."""
556
+ # Use the operational lock. It's entirely possible for something
557
+ # to check the active state *while* the RPC is being retried.
558
+ # Also, use finalized to track the actual terminal state here.
559
+ # This is because if the stream is re-established by the gRPC thread
560
+ # it's technically possible to check this between when gRPC marks the
561
+ # RPC as inactive and when gRPC executes our callback that re-opens
562
+ # the stream.
563
+ with self._operational_lock:
564
+ return self.call is not None and not self._finalized
565
+
566
+
567
+ class BackgroundConsumer(object):
568
+ """A bi-directional stream consumer that runs in a separate thread.
569
+
570
+ This maps the consumption of a stream into a callback-based model. It also
571
+ provides :func:`pause` and :func:`resume` to allow for flow-control.
572
+
573
+ Example::
574
+
575
+ def should_recover(exc):
576
+ return (
577
+ isinstance(exc, grpc.RpcError) and
578
+ exc.code() == grpc.StatusCode.UNAVAILABLE)
579
+
580
+ initial_request = example_pb2.StreamingRpcRequest(
581
+ setting='example')
582
+
583
+ rpc = ResumeableBidiRpc(
584
+ stub.StreamingRpc,
585
+ initial_request=initial_request,
586
+ should_recover=should_recover)
587
+
588
+ def on_response(response):
589
+ print(response)
590
+
591
+ consumer = BackgroundConsumer(rpc, on_response)
592
+ consumer.start()
593
+
594
+ Note that error handling *must* be done by using the provided
595
+ ``bidi_rpc``'s ``add_done_callback``. This helper will automatically exit
596
+ whenever the RPC itself exits and will not provide any error details.
597
+
598
+ Args:
599
+ bidi_rpc (BidiRpc): The RPC to consume. Should not have been
600
+ ``open()``ed yet.
601
+ on_response (Callable[[protobuf.Message], None]): The callback to
602
+ be called for every response on the stream.
603
+ on_fatal_exception (Callable[[Exception], None]): The callback to
604
+ be called on fatal errors during consumption. Default None.
605
+ """
606
+
607
+ def __init__(self, bidi_rpc, on_response, on_fatal_exception=None):
608
+ self._bidi_rpc = bidi_rpc
609
+ self._on_response = on_response
610
+ self._paused = False
611
+ self._on_fatal_exception = on_fatal_exception
612
+ self._wake = threading.Condition()
613
+ self._thread = None
614
+ self._operational_lock = threading.Lock()
615
+
616
+ def _on_call_done(self, future):
617
+ # Resume the thread if it's paused, this prevents blocking forever
618
+ # when the RPC has terminated.
619
+ self.resume()
620
+
621
+ def _thread_main(self, ready):
622
+ try:
623
+ ready.set()
624
+ self._bidi_rpc.add_done_callback(self._on_call_done)
625
+ self._bidi_rpc.open()
626
+
627
+ while self._bidi_rpc.is_active:
628
+ # Do not allow the paused status to change at all during this
629
+ # section. There is a condition where we could be resumed
630
+ # between checking if we are paused and calling wake.wait(),
631
+ # which means that we will miss the notification to wake up
632
+ # (oops!) and wait for a notification that will never come.
633
+ # Keeping the lock throughout avoids that.
634
+ # In the future, we could use `Condition.wait_for` if we drop
635
+ # Python 2.7.
636
+ # See: https://github.com/googleapis/python-api-core/issues/211
637
+ with self._wake:
638
+ while self._paused:
639
+ _LOGGER.debug("paused, waiting for waking.")
640
+ self._wake.wait()
641
+ _LOGGER.debug("woken.")
642
+
643
+ _LOGGER.debug("waiting for recv.")
644
+ response = self._bidi_rpc.recv()
645
+ _LOGGER.debug("recved response.")
646
+ if self._on_response is not None:
647
+ self._on_response(response)
648
+
649
+ except exceptions.GoogleAPICallError as exc:
650
+ _LOGGER.debug(
651
+ "%s caught error %s and will exit. Generally this is due to "
652
+ "the RPC itself being cancelled and the error will be "
653
+ "surfaced to the calling code.",
654
+ _BIDIRECTIONAL_CONSUMER_NAME,
655
+ exc,
656
+ exc_info=True,
657
+ )
658
+ if self._on_fatal_exception is not None:
659
+ self._on_fatal_exception(exc)
660
+
661
+ except Exception as exc:
662
+ _LOGGER.exception(
663
+ "%s caught unexpected exception %s and will exit.",
664
+ _BIDIRECTIONAL_CONSUMER_NAME,
665
+ exc,
666
+ )
667
+ if self._on_fatal_exception is not None:
668
+ self._on_fatal_exception(exc)
669
+
670
+ _LOGGER.info("%s exiting", _BIDIRECTIONAL_CONSUMER_NAME)
671
+
672
+ def start(self):
673
+ """Start the background thread and begin consuming the thread."""
674
+ with self._operational_lock:
675
+ ready = threading.Event()
676
+ thread = threading.Thread(
677
+ name=_BIDIRECTIONAL_CONSUMER_NAME,
678
+ target=self._thread_main,
679
+ args=(ready,),
680
+ daemon=True,
681
+ )
682
+ thread.start()
683
+ # Other parts of the code rely on `thread.is_alive` which
684
+ # isn't sufficient to know if a thread is active, just that it may
685
+ # soon be active. This can cause races. Further protect
686
+ # against races by using a ready event and wait on it to be set.
687
+ ready.wait()
688
+ self._thread = thread
689
+ _LOGGER.debug("Started helper thread %s", thread.name)
690
+
691
+ def stop(self):
692
+ """Stop consuming the stream and shutdown the background thread.
693
+
694
+ NOTE: Cannot be called within `_thread_main`, since it is not
695
+ possible to join a thread to itself.
696
+ """
697
+ with self._operational_lock:
698
+ self._bidi_rpc.close()
699
+
700
+ if self._thread is not None:
701
+ # Resume the thread to wake it up in case it is sleeping.
702
+ self.resume()
703
+ # The daemonized thread may itself block, so don't wait
704
+ # for it longer than a second.
705
+ self._thread.join(1.0)
706
+ if self._thread.is_alive(): # pragma: NO COVER
707
+ _LOGGER.warning("Background thread did not exit.")
708
+
709
+ self._thread = None
710
+ self._on_response = None
711
+ self._on_fatal_exception = None
712
+
713
+ @property
714
+ def is_active(self):
715
+ """bool: True if the background thread is active."""
716
+ return self._thread is not None and self._thread.is_alive()
717
+
718
+ def pause(self):
719
+ """Pauses the response stream.
720
+
721
+ This does *not* pause the request stream.
722
+ """
723
+ with self._wake:
724
+ self._paused = True
725
+
726
+ def resume(self):
727
+ """Resumes the response stream."""
728
+ with self._wake:
729
+ self._paused = False
730
+ self._wake.notify_all()
731
+
732
+ @property
733
+ def is_paused(self):
734
+ """bool: True if the response stream is paused."""
735
+ return self._paused
py311/lib/python3.11/site-packages/google/api_core/bidi_async.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025, Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Asynchronous bi-directional streaming RPC helpers."""
16
+
17
+ import asyncio
18
+ import logging
19
+ from typing import Callable, Optional, Union
20
+
21
+ from grpc import aio
22
+
23
+ from google.api_core import exceptions
24
+ from google.api_core.bidi_base import BidiRpcBase
25
+
26
+ from google.protobuf.message import Message as ProtobufMessage
27
+
28
+
29
+ _LOGGER = logging.getLogger(__name__)
30
+
31
+
32
+ class _AsyncRequestQueueGenerator:
33
+ """_AsyncRequestQueueGenerator is a helper class for sending asynchronous
34
+ requests to a gRPC stream from a Queue.
35
+
36
+ This generator takes asynchronous requests off a given `asyncio.Queue` and
37
+ yields them to gRPC.
38
+
39
+ It's useful when you have an indeterminate, indefinite, or otherwise
40
+ open-ended set of requests to send through a request-streaming (or
41
+ bidirectional) RPC.
42
+
43
+ Example::
44
+
45
+ requests = _AsyncRequestQueueGenerator(q)
46
+ call = await stub.StreamingRequest(requests)
47
+ requests.call = call
48
+
49
+ async for response in call:
50
+ print(response)
51
+ await q.put(...)
52
+
53
+ Args:
54
+ queue (asyncio.Queue): The request queue.
55
+ initial_request (Union[ProtobufMessage,
56
+ Callable[[], ProtobufMessage]]): The initial request to
57
+ yield. This is done independently of the request queue to allow for
58
+ easily restarting streams that require some initial configuration
59
+ request.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ queue: asyncio.Queue,
65
+ initial_request: Optional[
66
+ Union[ProtobufMessage, Callable[[], ProtobufMessage]]
67
+ ] = None,
68
+ ) -> None:
69
+ self._queue = queue
70
+ self._initial_request = initial_request
71
+ self.call: Optional[aio.Call] = None
72
+
73
+ def _is_active(self) -> bool:
74
+ """Returns true if the call is not set or not completed."""
75
+ # Note: there is a possibility that this starts *before* the call
76
+ # property is set. So we have to check if self.call is set before
77
+ # seeing if it's active. We need to return True if self.call is None.
78
+ # See https://github.com/googleapis/python-api-core/issues/560.
79
+ return self.call is None or not self.call.done()
80
+
81
+ async def __aiter__(self):
82
+ # The reason this is necessary is because it lets the user have
83
+ # control on when they would want to send requests proto messages
84
+ # instead of sending all of them initially.
85
+ #
86
+ # This is achieved via asynchronous queue (asyncio.Queue),
87
+ # gRPC awaits until there's a message in the queue.
88
+ #
89
+ # Finally, it allows for retrying without swapping queues because if
90
+ # it does pull an item off the queue when the RPC is inactive, it'll
91
+ # immediately put it back and then exit. This is necessary because
92
+ # yielding the item in this case will cause gRPC to discard it. In
93
+ # practice, this means that the order of messages is not guaranteed.
94
+ # If preserving order is necessary it would be easy to use a priority
95
+ # queue.
96
+ if self._initial_request is not None:
97
+ if callable(self._initial_request):
98
+ yield self._initial_request()
99
+ else:
100
+ yield self._initial_request
101
+
102
+ while True:
103
+ item = await self._queue.get()
104
+
105
+ # The consumer explicitly sent "None", indicating that the request
106
+ # should end.
107
+ if item is None:
108
+ _LOGGER.debug("Cleanly exiting request generator.")
109
+ return
110
+
111
+ if not self._is_active():
112
+ # We have an item, but the call is closed. We should put the
113
+ # item back on the queue so that the next call can consume it.
114
+ await self._queue.put(item)
115
+ _LOGGER.debug(
116
+ "Inactive call, replacing item on queue and exiting "
117
+ "request generator."
118
+ )
119
+ return
120
+
121
+ yield item
122
+
123
+
124
+ class AsyncBidiRpc(BidiRpcBase):
125
+ """A helper for consuming a async bi-directional streaming RPC.
126
+
127
+ This maps gRPC's built-in interface which uses a request iterator and a
128
+ response iterator into a socket-like :func:`send` and :func:`recv`. This
129
+ is a more useful pattern for long-running or asymmetric streams (streams
130
+ where there is not a direct correlation between the requests and
131
+ responses).
132
+
133
+ Example::
134
+
135
+ initial_request = example_pb2.StreamingRpcRequest(
136
+ setting='example')
137
+ rpc = AsyncBidiRpc(
138
+ stub.StreamingRpc,
139
+ initial_request=initial_request,
140
+ metadata=[('name', 'value')]
141
+ )
142
+
143
+ await rpc.open()
144
+
145
+ while rpc.is_active:
146
+ print(await rpc.recv())
147
+ await rpc.send(example_pb2.StreamingRpcRequest(
148
+ data='example'))
149
+
150
+ await rpc.close()
151
+
152
+ This does *not* retry the stream on errors.
153
+
154
+ Args:
155
+ start_rpc (grpc.aio.StreamStreamMultiCallable): The gRPC method used to
156
+ start the RPC.
157
+ initial_request (Union[ProtobufMessage,
158
+ Callable[[], ProtobufMessage]]): The initial request to
159
+ yield. This is useful if an initial request is needed to start the
160
+ stream.
161
+ metadata (Sequence[Tuple(str, str)]): RPC metadata to include in
162
+ the request.
163
+ """
164
+
165
+ def _create_queue(self) -> asyncio.Queue:
166
+ """Create a queue for requests."""
167
+ return asyncio.Queue()
168
+
169
+ async def open(self) -> None:
170
+ """Opens the stream."""
171
+ if self.is_active:
172
+ raise ValueError("Cannot open an already open stream.")
173
+
174
+ request_generator = _AsyncRequestQueueGenerator(
175
+ self._request_queue, initial_request=self._initial_request
176
+ )
177
+ try:
178
+ call = await self._start_rpc(request_generator, metadata=self._rpc_metadata)
179
+ except exceptions.GoogleAPICallError as exc:
180
+ # The original `grpc.aio.AioRpcError` (which is usually also a
181
+ # `grpc.aio.Call`) is available from the ``response`` property on
182
+ # the mapped exception.
183
+ self._on_call_done(exc.response)
184
+ raise
185
+
186
+ request_generator.call = call
187
+
188
+ # TODO: api_core should expose the future interface for wrapped
189
+ # callables as well.
190
+ if hasattr(call, "_wrapped"): # pragma: NO COVER
191
+ call._wrapped.add_done_callback(self._on_call_done)
192
+ else:
193
+ call.add_done_callback(self._on_call_done)
194
+
195
+ self._request_generator = request_generator
196
+ self.call = call
197
+
198
+ async def close(self) -> None:
199
+ """Closes the stream."""
200
+ if self.call is not None:
201
+ self.call.cancel()
202
+
203
+ # Put None in request queue to signal termination.
204
+ await self._request_queue.put(None)
205
+ self._request_generator = None
206
+ self._initial_request = None
207
+ self._callbacks = []
208
+ # Don't set self.call to None. Keep it around so that send/recv can
209
+ # raise the error.
210
+
211
+ async def send(self, request: ProtobufMessage) -> None:
212
+ """Queue a message to be sent on the stream.
213
+
214
+ If the underlying RPC has been closed, this will raise.
215
+
216
+ Args:
217
+ request (ProtobufMessage): The request to send.
218
+ """
219
+ if self.call is None:
220
+ raise ValueError("Cannot send on an RPC stream that has never been opened.")
221
+
222
+ if not self.call.done():
223
+ await self._request_queue.put(request)
224
+ else:
225
+ # calling read should cause the call to raise.
226
+ await self.call.read()
227
+
228
+ async def recv(self) -> ProtobufMessage:
229
+ """Wait for a message to be returned from the stream.
230
+
231
+ If the underlying RPC has been closed, this will raise.
232
+
233
+ Returns:
234
+ ProtobufMessage: The received message.
235
+ """
236
+ if self.call is None:
237
+ raise ValueError("Cannot recv on an RPC stream that has never been opened.")
238
+
239
+ return await self.call.read()
240
+
241
+ @property
242
+ def is_active(self) -> bool:
243
+ """Whether the stream is currently open and active."""
244
+ return self.call is not None and not self.call.done()
py311/lib/python3.11/site-packages/google/api_core/bidi_base.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025, Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # You may obtain a copy of the License at
5
+ # https://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """Base class for bi-directional streaming RPC helpers."""
14
+
15
+
16
+ class BidiRpcBase:
17
+ """A base class for consuming a bi-directional streaming RPC.
18
+
19
+ This maps gRPC's built-in interface which uses a request iterator and a
20
+ response iterator into a socket-like :func:`send` and :func:`recv`. This
21
+ is a more useful pattern for long-running or asymmetric streams (streams
22
+ where there is not a direct correlation between the requests and
23
+ responses).
24
+
25
+ This does *not* retry the stream on errors.
26
+
27
+ Args:
28
+ start_rpc (Union[grpc.StreamStreamMultiCallable,
29
+ grpc.aio.StreamStreamMultiCallable]): The gRPC method used
30
+ to start the RPC.
31
+ initial_request (Union[protobuf.Message,
32
+ Callable[[], protobuf.Message]]): The initial request to
33
+ yield. This is useful if an initial request is needed to start the
34
+ stream.
35
+ metadata (Sequence[Tuple(str, str)]): RPC metadata to include in
36
+ the request.
37
+ """
38
+
39
+ def __init__(self, start_rpc, initial_request=None, metadata=None):
40
+ self._start_rpc = start_rpc
41
+ self._initial_request = initial_request
42
+ self._rpc_metadata = metadata
43
+ self._request_queue = self._create_queue()
44
+ self._request_generator = None
45
+ self._callbacks = []
46
+ self.call = None
47
+
48
+ def _create_queue(self):
49
+ """Create a queue for requests."""
50
+ raise NotImplementedError("`_create_queue` is not implemented.")
51
+
52
+ def add_done_callback(self, callback):
53
+ """Adds a callback that will be called when the RPC terminates.
54
+
55
+ This occurs when the RPC errors or is successfully terminated.
56
+
57
+ Args:
58
+ callback (Union[Callable[[grpc.Future], None], Callable[[Any], None]]):
59
+ The callback to execute after gRPC call completed (success or
60
+ failure).
61
+
62
+ For sync streaming gRPC: Callable[[grpc.Future], None]
63
+
64
+ For async streaming gRPC: Callable[[Any], None]
65
+ """
66
+ self._callbacks.append(callback)
67
+
68
+ def _on_call_done(self, future):
69
+ # This occurs when the RPC errors or is successfully terminated.
70
+ # Note that grpc's "future" here can also be a grpc.RpcError.
71
+ # See note in https://github.com/grpc/grpc/issues/10885#issuecomment-302651331
72
+ # that `grpc.RpcError` is also `grpc.Call`.
73
+ # for asynchronous gRPC call it would be `grpc.aio.AioRpcError`
74
+
75
+ # Note: sync callbacks can be limiting for async code, because you can't
76
+ # await anything in a sync callback.
77
+ for callback in self._callbacks:
78
+ callback(future)
79
+
80
+ @property
81
+ def is_active(self):
82
+ """True if the gRPC call is not done yet."""
83
+ raise NotImplementedError("`is_active` is not implemented.")
84
+
85
+ @property
86
+ def pending_requests(self):
87
+ """Estimate of the number of queued requests."""
88
+ return self._request_queue.qsize()
py311/lib/python3.11/site-packages/google/api_core/client_info.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for providing client information.
16
+
17
+ Client information is used to send information about the calling client,
18
+ such as the library and Python version, to API services.
19
+ """
20
+
21
+ import platform
22
+ from typing import Union
23
+
24
+ from google.api_core import version as api_core_version
25
+
26
+ _PY_VERSION = platform.python_version()
27
+ _API_CORE_VERSION = api_core_version.__version__
28
+
29
+ _GRPC_VERSION: Union[str, None]
30
+
31
+ try:
32
+ import grpc
33
+
34
+ _GRPC_VERSION = grpc.__version__
35
+ except ImportError: # pragma: NO COVER
36
+ _GRPC_VERSION = None
37
+
38
+
39
+ class ClientInfo(object):
40
+ """Client information used to generate a user-agent for API calls.
41
+
42
+ This user-agent information is sent along with API calls to allow the
43
+ receiving service to do analytics on which versions of Python and Google
44
+ libraries are being used.
45
+
46
+ Args:
47
+ python_version (str): The Python interpreter version, for example,
48
+ ``'3.9.6'``.
49
+ grpc_version (Optional[str]): The gRPC library version.
50
+ api_core_version (str): The google-api-core library version.
51
+ gapic_version (Optional[str]): The version of gapic-generated client
52
+ library, if the library was generated by gapic.
53
+ client_library_version (Optional[str]): The version of the client
54
+ library, generally used if the client library was not generated
55
+ by gapic or if additional functionality was built on top of
56
+ a gapic client library.
57
+ user_agent (Optional[str]): Prefix to the user agent header. This is
58
+ used to supply information such as application name or partner tool.
59
+ Recommended format: ``application-or-tool-ID/major.minor.version``.
60
+ rest_version (Optional[str]): A string with labeled versions of the
61
+ dependencies used for REST transport.
62
+ protobuf_runtime_version (Optional[str]): The protobuf runtime version.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ python_version=_PY_VERSION,
68
+ grpc_version=_GRPC_VERSION,
69
+ api_core_version=_API_CORE_VERSION,
70
+ gapic_version=None,
71
+ client_library_version=None,
72
+ user_agent=None,
73
+ rest_version=None,
74
+ protobuf_runtime_version=None,
75
+ ):
76
+ self.python_version = python_version
77
+ self.grpc_version = grpc_version
78
+ self.api_core_version = api_core_version
79
+ self.gapic_version = gapic_version
80
+ self.client_library_version = client_library_version
81
+ self.user_agent = user_agent
82
+ self.rest_version = rest_version
83
+ self.protobuf_runtime_version = protobuf_runtime_version
84
+
85
+ def to_user_agent(self):
86
+ """Returns the user-agent string for this client info."""
87
+
88
+ # Note: the order here is important as the internal metrics system
89
+ # expects these items to be in specific locations.
90
+ ua = ""
91
+
92
+ if self.user_agent is not None:
93
+ ua += "{user_agent} "
94
+
95
+ ua += "gl-python/{python_version} "
96
+
97
+ if self.grpc_version is not None:
98
+ ua += "grpc/{grpc_version} "
99
+
100
+ if self.rest_version is not None:
101
+ ua += "rest/{rest_version} "
102
+
103
+ ua += "gax/{api_core_version} "
104
+
105
+ if self.gapic_version is not None:
106
+ ua += "gapic/{gapic_version} "
107
+
108
+ if self.client_library_version is not None:
109
+ ua += "gccl/{client_library_version} "
110
+
111
+ if self.protobuf_runtime_version is not None:
112
+ ua += "pb/{protobuf_runtime_version} "
113
+
114
+ return ua.format(**self.__dict__).strip()
py311/lib/python3.11/site-packages/google/api_core/client_logging.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import json
3
+ import os
4
+
5
+ from typing import List, Optional
6
+
7
+ _LOGGING_INITIALIZED = False
8
+ _BASE_LOGGER_NAME = "google"
9
+
10
+ # Fields to be included in the StructuredLogFormatter.
11
+ #
12
+ # TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields.
13
+ _recognized_logging_fields = [
14
+ "httpRequest",
15
+ "rpcName",
16
+ "serviceName",
17
+ "credentialsType",
18
+ "credentialsInfo",
19
+ "universeDomain",
20
+ "request",
21
+ "response",
22
+ "metadata",
23
+ "retryAttempt",
24
+ "httpResponse",
25
+ ] # Additional fields to be Logged.
26
+
27
+
28
+ def logger_configured(logger) -> bool:
29
+ """Determines whether `logger` has non-default configuration
30
+
31
+ Args:
32
+ logger: The logger to check.
33
+
34
+ Returns:
35
+ bool: Whether the logger has any non-default configuration.
36
+ """
37
+ return (
38
+ logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate
39
+ )
40
+
41
+
42
+ def initialize_logging():
43
+ """Initializes "google" loggers, partly based on the environment variable
44
+
45
+ Initializes the "google" logger and any loggers (at the "google"
46
+ level or lower) specified by the environment variable
47
+ GOOGLE_SDK_PYTHON_LOGGING_SCOPE, as long as none of these loggers
48
+ were previously configured. If any such loggers (including the
49
+ "google" logger) are initialized, they are set to NOT propagate
50
+ log events up to their parent loggers.
51
+
52
+ This initialization is executed only once, and hence the
53
+ environment variable is only processed the first time this
54
+ function is called.
55
+ """
56
+ global _LOGGING_INITIALIZED
57
+ if _LOGGING_INITIALIZED:
58
+ return
59
+ scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE", "")
60
+ setup_logging(scopes)
61
+ _LOGGING_INITIALIZED = True
62
+
63
+
64
+ def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]:
65
+ """Returns a list of logger names.
66
+
67
+ Splits the single string of comma-separated logger names into a list of individual logger name strings.
68
+
69
+ Args:
70
+ scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.)
71
+
72
+ Returns:
73
+ A list of all the logger names in scopes.
74
+ """
75
+ if not scopes:
76
+ return []
77
+ # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace.
78
+ # TODO(b/380481951): Support logging multiple scopes.
79
+ # TODO(b/380483756): Raise or log a warning for an invalid scope.
80
+ namespaces = [scopes]
81
+ return namespaces
82
+
83
+
84
+ def configure_defaults(logger):
85
+ """Configures `logger` to emit structured info to stdout."""
86
+ if not logger_configured(logger):
87
+ console_handler = logging.StreamHandler()
88
+ logger.setLevel("DEBUG")
89
+ logger.propagate = False
90
+ formatter = StructuredLogFormatter()
91
+ console_handler.setFormatter(formatter)
92
+ logger.addHandler(console_handler)
93
+
94
+
95
+ def setup_logging(scopes: str = ""):
96
+ """Sets up logging for the specified `scopes`.
97
+
98
+ If the loggers specified in `scopes` have not been previously
99
+ configured, this will configure them to emit structured log
100
+ entries to stdout, and to not propagate their log events to their
101
+ parent loggers. Additionally, if the "google" logger (whether it
102
+ was specified in `scopes` or not) was not previously configured,
103
+ it will also configure it to not propagate log events to the root
104
+ logger.
105
+
106
+ Args:
107
+ scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.)
108
+
109
+ """
110
+
111
+ # only returns valid logger scopes (namespaces)
112
+ # this list has at most one element.
113
+ logger_names = parse_logging_scopes(scopes)
114
+
115
+ for namespace in logger_names:
116
+ # This will either create a module level logger or get the reference of the base logger instantiated above.
117
+ logger = logging.getLogger(namespace)
118
+
119
+ # Configure default settings.
120
+ configure_defaults(logger)
121
+
122
+ # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes.
123
+ base_logger = logging.getLogger(_BASE_LOGGER_NAME)
124
+ if not logger_configured(base_logger):
125
+ base_logger.propagate = False
126
+
127
+
128
+ # TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation.
129
+ class StructuredLogFormatter(logging.Formatter):
130
+ # TODO(https://github.com/googleapis/python-api-core/issues/761): ensure that additional fields such as
131
+ # function name, file name, and line no. appear in a log output.
132
+ def format(self, record: logging.LogRecord):
133
+ log_obj = {
134
+ "timestamp": self.formatTime(record),
135
+ "severity": record.levelname,
136
+ "name": record.name,
137
+ "message": record.getMessage(),
138
+ }
139
+
140
+ for field_name in _recognized_logging_fields:
141
+ value = getattr(record, field_name, None)
142
+ if value is not None:
143
+ log_obj[field_name] = value
144
+ return json.dumps(log_obj)
py311/lib/python3.11/site-packages/google/api_core/client_options.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2019 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Client options class.
16
+
17
+ Client options provide a consistent interface for user options to be defined
18
+ across clients.
19
+
20
+ You can pass a client options object to a client.
21
+
22
+ .. code-block:: python
23
+
24
+ from google.api_core.client_options import ClientOptions
25
+ from google.cloud.vision_v1 import ImageAnnotatorClient
26
+
27
+ def get_client_cert():
28
+ # code to load client certificate and private key.
29
+ return client_cert_bytes, client_private_key_bytes
30
+
31
+ options = ClientOptions(api_endpoint="foo.googleapis.com",
32
+ client_cert_source=get_client_cert)
33
+
34
+ client = ImageAnnotatorClient(client_options=options)
35
+
36
+ You can also pass a mapping object.
37
+
38
+ .. code-block:: python
39
+
40
+ from google.cloud.vision_v1 import ImageAnnotatorClient
41
+
42
+ client = ImageAnnotatorClient(
43
+ client_options={
44
+ "api_endpoint": "foo.googleapis.com",
45
+ "client_cert_source" : get_client_cert
46
+ })
47
+
48
+
49
+ """
50
+
51
+ from typing import Callable, Mapping, Optional, Sequence, Tuple
52
+ import warnings
53
+
54
+ from google.api_core import general_helpers
55
+
56
+
57
+ class ClientOptions(object):
58
+ """Client Options used to set options on clients.
59
+
60
+ Args:
61
+ api_endpoint (Optional[str]): The desired API endpoint, e.g.,
62
+ compute.googleapis.com
63
+ client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A callback
64
+ which returns client certificate bytes and private key bytes both in
65
+ PEM format. ``client_cert_source`` and ``client_encrypted_cert_source``
66
+ are mutually exclusive.
67
+ client_encrypted_cert_source (Optional[Callable[[], Tuple[str, str, bytes]]]):
68
+ A callback which returns client certificate file path, encrypted
69
+ private key file path, and the passphrase bytes.``client_cert_source``
70
+ and ``client_encrypted_cert_source`` are mutually exclusive.
71
+ quota_project_id (Optional[str]): A project name that a client's
72
+ quota belongs to.
73
+ credentials_file (Optional[str]): Deprecated. A path to a file storing credentials.
74
+ ``credentials_file` and ``api_key`` are mutually exclusive. This argument will be
75
+ removed in the next major version of `google-api-core`.
76
+
77
+ .. warning::
78
+ Important: If you accept a credential configuration (credential JSON/File/Stream)
79
+ from an external source for authentication to Google Cloud Platform, you must
80
+ validate it before providing it to any Google API or client library. Providing an
81
+ unvalidated credential configuration to Google APIs or libraries can compromise
82
+ the security of your systems and data. For more information, refer to
83
+ `Validate credential configurations from external sources`_.
84
+
85
+ .. _Validate credential configurations from external sources:
86
+
87
+ https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
88
+ scopes (Optional[Sequence[str]]): OAuth access token override scopes.
89
+ api_key (Optional[str]): Google API key. ``credentials_file`` and
90
+ ``api_key`` are mutually exclusive.
91
+ api_audience (Optional[str]): The intended audience for the API calls
92
+ to the service that will be set when using certain 3rd party
93
+ authentication flows. Audience is typically a resource identifier.
94
+ If not set, the service endpoint value will be used as a default.
95
+ An example of a valid ``api_audience`` is: "https://language.googleapis.com".
96
+ universe_domain (Optional[str]): The desired universe domain. This must match
97
+ the one in credentials. If not set, the default universe domain is
98
+ `googleapis.com`. If both `api_endpoint` and `universe_domain` are set,
99
+ then `api_endpoint` is used as the service endpoint. If `api_endpoint` is
100
+ not specified, the format will be `{service}.{universe_domain}`.
101
+
102
+ Raises:
103
+ ValueError: If both ``client_cert_source`` and ``client_encrypted_cert_source``
104
+ are provided, or both ``credentials_file`` and ``api_key`` are provided.
105
+ """
106
+
107
+ def __init__(
108
+ self,
109
+ api_endpoint: Optional[str] = None,
110
+ client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]] = None,
111
+ client_encrypted_cert_source: Optional[
112
+ Callable[[], Tuple[str, str, bytes]]
113
+ ] = None,
114
+ quota_project_id: Optional[str] = None,
115
+ credentials_file: Optional[str] = None,
116
+ scopes: Optional[Sequence[str]] = None,
117
+ api_key: Optional[str] = None,
118
+ api_audience: Optional[str] = None,
119
+ universe_domain: Optional[str] = None,
120
+ ):
121
+ if credentials_file is not None:
122
+ warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)
123
+
124
+ if client_cert_source and client_encrypted_cert_source:
125
+ raise ValueError(
126
+ "client_cert_source and client_encrypted_cert_source are mutually exclusive"
127
+ )
128
+ if api_key and credentials_file:
129
+ raise ValueError("api_key and credentials_file are mutually exclusive")
130
+ self.api_endpoint = api_endpoint
131
+ self.client_cert_source = client_cert_source
132
+ self.client_encrypted_cert_source = client_encrypted_cert_source
133
+ self.quota_project_id = quota_project_id
134
+ self.credentials_file = credentials_file
135
+ self.scopes = scopes
136
+ self.api_key = api_key
137
+ self.api_audience = api_audience
138
+ self.universe_domain = universe_domain
139
+
140
+ def __repr__(self) -> str:
141
+ return "ClientOptions: " + repr(self.__dict__)
142
+
143
+
144
+ def from_dict(options: Mapping[str, object]) -> ClientOptions:
145
+ """Construct a client options object from a mapping object.
146
+
147
+ Args:
148
+ options (collections.abc.Mapping): A mapping object with client options.
149
+ See the docstring for ClientOptions for details on valid arguments.
150
+ """
151
+
152
+ client_options = ClientOptions()
153
+
154
+ for key, value in options.items():
155
+ if hasattr(client_options, key):
156
+ setattr(client_options, key, value)
157
+ else:
158
+ raise ValueError("ClientOptions does not accept an option '" + key + "'")
159
+
160
+ return client_options
py311/lib/python3.11/site-packages/google/api_core/datetime_helpers.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for :mod:`datetime`."""
16
+
17
+ import calendar
18
+ import datetime
19
+ import re
20
+
21
+ from google.protobuf import timestamp_pb2
22
+
23
+
24
+ _UTC_EPOCH = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
25
+ _RFC3339_MICROS = "%Y-%m-%dT%H:%M:%S.%fZ"
26
+ _RFC3339_NO_FRACTION = "%Y-%m-%dT%H:%M:%S"
27
+ # datetime.strptime cannot handle nanosecond precision: parse w/ regex
28
+ _RFC3339_NANOS = re.compile(
29
+ r"""
30
+ (?P<no_fraction>
31
+ \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} # YYYY-MM-DDTHH:MM:SS
32
+ )
33
+ ( # Optional decimal part
34
+ \. # decimal point
35
+ (?P<nanos>\d{1,9}) # nanoseconds, maybe truncated
36
+ )?
37
+ Z # Zulu
38
+ """,
39
+ re.VERBOSE,
40
+ )
41
+
42
+
43
+ def utcnow():
44
+ """A :meth:`datetime.datetime.utcnow()` alias to allow mocking in tests."""
45
+ return datetime.datetime.now(tz=datetime.timezone.utc).replace(tzinfo=None)
46
+
47
+
48
+ def to_milliseconds(value):
49
+ """Convert a zone-aware datetime to milliseconds since the unix epoch.
50
+
51
+ Args:
52
+ value (datetime.datetime): The datetime to covert.
53
+
54
+ Returns:
55
+ int: Milliseconds since the unix epoch.
56
+ """
57
+ micros = to_microseconds(value)
58
+ return micros // 1000
59
+
60
+
61
+ def from_microseconds(value):
62
+ """Convert timestamp in microseconds since the unix epoch to datetime.
63
+
64
+ Args:
65
+ value (float): The timestamp to convert, in microseconds.
66
+
67
+ Returns:
68
+ datetime.datetime: The datetime object equivalent to the timestamp in
69
+ UTC.
70
+ """
71
+ return _UTC_EPOCH + datetime.timedelta(microseconds=value)
72
+
73
+
74
+ def to_microseconds(value):
75
+ """Convert a datetime to microseconds since the unix epoch.
76
+
77
+ Args:
78
+ value (datetime.datetime): The datetime to covert.
79
+
80
+ Returns:
81
+ int: Microseconds since the unix epoch.
82
+ """
83
+ if not value.tzinfo:
84
+ value = value.replace(tzinfo=datetime.timezone.utc)
85
+ # Regardless of what timezone is on the value, convert it to UTC.
86
+ value = value.astimezone(datetime.timezone.utc)
87
+ # Convert the datetime to a microsecond timestamp.
88
+ return int(calendar.timegm(value.timetuple()) * 1e6) + value.microsecond
89
+
90
+
91
+ def from_iso8601_date(value):
92
+ """Convert a ISO8601 date string to a date.
93
+
94
+ Args:
95
+ value (str): The ISO8601 date string.
96
+
97
+ Returns:
98
+ datetime.date: A date equivalent to the date string.
99
+ """
100
+ return datetime.datetime.strptime(value, "%Y-%m-%d").date()
101
+
102
+
103
+ def from_iso8601_time(value):
104
+ """Convert a zoneless ISO8601 time string to a time.
105
+
106
+ Args:
107
+ value (str): The ISO8601 time string.
108
+
109
+ Returns:
110
+ datetime.time: A time equivalent to the time string.
111
+ """
112
+ return datetime.datetime.strptime(value, "%H:%M:%S").time()
113
+
114
+
115
+ def from_rfc3339(value):
116
+ """Convert an RFC3339-format timestamp to a native datetime.
117
+
118
+ Supported formats include those without fractional seconds, or with
119
+ any fraction up to nanosecond precision.
120
+
121
+ .. note::
122
+ Python datetimes do not support nanosecond precision; this function
123
+ therefore truncates such values to microseconds.
124
+
125
+ Args:
126
+ value (str): The RFC3339 string to convert.
127
+
128
+ Returns:
129
+ datetime.datetime: The datetime object equivalent to the timestamp
130
+ in UTC.
131
+
132
+ Raises:
133
+ ValueError: If the timestamp does not match the RFC3339
134
+ regular expression.
135
+ """
136
+ with_nanos = _RFC3339_NANOS.match(value)
137
+
138
+ if with_nanos is None:
139
+ raise ValueError(
140
+ "Timestamp: {!r}, does not match pattern: {!r}".format(
141
+ value, _RFC3339_NANOS.pattern
142
+ )
143
+ )
144
+
145
+ bare_seconds = datetime.datetime.strptime(
146
+ with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION
147
+ )
148
+ fraction = with_nanos.group("nanos")
149
+
150
+ if fraction is None:
151
+ micros = 0
152
+ else:
153
+ scale = 9 - len(fraction)
154
+ nanos = int(fraction) * (10**scale)
155
+ micros = nanos // 1000
156
+
157
+ return bare_seconds.replace(microsecond=micros, tzinfo=datetime.timezone.utc)
158
+
159
+
160
+ from_rfc3339_nanos = from_rfc3339 # from_rfc3339_nanos method was deprecated.
161
+
162
+
163
+ def to_rfc3339(value, ignore_zone=True):
164
+ """Convert a datetime to an RFC3339 timestamp string.
165
+
166
+ Args:
167
+ value (datetime.datetime):
168
+ The datetime object to be converted to a string.
169
+ ignore_zone (bool): If True, then the timezone (if any) of the
170
+ datetime object is ignored and the datetime is treated as UTC.
171
+
172
+ Returns:
173
+ str: The RFC3339 formatted string representing the datetime.
174
+ """
175
+ if not ignore_zone and value.tzinfo is not None:
176
+ # Convert to UTC and remove the time zone info.
177
+ value = value.replace(tzinfo=None) - value.utcoffset()
178
+
179
+ return value.strftime(_RFC3339_MICROS)
180
+
181
+
182
+ class DatetimeWithNanoseconds(datetime.datetime):
183
+ """Track nanosecond in addition to normal datetime attrs.
184
+
185
+ Nanosecond can be passed only as a keyword argument.
186
+ """
187
+
188
+ __slots__ = ("_nanosecond",)
189
+
190
+ # pylint: disable=arguments-differ
191
+ def __new__(cls, *args, **kw):
192
+ nanos = kw.pop("nanosecond", 0)
193
+ if nanos > 0:
194
+ if "microsecond" in kw:
195
+ raise TypeError("Specify only one of 'microsecond' or 'nanosecond'")
196
+ kw["microsecond"] = nanos // 1000
197
+ inst = datetime.datetime.__new__(cls, *args, **kw)
198
+ inst._nanosecond = nanos or 0
199
+ return inst
200
+
201
+ # pylint: disable=arguments-differ
202
+
203
+ @property
204
+ def nanosecond(self):
205
+ """Read-only: nanosecond precision."""
206
+ return self._nanosecond
207
+
208
+ def rfc3339(self):
209
+ """Return an RFC3339-compliant timestamp.
210
+
211
+ Returns:
212
+ (str): Timestamp string according to RFC3339 spec.
213
+ """
214
+ if self._nanosecond == 0:
215
+ return to_rfc3339(self)
216
+ nanos = str(self._nanosecond).rjust(9, "0").rstrip("0")
217
+ return "{}.{}Z".format(self.strftime(_RFC3339_NO_FRACTION), nanos)
218
+
219
+ @classmethod
220
+ def from_rfc3339(cls, stamp):
221
+ """Parse RFC3339-compliant timestamp, preserving nanoseconds.
222
+
223
+ Args:
224
+ stamp (str): RFC3339 stamp, with up to nanosecond precision
225
+
226
+ Returns:
227
+ :class:`DatetimeWithNanoseconds`:
228
+ an instance matching the timestamp string
229
+
230
+ Raises:
231
+ ValueError: if `stamp` does not match the expected format
232
+ """
233
+ with_nanos = _RFC3339_NANOS.match(stamp)
234
+ if with_nanos is None:
235
+ raise ValueError(
236
+ "Timestamp: {}, does not match pattern: {}".format(
237
+ stamp, _RFC3339_NANOS.pattern
238
+ )
239
+ )
240
+ bare = datetime.datetime.strptime(
241
+ with_nanos.group("no_fraction"), _RFC3339_NO_FRACTION
242
+ )
243
+ fraction = with_nanos.group("nanos")
244
+ if fraction is None:
245
+ nanos = 0
246
+ else:
247
+ scale = 9 - len(fraction)
248
+ nanos = int(fraction) * (10**scale)
249
+ return cls(
250
+ bare.year,
251
+ bare.month,
252
+ bare.day,
253
+ bare.hour,
254
+ bare.minute,
255
+ bare.second,
256
+ nanosecond=nanos,
257
+ tzinfo=datetime.timezone.utc,
258
+ )
259
+
260
+ def timestamp_pb(self):
261
+ """Return a timestamp message.
262
+
263
+ Returns:
264
+ (:class:`~google.protobuf.timestamp_pb2.Timestamp`): Timestamp message
265
+ """
266
+ inst = (
267
+ self
268
+ if self.tzinfo is not None
269
+ else self.replace(tzinfo=datetime.timezone.utc)
270
+ )
271
+ delta = inst - _UTC_EPOCH
272
+ seconds = int(delta.total_seconds())
273
+ nanos = self._nanosecond or self.microsecond * 1000
274
+ return timestamp_pb2.Timestamp(seconds=seconds, nanos=nanos)
275
+
276
+ @classmethod
277
+ def from_timestamp_pb(cls, stamp):
278
+ """Parse RFC3339-compliant timestamp, preserving nanoseconds.
279
+
280
+ Args:
281
+ stamp (:class:`~google.protobuf.timestamp_pb2.Timestamp`): timestamp message
282
+
283
+ Returns:
284
+ :class:`DatetimeWithNanoseconds`:
285
+ an instance matching the timestamp message
286
+ """
287
+ microseconds = int(stamp.seconds * 1e6)
288
+ bare = from_microseconds(microseconds)
289
+ return cls(
290
+ bare.year,
291
+ bare.month,
292
+ bare.day,
293
+ bare.hour,
294
+ bare.minute,
295
+ bare.second,
296
+ nanosecond=stamp.nanos,
297
+ tzinfo=datetime.timezone.utc,
298
+ )
py311/lib/python3.11/site-packages/google/api_core/exceptions.py ADDED
@@ -0,0 +1,670 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2014 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Exceptions raised by Google API core & clients.
16
+
17
+ This module provides base classes for all errors raised by libraries based
18
+ on :mod:`google.api_core`, including both HTTP and gRPC clients.
19
+ """
20
+
21
+ from __future__ import absolute_import
22
+ from __future__ import unicode_literals
23
+
24
+ import http.client
25
+ from typing import Optional, Dict
26
+ from typing import Union
27
+ import warnings
28
+
29
+ from google.rpc import error_details_pb2
30
+
31
+
32
+ def _warn_could_not_import_grpcio_status():
33
+ warnings.warn(
34
+ "Please install grpcio-status to obtain helpful grpc error messages.",
35
+ ImportWarning,
36
+ ) # pragma: NO COVER
37
+
38
+
39
+ try:
40
+ import grpc
41
+
42
+ try:
43
+ from grpc_status import rpc_status
44
+ except ImportError: # pragma: NO COVER
45
+ _warn_could_not_import_grpcio_status()
46
+ rpc_status = None
47
+ except ImportError: # pragma: NO COVER
48
+ grpc = None
49
+
50
+ # Lookup tables for mapping exceptions from HTTP and gRPC transports.
51
+ # Populated by _GoogleAPICallErrorMeta
52
+ _HTTP_CODE_TO_EXCEPTION: Dict[int, Exception] = {}
53
+ _GRPC_CODE_TO_EXCEPTION: Dict[int, Exception] = {}
54
+
55
+ # Additional lookup table to map integer status codes to grpc status code
56
+ # grpc does not currently support initializing enums from ints
57
+ # i.e., grpc.StatusCode(5) raises an error
58
+ _INT_TO_GRPC_CODE = {}
59
+ if grpc is not None: # pragma: no branch
60
+ for x in grpc.StatusCode:
61
+ _INT_TO_GRPC_CODE[x.value[0]] = x
62
+
63
+
64
+ class GoogleAPIError(Exception):
65
+ """Base class for all exceptions raised by Google API Clients."""
66
+
67
+ pass
68
+
69
+
70
+ class DuplicateCredentialArgs(GoogleAPIError):
71
+ """Raised when multiple credentials are passed."""
72
+
73
+ pass
74
+
75
+
76
+ class RetryError(GoogleAPIError):
77
+ """Raised when a function has exhausted all of its available retries.
78
+
79
+ Args:
80
+ message (str): The exception message.
81
+ cause (Exception): The last exception raised when retrying the
82
+ function.
83
+ """
84
+
85
+ def __init__(self, message, cause):
86
+ super(RetryError, self).__init__(message)
87
+ self.message = message
88
+ self._cause = cause
89
+
90
+ @property
91
+ def cause(self):
92
+ """The last exception raised when retrying the function."""
93
+ return self._cause
94
+
95
+ def __str__(self):
96
+ return "{}, last exception: {}".format(self.message, self.cause)
97
+
98
+
99
+ class _GoogleAPICallErrorMeta(type):
100
+ """Metaclass for registering GoogleAPICallError subclasses."""
101
+
102
+ def __new__(mcs, name, bases, class_dict):
103
+ cls = type.__new__(mcs, name, bases, class_dict)
104
+ if cls.code is not None:
105
+ _HTTP_CODE_TO_EXCEPTION.setdefault(cls.code, cls)
106
+ if cls.grpc_status_code is not None:
107
+ _GRPC_CODE_TO_EXCEPTION.setdefault(cls.grpc_status_code, cls)
108
+ return cls
109
+
110
+
111
+ class GoogleAPICallError(GoogleAPIError, metaclass=_GoogleAPICallErrorMeta):
112
+ """Base class for exceptions raised by calling API methods.
113
+
114
+ Args:
115
+ message (str): The exception message.
116
+ errors (Sequence[Any]): An optional list of error details.
117
+ details (Sequence[Any]): An optional list of objects defined in google.rpc.error_details.
118
+ response (Union[requests.Request, grpc.Call]): The response or
119
+ gRPC call metadata.
120
+ error_info (Union[error_details_pb2.ErrorInfo, None]): An optional object containing error info
121
+ (google.rpc.error_details.ErrorInfo).
122
+ """
123
+
124
+ code: Union[int, None] = None
125
+ """Optional[int]: The HTTP status code associated with this error.
126
+
127
+ This may be ``None`` if the exception does not have a direct mapping
128
+ to an HTTP error.
129
+
130
+ See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
131
+ """
132
+
133
+ grpc_status_code = None
134
+ """Optional[grpc.StatusCode]: The gRPC status code associated with this
135
+ error.
136
+
137
+ This may be ``None`` if the exception does not match up to a gRPC error.
138
+ """
139
+
140
+ def __init__(self, message, errors=(), details=(), response=None, error_info=None):
141
+ super(GoogleAPICallError, self).__init__(message)
142
+ self.message = message
143
+ """str: The exception message."""
144
+ self._errors = errors
145
+ self._details = details
146
+ self._response = response
147
+ self._error_info = error_info
148
+
149
+ def __str__(self):
150
+ error_msg = "{} {}".format(self.code, self.message)
151
+ if self.details:
152
+ error_msg = "{} {}".format(error_msg, self.details)
153
+ # Note: This else condition can be removed once proposal A from
154
+ # b/284179390 is implemented.
155
+ else:
156
+ if self.errors:
157
+ errors = [
158
+ f"{error.code}: {error.message}"
159
+ for error in self.errors
160
+ if hasattr(error, "code") and hasattr(error, "message")
161
+ ]
162
+ if errors:
163
+ error_msg = "{} {}".format(error_msg, "\n".join(errors))
164
+ return error_msg
165
+
166
+ @property
167
+ def reason(self):
168
+ """The reason of the error.
169
+
170
+ Reference:
171
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112
172
+
173
+ Returns:
174
+ Union[str, None]: An optional string containing reason of the error.
175
+ """
176
+ return self._error_info.reason if self._error_info else None
177
+
178
+ @property
179
+ def domain(self):
180
+ """The logical grouping to which the "reason" belongs.
181
+
182
+ Reference:
183
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112
184
+
185
+ Returns:
186
+ Union[str, None]: An optional string containing a logical grouping to which the "reason" belongs.
187
+ """
188
+ return self._error_info.domain if self._error_info else None
189
+
190
+ @property
191
+ def metadata(self):
192
+ """Additional structured details about this error.
193
+
194
+ Reference:
195
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto#L112
196
+
197
+ Returns:
198
+ Union[Dict[str, str], None]: An optional object containing structured details about the error.
199
+ """
200
+ return self._error_info.metadata if self._error_info else None
201
+
202
+ @property
203
+ def errors(self):
204
+ """Detailed error information.
205
+
206
+ Returns:
207
+ Sequence[Any]: A list of additional error details.
208
+ """
209
+ return list(self._errors)
210
+
211
+ @property
212
+ def details(self):
213
+ """Information contained in google.rpc.status.details.
214
+
215
+ Reference:
216
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
217
+ https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
218
+
219
+ Returns:
220
+ Sequence[Any]: A list of structured objects from error_details.proto
221
+ """
222
+ return list(self._details)
223
+
224
+ @property
225
+ def response(self):
226
+ """Optional[Union[requests.Request, grpc.Call]]: The response or
227
+ gRPC call metadata."""
228
+ return self._response
229
+
230
+
231
+ class Redirection(GoogleAPICallError):
232
+ """Base class for for all redirection (HTTP 3xx) responses."""
233
+
234
+
235
+ class MovedPermanently(Redirection):
236
+ """Exception mapping a ``301 Moved Permanently`` response."""
237
+
238
+ code = http.client.MOVED_PERMANENTLY
239
+
240
+
241
+ class NotModified(Redirection):
242
+ """Exception mapping a ``304 Not Modified`` response."""
243
+
244
+ code = http.client.NOT_MODIFIED
245
+
246
+
247
+ class TemporaryRedirect(Redirection):
248
+ """Exception mapping a ``307 Temporary Redirect`` response."""
249
+
250
+ code = http.client.TEMPORARY_REDIRECT
251
+
252
+
253
+ class ResumeIncomplete(Redirection):
254
+ """Exception mapping a ``308 Resume Incomplete`` response.
255
+
256
+ .. note:: :attr:`http.client.PERMANENT_REDIRECT` is ``308``, but Google
257
+ APIs differ in their use of this status code.
258
+ """
259
+
260
+ code = 308
261
+
262
+
263
+ class ClientError(GoogleAPICallError):
264
+ """Base class for all client error (HTTP 4xx) responses."""
265
+
266
+
267
+ class BadRequest(ClientError):
268
+ """Exception mapping a ``400 Bad Request`` response."""
269
+
270
+ code = http.client.BAD_REQUEST
271
+
272
+
273
+ class InvalidArgument(BadRequest):
274
+ """Exception mapping a :attr:`grpc.StatusCode.INVALID_ARGUMENT` error."""
275
+
276
+ grpc_status_code = grpc.StatusCode.INVALID_ARGUMENT if grpc is not None else None
277
+
278
+
279
+ class FailedPrecondition(BadRequest):
280
+ """Exception mapping a :attr:`grpc.StatusCode.FAILED_PRECONDITION`
281
+ error."""
282
+
283
+ grpc_status_code = grpc.StatusCode.FAILED_PRECONDITION if grpc is not None else None
284
+
285
+
286
+ class OutOfRange(BadRequest):
287
+ """Exception mapping a :attr:`grpc.StatusCode.OUT_OF_RANGE` error."""
288
+
289
+ grpc_status_code = grpc.StatusCode.OUT_OF_RANGE if grpc is not None else None
290
+
291
+
292
+ class Unauthorized(ClientError):
293
+ """Exception mapping a ``401 Unauthorized`` response."""
294
+
295
+ code = http.client.UNAUTHORIZED
296
+
297
+
298
+ class Unauthenticated(Unauthorized):
299
+ """Exception mapping a :attr:`grpc.StatusCode.UNAUTHENTICATED` error."""
300
+
301
+ grpc_status_code = grpc.StatusCode.UNAUTHENTICATED if grpc is not None else None
302
+
303
+
304
+ class Forbidden(ClientError):
305
+ """Exception mapping a ``403 Forbidden`` response."""
306
+
307
+ code = http.client.FORBIDDEN
308
+
309
+
310
+ class PermissionDenied(Forbidden):
311
+ """Exception mapping a :attr:`grpc.StatusCode.PERMISSION_DENIED` error."""
312
+
313
+ grpc_status_code = grpc.StatusCode.PERMISSION_DENIED if grpc is not None else None
314
+
315
+
316
+ class NotFound(ClientError):
317
+ """Exception mapping a ``404 Not Found`` response or a
318
+ :attr:`grpc.StatusCode.NOT_FOUND` error."""
319
+
320
+ code = http.client.NOT_FOUND
321
+ grpc_status_code = grpc.StatusCode.NOT_FOUND if grpc is not None else None
322
+
323
+
324
+ class MethodNotAllowed(ClientError):
325
+ """Exception mapping a ``405 Method Not Allowed`` response."""
326
+
327
+ code = http.client.METHOD_NOT_ALLOWED
328
+
329
+
330
+ class Conflict(ClientError):
331
+ """Exception mapping a ``409 Conflict`` response."""
332
+
333
+ code = http.client.CONFLICT
334
+
335
+
336
+ class AlreadyExists(Conflict):
337
+ """Exception mapping a :attr:`grpc.StatusCode.ALREADY_EXISTS` error."""
338
+
339
+ grpc_status_code = grpc.StatusCode.ALREADY_EXISTS if grpc is not None else None
340
+
341
+
342
+ class Aborted(Conflict):
343
+ """Exception mapping a :attr:`grpc.StatusCode.ABORTED` error."""
344
+
345
+ grpc_status_code = grpc.StatusCode.ABORTED if grpc is not None else None
346
+
347
+
348
+ class LengthRequired(ClientError):
349
+ """Exception mapping a ``411 Length Required`` response."""
350
+
351
+ code = http.client.LENGTH_REQUIRED
352
+
353
+
354
+ class PreconditionFailed(ClientError):
355
+ """Exception mapping a ``412 Precondition Failed`` response."""
356
+
357
+ code = http.client.PRECONDITION_FAILED
358
+
359
+
360
+ class RequestRangeNotSatisfiable(ClientError):
361
+ """Exception mapping a ``416 Request Range Not Satisfiable`` response."""
362
+
363
+ code = http.client.REQUESTED_RANGE_NOT_SATISFIABLE
364
+
365
+
366
+ class TooManyRequests(ClientError):
367
+ """Exception mapping a ``429 Too Many Requests`` response."""
368
+
369
+ code = http.client.TOO_MANY_REQUESTS
370
+
371
+
372
+ class ResourceExhausted(TooManyRequests):
373
+ """Exception mapping a :attr:`grpc.StatusCode.RESOURCE_EXHAUSTED` error."""
374
+
375
+ grpc_status_code = grpc.StatusCode.RESOURCE_EXHAUSTED if grpc is not None else None
376
+
377
+
378
+ class Cancelled(ClientError):
379
+ """Exception mapping a :attr:`grpc.StatusCode.CANCELLED` error."""
380
+
381
+ # This maps to HTTP status code 499. See
382
+ # https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
383
+ code = 499
384
+ grpc_status_code = grpc.StatusCode.CANCELLED if grpc is not None else None
385
+
386
+
387
+ class ServerError(GoogleAPICallError):
388
+ """Base for 5xx responses."""
389
+
390
+
391
+ class InternalServerError(ServerError):
392
+ """Exception mapping a ``500 Internal Server Error`` response. or a
393
+ :attr:`grpc.StatusCode.INTERNAL` error."""
394
+
395
+ code = http.client.INTERNAL_SERVER_ERROR
396
+ grpc_status_code = grpc.StatusCode.INTERNAL if grpc is not None else None
397
+
398
+
399
+ class Unknown(ServerError):
400
+ """Exception mapping a :attr:`grpc.StatusCode.UNKNOWN` error."""
401
+
402
+ grpc_status_code = grpc.StatusCode.UNKNOWN if grpc is not None else None
403
+
404
+
405
+ class DataLoss(ServerError):
406
+ """Exception mapping a :attr:`grpc.StatusCode.DATA_LOSS` error."""
407
+
408
+ grpc_status_code = grpc.StatusCode.DATA_LOSS if grpc is not None else None
409
+
410
+
411
+ class MethodNotImplemented(ServerError):
412
+ """Exception mapping a ``501 Not Implemented`` response or a
413
+ :attr:`grpc.StatusCode.UNIMPLEMENTED` error."""
414
+
415
+ code = http.client.NOT_IMPLEMENTED
416
+ grpc_status_code = grpc.StatusCode.UNIMPLEMENTED if grpc is not None else None
417
+
418
+
419
+ class BadGateway(ServerError):
420
+ """Exception mapping a ``502 Bad Gateway`` response."""
421
+
422
+ code = http.client.BAD_GATEWAY
423
+
424
+
425
+ class ServiceUnavailable(ServerError):
426
+ """Exception mapping a ``503 Service Unavailable`` response or a
427
+ :attr:`grpc.StatusCode.UNAVAILABLE` error."""
428
+
429
+ code = http.client.SERVICE_UNAVAILABLE
430
+ grpc_status_code = grpc.StatusCode.UNAVAILABLE if grpc is not None else None
431
+
432
+
433
+ class GatewayTimeout(ServerError):
434
+ """Exception mapping a ``504 Gateway Timeout`` response."""
435
+
436
+ code = http.client.GATEWAY_TIMEOUT
437
+
438
+
439
+ class DeadlineExceeded(GatewayTimeout):
440
+ """Exception mapping a :attr:`grpc.StatusCode.DEADLINE_EXCEEDED` error."""
441
+
442
+ grpc_status_code = grpc.StatusCode.DEADLINE_EXCEEDED if grpc is not None else None
443
+
444
+
445
+ class AsyncRestUnsupportedParameterError(NotImplementedError):
446
+ """Raised when an unsupported parameter is configured against async rest transport."""
447
+
448
+ pass
449
+
450
+
451
+ def exception_class_for_http_status(status_code):
452
+ """Return the exception class for a specific HTTP status code.
453
+
454
+ Args:
455
+ status_code (int): The HTTP status code.
456
+
457
+ Returns:
458
+ :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`.
459
+ """
460
+ return _HTTP_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError)
461
+
462
+
463
+ def from_http_status(status_code, message, **kwargs):
464
+ """Create a :class:`GoogleAPICallError` from an HTTP status code.
465
+
466
+ Args:
467
+ status_code (int): The HTTP status code.
468
+ message (str): The exception message.
469
+ kwargs: Additional arguments passed to the :class:`GoogleAPICallError`
470
+ constructor.
471
+
472
+ Returns:
473
+ GoogleAPICallError: An instance of the appropriate subclass of
474
+ :class:`GoogleAPICallError`.
475
+ """
476
+ error_class = exception_class_for_http_status(status_code)
477
+ error = error_class(message, **kwargs)
478
+
479
+ if error.code is None:
480
+ error.code = status_code
481
+
482
+ return error
483
+
484
+
485
+ def _format_rest_error_message(error, method, url):
486
+ method = method.upper() if method else None
487
+ message = "{method} {url}: {error}".format(
488
+ method=method,
489
+ url=url,
490
+ error=error,
491
+ )
492
+ return message
493
+
494
+
495
+ # NOTE: We're moving away from `from_http_status` because it expects an aiohttp response compared
496
+ # to `format_http_response_error` which expects a more abstract response from google.auth and is
497
+ # compatible with both sync and async response types.
498
+ # TODO(https://github.com/googleapis/python-api-core/issues/691): Add type hint for response.
499
+ def format_http_response_error(
500
+ response, method: str, url: str, payload: Optional[Dict] = None
501
+ ):
502
+ """Create a :class:`GoogleAPICallError` from a google auth rest response.
503
+
504
+ Args:
505
+ response Union[google.auth.transport.Response, google.auth.aio.transport.Response]: The HTTP response.
506
+ method Optional(str): The HTTP request method.
507
+ url Optional(str): The HTTP request url.
508
+ payload Optional(dict): The HTTP response payload. If not passed in, it is read from response for a response type of google.auth.transport.Response.
509
+
510
+ Returns:
511
+ GoogleAPICallError: An instance of the appropriate subclass of
512
+ :class:`GoogleAPICallError`, with the message and errors populated
513
+ from the response.
514
+ """
515
+ payload = {} if not payload else payload
516
+ error_message = payload.get("error", {}).get("message", "unknown error")
517
+ errors = payload.get("error", {}).get("errors", ())
518
+ # In JSON, details are already formatted in developer-friendly way.
519
+ details = payload.get("error", {}).get("details", ())
520
+ error_info_list = list(
521
+ filter(
522
+ lambda detail: detail.get("@type", "")
523
+ == "type.googleapis.com/google.rpc.ErrorInfo",
524
+ details,
525
+ )
526
+ )
527
+ error_info = error_info_list[0] if error_info_list else None
528
+ message = _format_rest_error_message(error_message, method, url)
529
+
530
+ exception = from_http_status(
531
+ response.status_code,
532
+ message,
533
+ errors=errors,
534
+ details=details,
535
+ response=response,
536
+ error_info=error_info,
537
+ )
538
+ return exception
539
+
540
+
541
+ def from_http_response(response):
542
+ """Create a :class:`GoogleAPICallError` from a :class:`requests.Response`.
543
+
544
+ Args:
545
+ response (requests.Response): The HTTP response.
546
+
547
+ Returns:
548
+ GoogleAPICallError: An instance of the appropriate subclass of
549
+ :class:`GoogleAPICallError`, with the message and errors populated
550
+ from the response.
551
+ """
552
+ try:
553
+ payload = response.json()
554
+ except ValueError:
555
+ payload = {"error": {"message": response.text or "unknown error"}}
556
+ return format_http_response_error(
557
+ response, response.request.method, response.request.url, payload
558
+ )
559
+
560
+
561
+ def exception_class_for_grpc_status(status_code):
562
+ """Return the exception class for a specific :class:`grpc.StatusCode`.
563
+
564
+ Args:
565
+ status_code (grpc.StatusCode): The gRPC status code.
566
+
567
+ Returns:
568
+ :func:`type`: the appropriate subclass of :class:`GoogleAPICallError`.
569
+ """
570
+ return _GRPC_CODE_TO_EXCEPTION.get(status_code, GoogleAPICallError)
571
+
572
+
573
+ def from_grpc_status(status_code, message, **kwargs):
574
+ """Create a :class:`GoogleAPICallError` from a :class:`grpc.StatusCode`.
575
+
576
+ Args:
577
+ status_code (Union[grpc.StatusCode, int]): The gRPC status code.
578
+ message (str): The exception message.
579
+ kwargs: Additional arguments passed to the :class:`GoogleAPICallError`
580
+ constructor.
581
+
582
+ Returns:
583
+ GoogleAPICallError: An instance of the appropriate subclass of
584
+ :class:`GoogleAPICallError`.
585
+ """
586
+
587
+ if isinstance(status_code, int):
588
+ status_code = _INT_TO_GRPC_CODE.get(status_code, status_code)
589
+
590
+ error_class = exception_class_for_grpc_status(status_code)
591
+ error = error_class(message, **kwargs)
592
+
593
+ if error.grpc_status_code is None:
594
+ error.grpc_status_code = status_code
595
+
596
+ return error
597
+
598
+
599
+ def _is_informative_grpc_error(rpc_exc):
600
+ return hasattr(rpc_exc, "code") and hasattr(rpc_exc, "details")
601
+
602
+
603
+ def _parse_grpc_error_details(rpc_exc):
604
+ if not rpc_status: # pragma: NO COVER
605
+ _warn_could_not_import_grpcio_status()
606
+ return [], None
607
+ try:
608
+ status = rpc_status.from_call(rpc_exc)
609
+ except NotImplementedError: # workaround
610
+ return [], None
611
+
612
+ if not status:
613
+ return [], None
614
+
615
+ possible_errors = [
616
+ error_details_pb2.BadRequest,
617
+ error_details_pb2.PreconditionFailure,
618
+ error_details_pb2.QuotaFailure,
619
+ error_details_pb2.ErrorInfo,
620
+ error_details_pb2.RetryInfo,
621
+ error_details_pb2.ResourceInfo,
622
+ error_details_pb2.RequestInfo,
623
+ error_details_pb2.DebugInfo,
624
+ error_details_pb2.Help,
625
+ error_details_pb2.LocalizedMessage,
626
+ ]
627
+ error_info = None
628
+ error_details = []
629
+ for detail in status.details:
630
+ matched_detail_cls = list(
631
+ filter(lambda x: detail.Is(x.DESCRIPTOR), possible_errors)
632
+ )
633
+ # If nothing matched, use detail directly.
634
+ if len(matched_detail_cls) == 0:
635
+ info = detail
636
+ else:
637
+ info = matched_detail_cls[0]()
638
+ detail.Unpack(info)
639
+ error_details.append(info)
640
+ if isinstance(info, error_details_pb2.ErrorInfo):
641
+ error_info = info
642
+ return error_details, error_info
643
+
644
+
645
+ def from_grpc_error(rpc_exc):
646
+ """Create a :class:`GoogleAPICallError` from a :class:`grpc.RpcError`.
647
+
648
+ Args:
649
+ rpc_exc (grpc.RpcError): The gRPC error.
650
+
651
+ Returns:
652
+ GoogleAPICallError: An instance of the appropriate subclass of
653
+ :class:`GoogleAPICallError`.
654
+ """
655
+ # NOTE(lidiz) All gRPC error shares the parent class grpc.RpcError.
656
+ # However, check for grpc.RpcError breaks backward compatibility.
657
+ if (
658
+ grpc is not None and isinstance(rpc_exc, grpc.Call)
659
+ ) or _is_informative_grpc_error(rpc_exc):
660
+ details, err_info = _parse_grpc_error_details(rpc_exc)
661
+ return from_grpc_status(
662
+ rpc_exc.code(),
663
+ rpc_exc.details(),
664
+ errors=(rpc_exc,),
665
+ details=details,
666
+ response=rpc_exc,
667
+ error_info=err_info,
668
+ )
669
+ else:
670
+ return GoogleAPICallError(str(rpc_exc), errors=(rpc_exc,), response=rpc_exc)
py311/lib/python3.11/site-packages/google/api_core/extended_operation.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Futures for extended long-running operations returned from Google Cloud APIs.
16
+
17
+ These futures can be used to synchronously wait for the result of a
18
+ long-running operations using :meth:`ExtendedOperation.result`:
19
+
20
+ .. code-block:: python
21
+
22
+ extended_operation = my_api_client.long_running_method()
23
+
24
+ extended_operation.result()
25
+
26
+ Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:
27
+
28
+ .. code-block:: python
29
+
30
+ extended_operation = my_api_client.long_running_method()
31
+
32
+ def my_callback(ex_op):
33
+ print(f"Operation {ex_op.name} completed")
34
+
35
+ extended_operation.add_done_callback(my_callback)
36
+
37
+ """
38
+
39
+ import threading
40
+
41
+ from google.api_core import exceptions
42
+ from google.api_core.future import polling
43
+
44
+
45
+ class ExtendedOperation(polling.PollingFuture):
46
+ """An ExtendedOperation future for interacting with a Google API Long-Running Operation.
47
+
48
+ Args:
49
+ extended_operation (proto.Message): The initial operation.
50
+ refresh (Callable[[], type(extended_operation)]): A callable that returns
51
+ the latest state of the operation.
52
+ cancel (Callable[[], None]): A callable that tries to cancel the operation.
53
+ polling Optional(google.api_core.retry.Retry): The configuration used
54
+ for polling. This can be used to control how often :meth:`done`
55
+ is polled. If the ``timeout`` argument to :meth:`result` is
56
+ specified it will override the ``polling.timeout`` property.
57
+ retry Optional(google.api_core.retry.Retry): DEPRECATED use ``polling``
58
+ instead. If specified it will override ``polling`` parameter to
59
+ maintain backward compatibility.
60
+
61
+ Note: Most long-running API methods use google.api_core.operation.Operation
62
+ This class is a wrapper for a subset of methods that use alternative
63
+ Long-Running Operation (LRO) semantics.
64
+
65
+ Note: there is not a concrete type the extended operation must be.
66
+ It MUST have fields that correspond to the following, POSSIBLY WITH DIFFERENT NAMES:
67
+ * name: str
68
+ * status: Union[str, bool, enum.Enum]
69
+ * error_code: int
70
+ * error_message: str
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ extended_operation,
76
+ refresh,
77
+ cancel,
78
+ polling=polling.DEFAULT_POLLING,
79
+ **kwargs,
80
+ ):
81
+ super().__init__(polling=polling, **kwargs)
82
+ self._extended_operation = extended_operation
83
+ self._refresh = refresh
84
+ self._cancel = cancel
85
+ # Note: the extended operation does not give a good way to indicate cancellation.
86
+ # We make do with manually tracking cancellation and checking for doneness.
87
+ self._cancelled = False
88
+ self._completion_lock = threading.Lock()
89
+ # Invoke in case the operation came back already complete.
90
+ self._handle_refreshed_operation()
91
+
92
+ # Note: the following four properties MUST be overridden in a subclass
93
+ # if, and only if, the fields in the corresponding extended operation message
94
+ # have different names.
95
+ #
96
+ # E.g. we have an extended operation class that looks like
97
+ #
98
+ # class MyOperation(proto.Message):
99
+ # moniker = proto.Field(proto.STRING, number=1)
100
+ # status_msg = proto.Field(proto.STRING, number=2)
101
+ # optional http_error_code = proto.Field(proto.INT32, number=3)
102
+ # optional http_error_msg = proto.Field(proto.STRING, number=4)
103
+ #
104
+ # the ExtendedOperation subclass would provide property overrides that map
105
+ # to these (poorly named) fields.
106
+ @property
107
+ def name(self):
108
+ return self._extended_operation.name
109
+
110
+ @property
111
+ def status(self):
112
+ return self._extended_operation.status
113
+
114
+ @property
115
+ def error_code(self):
116
+ return self._extended_operation.error_code
117
+
118
+ @property
119
+ def error_message(self):
120
+ return self._extended_operation.error_message
121
+
122
+ def __getattr__(self, name):
123
+ return getattr(self._extended_operation, name)
124
+
125
+ def done(self, retry=None):
126
+ self._refresh_and_update(retry)
127
+ return self._extended_operation.done
128
+
129
+ def cancel(self):
130
+ if self.done():
131
+ return False
132
+
133
+ self._cancel()
134
+ self._cancelled = True
135
+ return True
136
+
137
+ def cancelled(self):
138
+ # TODO(dovs): there is not currently a good way to determine whether the
139
+ # operation has been cancelled.
140
+ # The best we can do is manually keep track of cancellation
141
+ # and check for doneness.
142
+ if not self._cancelled:
143
+ return False
144
+
145
+ self._refresh_and_update()
146
+ return self._extended_operation.done
147
+
148
+ def _refresh_and_update(self, retry=None):
149
+ if not self._extended_operation.done:
150
+ self._extended_operation = (
151
+ self._refresh(retry=retry) if retry else self._refresh()
152
+ )
153
+ self._handle_refreshed_operation()
154
+
155
+ def _handle_refreshed_operation(self):
156
+ with self._completion_lock:
157
+ if not self._extended_operation.done:
158
+ return
159
+
160
+ if self.error_code and self.error_message:
161
+ # Note: `errors` can be removed once proposal A from
162
+ # b/284179390 is implemented.
163
+ errors = []
164
+ if hasattr(self, "error") and hasattr(self.error, "errors"):
165
+ errors = self.error.errors
166
+ exception = exceptions.from_http_status(
167
+ status_code=self.error_code,
168
+ message=self.error_message,
169
+ response=self._extended_operation,
170
+ errors=errors,
171
+ )
172
+ self.set_exception(exception)
173
+ elif self.error_code or self.error_message:
174
+ exception = exceptions.GoogleAPICallError(
175
+ f"Unexpected error {self.error_code}: {self.error_message}"
176
+ )
177
+ self.set_exception(exception)
178
+ else:
179
+ # Extended operations have no payload.
180
+ self.set_result(None)
181
+
182
+ @classmethod
183
+ def make(cls, refresh, cancel, extended_operation, **kwargs):
184
+ """
185
+ Return an instantiated ExtendedOperation (or child) that wraps
186
+ * a refresh callable
187
+ * a cancel callable (can be a no-op)
188
+ * an initial result
189
+
190
+ .. note::
191
+ It is the caller's responsibility to set up refresh and cancel
192
+ with their correct request argument.
193
+ The reason for this is that the services that use Extended Operations
194
+ have rpcs that look something like the following:
195
+
196
+ // service.proto
197
+ service MyLongService {
198
+ rpc StartLongTask(StartLongTaskRequest) returns (ExtendedOperation) {
199
+ option (google.cloud.operation_service) = "CustomOperationService";
200
+ }
201
+ }
202
+
203
+ service CustomOperationService {
204
+ rpc Get(GetOperationRequest) returns (ExtendedOperation) {
205
+ option (google.cloud.operation_polling_method) = true;
206
+ }
207
+ }
208
+
209
+ Any info needed for the poll, e.g. a name, path params, etc.
210
+ is held in the request, which the initial client method is in a much
211
+ better position to make made because the caller made the initial request.
212
+
213
+ TL;DR: the caller sets up closures for refresh and cancel that carry
214
+ the properly configured requests.
215
+
216
+ Args:
217
+ refresh (Callable[Optional[Retry]][type(extended_operation)]): A callable that
218
+ returns the latest state of the operation.
219
+ cancel (Callable[][Any]): A callable that tries to cancel the operation
220
+ on a best effort basis.
221
+ extended_operation (Any): The initial response of the long running method.
222
+ See the docstring for ExtendedOperation.__init__ for requirements on
223
+ the type and fields of extended_operation
224
+ """
225
+ return cls(extended_operation, refresh, cancel, **kwargs)
py311/lib/python3.11/site-packages/google/api_core/general_helpers.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # This import for backward compatibility only.
16
+ from functools import wraps # noqa: F401 pragma: NO COVER
17
+
18
+ _CREDENTIALS_FILE_WARNING = """\
19
+ The `credentials_file` argument is deprecated because of a potential security risk.
20
+
21
+ The `google.auth.load_credentials_from_file` method does not validate the credential
22
+ configuration. The security risk occurs when a credential configuration is accepted
23
+ from a source that is not under your control and used without validation on your side.
24
+
25
+ If you know that you will be loading credential configurations of a
26
+ specific type, it is recommended to use a credential-type-specific
27
+ load method.
28
+
29
+ This will ensure that an unexpected credential type with potential for
30
+ malicious intent is not loaded unintentionally. You might still have to do
31
+ validation for certain credential types. Please follow the recommendations
32
+ for that method. For example, if you want to load only service accounts,
33
+ you can create the service account credentials explicitly:
34
+
35
+ ```
36
+ from google.cloud.vision_v1 import ImageAnnotatorClient
37
+ from google.oauth2 import service_account
38
+
39
+ credentials = service_account.Credentials.from_service_account_file(filename)
40
+ client = ImageAnnotatorClient(credentials=credentials)
41
+ ```
42
+
43
+ If you are loading your credential configuration from an untrusted source and have
44
+ not mitigated the risks (e.g. by validating the configuration yourself), make
45
+ these changes as soon as possible to prevent security risks to your environment.
46
+
47
+ Regardless of the method used, it is always your responsibility to validate
48
+ configurations received from external sources.
49
+
50
+ Refer to https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
51
+ for more details.
52
+ """
py311/lib/python3.11/site-packages/google/api_core/grpc_helpers.py ADDED
@@ -0,0 +1,649 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for :mod:`grpc`."""
16
+ import collections
17
+ import functools
18
+ from typing import Generic, Iterator, Optional, TypeVar
19
+ import warnings
20
+
21
+ import google.auth
22
+ import google.auth.credentials
23
+ import google.auth.transport.grpc
24
+ import google.auth.transport.requests
25
+ import google.protobuf
26
+ import grpc
27
+
28
+ from google.api_core import exceptions, general_helpers
29
+
30
+ PROTOBUF_VERSION = google.protobuf.__version__
31
+
32
+ # The grpcio-gcp package only has support for protobuf < 4
33
+ if PROTOBUF_VERSION[0:2] == "3.": # pragma: NO COVER
34
+ try:
35
+ import grpc_gcp
36
+
37
+ warnings.warn(
38
+ """Support for grpcio-gcp is deprecated. This feature will be
39
+ removed from `google-api-core` after January 1, 2024. If you need to
40
+ continue to use this feature, please pin to a specific version of
41
+ `google-api-core`.""",
42
+ DeprecationWarning,
43
+ )
44
+ HAS_GRPC_GCP = True
45
+ except ImportError:
46
+ HAS_GRPC_GCP = False
47
+ else:
48
+ HAS_GRPC_GCP = False
49
+
50
+
51
+ # The list of gRPC Callable interfaces that return iterators.
52
+ _STREAM_WRAP_CLASSES = (grpc.UnaryStreamMultiCallable, grpc.StreamStreamMultiCallable)
53
+
54
+ # denotes the proto response type for grpc calls
55
+ P = TypeVar("P")
56
+
57
+
58
+ def _patch_callable_name(callable_):
59
+ """Fix-up gRPC callable attributes.
60
+
61
+ gRPC callable lack the ``__name__`` attribute which causes
62
+ :func:`functools.wraps` to error. This adds the attribute if needed.
63
+ """
64
+ if not hasattr(callable_, "__name__"):
65
+ callable_.__name__ = callable_.__class__.__name__
66
+
67
+
68
+ def _wrap_unary_errors(callable_):
69
+ """Map errors for Unary-Unary and Stream-Unary gRPC callables."""
70
+ _patch_callable_name(callable_)
71
+
72
+ @functools.wraps(callable_)
73
+ def error_remapped_callable(*args, **kwargs):
74
+ try:
75
+ return callable_(*args, **kwargs)
76
+ except grpc.RpcError as exc:
77
+ raise exceptions.from_grpc_error(exc) from exc
78
+
79
+ return error_remapped_callable
80
+
81
+
82
+ class _StreamingResponseIterator(Generic[P], grpc.Call):
83
+ def __init__(self, wrapped, prefetch_first_result=True):
84
+ self._wrapped = wrapped
85
+
86
+ # This iterator is used in a retry context, and returned outside after init.
87
+ # gRPC will not throw an exception until the stream is consumed, so we need
88
+ # to retrieve the first result, in order to fail, in order to trigger a retry.
89
+ try:
90
+ if prefetch_first_result:
91
+ self._stored_first_result = next(self._wrapped)
92
+ except TypeError:
93
+ # It is possible the wrapped method isn't an iterable (a grpc.Call
94
+ # for instance). If this happens don't store the first result.
95
+ pass
96
+ except StopIteration:
97
+ # ignore stop iteration at this time. This should be handled outside of retry.
98
+ pass
99
+
100
+ def __iter__(self) -> Iterator[P]:
101
+ """This iterator is also an iterable that returns itself."""
102
+ return self
103
+
104
+ def __next__(self) -> P:
105
+ """Get the next response from the stream.
106
+
107
+ Returns:
108
+ protobuf.Message: A single response from the stream.
109
+ """
110
+ try:
111
+ if hasattr(self, "_stored_first_result"):
112
+ result = self._stored_first_result
113
+ del self._stored_first_result
114
+ return result
115
+ return next(self._wrapped)
116
+ except grpc.RpcError as exc:
117
+ # If the stream has already returned data, we cannot recover here.
118
+ raise exceptions.from_grpc_error(exc) from exc
119
+
120
+ # grpc.Call & grpc.RpcContext interface
121
+
122
+ def add_callback(self, callback):
123
+ return self._wrapped.add_callback(callback)
124
+
125
+ def cancel(self):
126
+ return self._wrapped.cancel()
127
+
128
+ def code(self):
129
+ return self._wrapped.code()
130
+
131
+ def details(self):
132
+ return self._wrapped.details()
133
+
134
+ def initial_metadata(self):
135
+ return self._wrapped.initial_metadata()
136
+
137
+ def is_active(self):
138
+ return self._wrapped.is_active()
139
+
140
+ def time_remaining(self):
141
+ return self._wrapped.time_remaining()
142
+
143
+ def trailing_metadata(self):
144
+ return self._wrapped.trailing_metadata()
145
+
146
+
147
+ # public type alias denoting the return type of streaming gapic calls
148
+ GrpcStream = _StreamingResponseIterator[P]
149
+
150
+
151
+ def _wrap_stream_errors(callable_):
152
+ """Wrap errors for Unary-Stream and Stream-Stream gRPC callables.
153
+
154
+ The callables that return iterators require a bit more logic to re-map
155
+ errors when iterating. This wraps both the initial invocation and the
156
+ iterator of the return value to re-map errors.
157
+ """
158
+ _patch_callable_name(callable_)
159
+
160
+ @functools.wraps(callable_)
161
+ def error_remapped_callable(*args, **kwargs):
162
+ try:
163
+ result = callable_(*args, **kwargs)
164
+ # Auto-fetching the first result causes PubSub client's streaming pull
165
+ # to hang when re-opening the stream, thus we need examine the hacky
166
+ # hidden flag to see if pre-fetching is disabled.
167
+ # https://github.com/googleapis/python-pubsub/issues/93#issuecomment-630762257
168
+ prefetch_first = getattr(callable_, "_prefetch_first_result_", True)
169
+ return _StreamingResponseIterator(
170
+ result, prefetch_first_result=prefetch_first
171
+ )
172
+ except grpc.RpcError as exc:
173
+ raise exceptions.from_grpc_error(exc) from exc
174
+
175
+ return error_remapped_callable
176
+
177
+
178
+ def wrap_errors(callable_):
179
+ """Wrap a gRPC callable and map :class:`grpc.RpcErrors` to friendly error
180
+ classes.
181
+
182
+ Errors raised by the gRPC callable are mapped to the appropriate
183
+ :class:`google.api_core.exceptions.GoogleAPICallError` subclasses.
184
+ The original `grpc.RpcError` (which is usually also a `grpc.Call`) is
185
+ available from the ``response`` property on the mapped exception. This
186
+ is useful for extracting metadata from the original error.
187
+
188
+ Args:
189
+ callable_ (Callable): A gRPC callable.
190
+
191
+ Returns:
192
+ Callable: The wrapped gRPC callable.
193
+ """
194
+ if isinstance(callable_, _STREAM_WRAP_CLASSES):
195
+ return _wrap_stream_errors(callable_)
196
+ else:
197
+ return _wrap_unary_errors(callable_)
198
+
199
+
200
+ def _create_composite_credentials(
201
+ credentials=None,
202
+ credentials_file=None,
203
+ default_scopes=None,
204
+ scopes=None,
205
+ ssl_credentials=None,
206
+ quota_project_id=None,
207
+ default_host=None,
208
+ ):
209
+ """Create the composite credentials for secure channels.
210
+
211
+ Args:
212
+ credentials (google.auth.credentials.Credentials): The credentials. If
213
+ not specified, then this function will attempt to ascertain the
214
+ credentials from the environment using :func:`google.auth.default`.
215
+ credentials_file (str): Deprecated. A file with credentials that can be loaded with
216
+ :func:`google.auth.load_credentials_from_file`. This argument is
217
+ mutually exclusive with credentials. This argument will be
218
+ removed in the next major version of `google-api-core`.
219
+
220
+ .. warning::
221
+ Important: If you accept a credential configuration (credential JSON/File/Stream)
222
+ from an external source for authentication to Google Cloud Platform, you must
223
+ validate it before providing it to any Google API or client library. Providing an
224
+ unvalidated credential configuration to Google APIs or libraries can compromise
225
+ the security of your systems and data. For more information, refer to
226
+ `Validate credential configurations from external sources`_.
227
+
228
+ .. _Validate credential configurations from external sources:
229
+
230
+ https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
231
+ default_scopes (Sequence[str]): A optional list of scopes needed for this
232
+ service. These are only used when credentials are not specified and
233
+ are passed to :func:`google.auth.default`.
234
+ scopes (Sequence[str]): A optional list of scopes needed for this
235
+ service. These are only used when credentials are not specified and
236
+ are passed to :func:`google.auth.default`.
237
+ ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
238
+ credentials. This can be used to specify different certificates.
239
+ quota_project_id (str): An optional project to use for billing and quota.
240
+ default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
241
+
242
+ Returns:
243
+ grpc.ChannelCredentials: The composed channel credentials object.
244
+
245
+ Raises:
246
+ google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
247
+ """
248
+ if credentials_file is not None:
249
+ warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)
250
+
251
+ if credentials and credentials_file:
252
+ raise exceptions.DuplicateCredentialArgs(
253
+ "'credentials' and 'credentials_file' are mutually exclusive."
254
+ )
255
+
256
+ if credentials_file:
257
+ credentials, _ = google.auth.load_credentials_from_file(
258
+ credentials_file, scopes=scopes, default_scopes=default_scopes
259
+ )
260
+ elif credentials:
261
+ credentials = google.auth.credentials.with_scopes_if_required(
262
+ credentials, scopes=scopes, default_scopes=default_scopes
263
+ )
264
+ else:
265
+ credentials, _ = google.auth.default(
266
+ scopes=scopes, default_scopes=default_scopes
267
+ )
268
+
269
+ if quota_project_id and isinstance(
270
+ credentials, google.auth.credentials.CredentialsWithQuotaProject
271
+ ):
272
+ credentials = credentials.with_quota_project(quota_project_id)
273
+
274
+ request = google.auth.transport.requests.Request()
275
+
276
+ # Create the metadata plugin for inserting the authorization header.
277
+ metadata_plugin = google.auth.transport.grpc.AuthMetadataPlugin(
278
+ credentials,
279
+ request,
280
+ default_host=default_host,
281
+ )
282
+
283
+ # Create a set of grpc.CallCredentials using the metadata plugin.
284
+ google_auth_credentials = grpc.metadata_call_credentials(metadata_plugin)
285
+
286
+ # if `ssl_credentials` is set, use `grpc.composite_channel_credentials` instead of
287
+ # `grpc.compute_engine_channel_credentials` as the former supports passing
288
+ # `ssl_credentials` via `channel_credentials` which is needed for mTLS.
289
+ if ssl_credentials:
290
+ # Combine the ssl credentials and the authorization credentials.
291
+ # See https://grpc.github.io/grpc/python/grpc.html#grpc.composite_channel_credentials
292
+ return grpc.composite_channel_credentials(
293
+ ssl_credentials, google_auth_credentials
294
+ )
295
+ else:
296
+ # Use grpc.compute_engine_channel_credentials in order to support Direct Path.
297
+ # See https://grpc.github.io/grpc/python/grpc.html#grpc.compute_engine_channel_credentials
298
+ # TODO(https://github.com/googleapis/python-api-core/issues/598):
299
+ # Although `grpc.compute_engine_channel_credentials` returns channel credentials
300
+ # outside of a Google Compute Engine environment (GCE), we should determine if
301
+ # there is a way to reliably detect a GCE environment so that
302
+ # `grpc.compute_engine_channel_credentials` is not called outside of GCE.
303
+ return grpc.compute_engine_channel_credentials(google_auth_credentials)
304
+
305
+
306
+ def create_channel(
307
+ target,
308
+ credentials=None,
309
+ scopes=None,
310
+ ssl_credentials=None,
311
+ credentials_file=None,
312
+ quota_project_id=None,
313
+ default_scopes=None,
314
+ default_host=None,
315
+ compression=None,
316
+ attempt_direct_path: Optional[bool] = False,
317
+ **kwargs,
318
+ ):
319
+ """Create a secure channel with credentials.
320
+
321
+ Args:
322
+ target (str): The target service address in the format 'hostname:port'.
323
+ credentials (google.auth.credentials.Credentials): The credentials. If
324
+ not specified, then this function will attempt to ascertain the
325
+ credentials from the environment using :func:`google.auth.default`.
326
+ scopes (Sequence[str]): A optional list of scopes needed for this
327
+ service. These are only used when credentials are not specified and
328
+ are passed to :func:`google.auth.default`.
329
+ ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
330
+ credentials. This can be used to specify different certificates.
331
+ credentials_file (str): A file with credentials that can be loaded with
332
+ :func:`google.auth.load_credentials_from_file`. This argument is
333
+ mutually exclusive with credentials.
334
+
335
+ .. warning::
336
+ Important: If you accept a credential configuration (credential JSON/File/Stream)
337
+ from an external source for authentication to Google Cloud Platform, you must
338
+ validate it before providing it to any Google API or client library. Providing an
339
+ unvalidated credential configuration to Google APIs or libraries can compromise
340
+ the security of your systems and data. For more information, refer to
341
+ `Validate credential configurations from external sources`_.
342
+
343
+ .. _Validate credential configurations from external sources:
344
+
345
+ https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
346
+ quota_project_id (str): An optional project to use for billing and quota.
347
+ default_scopes (Sequence[str]): Default scopes passed by a Google client
348
+ library. Use 'scopes' for user-defined scopes.
349
+ default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
350
+ compression (grpc.Compression): An optional value indicating the
351
+ compression method to be used over the lifetime of the channel.
352
+ attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted
353
+ when the request is made. Direct Path is only available within a Google
354
+ Compute Engine (GCE) environment and provides a proxyless connection
355
+ which increases the available throughput, reduces latency, and increases
356
+ reliability. Note:
357
+
358
+ - This argument should only be set in a GCE environment and for Services
359
+ that are known to support Direct Path.
360
+ - If this argument is set outside of GCE, then this request will fail
361
+ unless the back-end service happens to have configured fall-back to DNS.
362
+ - If the request causes a `ServiceUnavailable` response, it is recommended
363
+ that the client repeat the request with `attempt_direct_path` set to
364
+ `False` as the Service may not support Direct Path.
365
+ - Using `ssl_credentials` with `attempt_direct_path` set to `True` will
366
+ result in `ValueError` as this combination is not yet supported.
367
+
368
+ kwargs: Additional key-word args passed to
369
+ :func:`grpc_gcp.secure_channel` or :func:`grpc.secure_channel`.
370
+ Note: `grpc_gcp` is only supported in environments with protobuf < 4.0.0.
371
+
372
+ Returns:
373
+ grpc.Channel: The created channel.
374
+
375
+ Raises:
376
+ google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
377
+ ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
378
+ """
379
+
380
+ # If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
381
+ # raise ValueError as this is not yet supported.
382
+ # See https://github.com/googleapis/python-api-core/issues/590
383
+ if ssl_credentials and attempt_direct_path:
384
+ raise ValueError("Using ssl_credentials with Direct Path is not supported")
385
+
386
+ composite_credentials = _create_composite_credentials(
387
+ credentials=credentials,
388
+ credentials_file=credentials_file,
389
+ default_scopes=default_scopes,
390
+ scopes=scopes,
391
+ ssl_credentials=ssl_credentials,
392
+ quota_project_id=quota_project_id,
393
+ default_host=default_host,
394
+ )
395
+
396
+ # Note that grpcio-gcp is deprecated
397
+ if HAS_GRPC_GCP: # pragma: NO COVER
398
+ if compression is not None and compression != grpc.Compression.NoCompression:
399
+ warnings.warn(
400
+ "The `compression` argument is ignored for grpc_gcp.secure_channel creation.",
401
+ DeprecationWarning,
402
+ )
403
+ if attempt_direct_path:
404
+ warnings.warn(
405
+ """The `attempt_direct_path` argument is ignored for grpc_gcp.secure_channel creation.""",
406
+ DeprecationWarning,
407
+ )
408
+ return grpc_gcp.secure_channel(target, composite_credentials, **kwargs)
409
+
410
+ if attempt_direct_path:
411
+ target = _modify_target_for_direct_path(target)
412
+
413
+ return grpc.secure_channel(
414
+ target, composite_credentials, compression=compression, **kwargs
415
+ )
416
+
417
+
418
+ def _modify_target_for_direct_path(target: str) -> str:
419
+ """
420
+ Given a target, return a modified version which is compatible with Direct Path.
421
+
422
+ Args:
423
+ target (str): The target service address in the format 'hostname[:port]' or
424
+ 'dns://hostname[:port]'.
425
+
426
+ Returns:
427
+ target (str): The target service address which is converted into a format compatible with Direct Path.
428
+ If the target contains `dns:///` or does not contain `:///`, the target will be converted in
429
+ a format compatible with Direct Path; otherwise the original target will be returned as the
430
+ original target may already denote Direct Path.
431
+ """
432
+
433
+ # A DNS prefix may be included with the target to indicate the endpoint is living in the Internet,
434
+ # outside of Google Cloud Platform.
435
+ dns_prefix = "dns:///"
436
+ # Remove "dns:///" if `attempt_direct_path` is set to True as
437
+ # the Direct Path prefix `google-c2p:///` will be used instead.
438
+ target = target.replace(dns_prefix, "")
439
+
440
+ direct_path_separator = ":///"
441
+ if direct_path_separator not in target:
442
+ target_without_port = target.split(":")[0]
443
+ # Modify the target to use Direct Path by adding the `google-c2p:///` prefix
444
+ target = f"google-c2p{direct_path_separator}{target_without_port}"
445
+ return target
446
+
447
+
448
+ _MethodCall = collections.namedtuple(
449
+ "_MethodCall", ("request", "timeout", "metadata", "credentials", "compression")
450
+ )
451
+
452
+ _ChannelRequest = collections.namedtuple("_ChannelRequest", ("method", "request"))
453
+
454
+
455
+ class _CallableStub(object):
456
+ """Stub for the grpc.*MultiCallable interfaces."""
457
+
458
+ def __init__(self, method, channel):
459
+ self._method = method
460
+ self._channel = channel
461
+ self.response = None
462
+ """Union[protobuf.Message, Callable[protobuf.Message], exception]:
463
+ The response to give when invoking this callable. If this is a
464
+ callable, it will be invoked with the request protobuf. If it's an
465
+ exception, the exception will be raised when this is invoked.
466
+ """
467
+ self.responses = None
468
+ """Iterator[
469
+ Union[protobuf.Message, Callable[protobuf.Message], exception]]:
470
+ An iterator of responses. If specified, self.response will be populated
471
+ on each invocation by calling ``next(self.responses)``."""
472
+ self.requests = []
473
+ """List[protobuf.Message]: All requests sent to this callable."""
474
+ self.calls = []
475
+ """List[Tuple]: All invocations of this callable. Each tuple is the
476
+ request, timeout, metadata, compression, and credentials."""
477
+
478
+ def __call__(
479
+ self, request, timeout=None, metadata=None, credentials=None, compression=None
480
+ ):
481
+ self._channel.requests.append(_ChannelRequest(self._method, request))
482
+ self.calls.append(
483
+ _MethodCall(request, timeout, metadata, credentials, compression)
484
+ )
485
+ self.requests.append(request)
486
+
487
+ response = self.response
488
+ if self.responses is not None:
489
+ if response is None:
490
+ response = next(self.responses)
491
+ else:
492
+ raise ValueError(
493
+ "{method}.response and {method}.responses are mutually "
494
+ "exclusive.".format(method=self._method)
495
+ )
496
+
497
+ if callable(response):
498
+ return response(request)
499
+
500
+ if isinstance(response, Exception):
501
+ raise response
502
+
503
+ if response is not None:
504
+ return response
505
+
506
+ raise ValueError('Method stub for "{}" has no response.'.format(self._method))
507
+
508
+
509
+ def _simplify_method_name(method):
510
+ """Simplifies a gRPC method name.
511
+
512
+ When gRPC invokes the channel to create a callable, it gives a full
513
+ method name like "/google.pubsub.v1.Publisher/CreateTopic". This
514
+ returns just the name of the method, in this case "CreateTopic".
515
+
516
+ Args:
517
+ method (str): The name of the method.
518
+
519
+ Returns:
520
+ str: The simplified name of the method.
521
+ """
522
+ return method.rsplit("/", 1).pop()
523
+
524
+
525
+ class ChannelStub(grpc.Channel):
526
+ """A testing stub for the grpc.Channel interface.
527
+
528
+ This can be used to test any client that eventually uses a gRPC channel
529
+ to communicate. By passing in a channel stub, you can configure which
530
+ responses are returned and track which requests are made.
531
+
532
+ For example:
533
+
534
+ .. code-block:: python
535
+
536
+ channel_stub = grpc_helpers.ChannelStub()
537
+ client = FooClient(channel=channel_stub)
538
+
539
+ channel_stub.GetFoo.response = foo_pb2.Foo(name='bar')
540
+
541
+ foo = client.get_foo(labels=['baz'])
542
+
543
+ assert foo.name == 'bar'
544
+ assert channel_stub.GetFoo.requests[0].labels = ['baz']
545
+
546
+ Each method on the stub can be accessed and configured on the channel.
547
+ Here's some examples of various configurations:
548
+
549
+ .. code-block:: python
550
+
551
+ # Return a basic response:
552
+
553
+ channel_stub.GetFoo.response = foo_pb2.Foo(name='bar')
554
+ assert client.get_foo().name == 'bar'
555
+
556
+ # Raise an exception:
557
+ channel_stub.GetFoo.response = NotFound('...')
558
+
559
+ with pytest.raises(NotFound):
560
+ client.get_foo()
561
+
562
+ # Use a sequence of responses:
563
+ channel_stub.GetFoo.responses = iter([
564
+ foo_pb2.Foo(name='bar'),
565
+ foo_pb2.Foo(name='baz'),
566
+ ])
567
+
568
+ assert client.get_foo().name == 'bar'
569
+ assert client.get_foo().name == 'baz'
570
+
571
+ # Use a callable
572
+
573
+ def on_get_foo(request):
574
+ return foo_pb2.Foo(name='bar' + request.id)
575
+
576
+ channel_stub.GetFoo.response = on_get_foo
577
+
578
+ assert client.get_foo(id='123').name == 'bar123'
579
+ """
580
+
581
+ def __init__(self, responses=[]):
582
+ self.requests = []
583
+ """Sequence[Tuple[str, protobuf.Message]]: A list of all requests made
584
+ on this channel in order. The tuple is of method name, request
585
+ message."""
586
+ self._method_stubs = {}
587
+
588
+ def _stub_for_method(self, method):
589
+ method = _simplify_method_name(method)
590
+ self._method_stubs[method] = _CallableStub(method, self)
591
+ return self._method_stubs[method]
592
+
593
+ def __getattr__(self, key):
594
+ try:
595
+ return self._method_stubs[key]
596
+ except KeyError:
597
+ raise AttributeError
598
+
599
+ def unary_unary(
600
+ self,
601
+ method,
602
+ request_serializer=None,
603
+ response_deserializer=None,
604
+ _registered_method=False,
605
+ ):
606
+ """grpc.Channel.unary_unary implementation."""
607
+ return self._stub_for_method(method)
608
+
609
+ def unary_stream(
610
+ self,
611
+ method,
612
+ request_serializer=None,
613
+ response_deserializer=None,
614
+ _registered_method=False,
615
+ ):
616
+ """grpc.Channel.unary_stream implementation."""
617
+ return self._stub_for_method(method)
618
+
619
+ def stream_unary(
620
+ self,
621
+ method,
622
+ request_serializer=None,
623
+ response_deserializer=None,
624
+ _registered_method=False,
625
+ ):
626
+ """grpc.Channel.stream_unary implementation."""
627
+ return self._stub_for_method(method)
628
+
629
+ def stream_stream(
630
+ self,
631
+ method,
632
+ request_serializer=None,
633
+ response_deserializer=None,
634
+ _registered_method=False,
635
+ ):
636
+ """grpc.Channel.stream_stream implementation."""
637
+ return self._stub_for_method(method)
638
+
639
+ def subscribe(self, callback, try_to_connect=False):
640
+ """grpc.Channel.subscribe implementation."""
641
+ pass
642
+
643
+ def unsubscribe(self, callback):
644
+ """grpc.Channel.unsubscribe implementation."""
645
+ pass
646
+
647
+ def close(self):
648
+ """grpc.Channel.close implementation."""
649
+ pass
py311/lib/python3.11/site-packages/google/api_core/grpc_helpers_async.py ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """AsyncIO helpers for :mod:`grpc` supporting 3.7+.
16
+
17
+ Please combine more detailed docstring in grpc_helpers.py to use following
18
+ functions. This module is implementing the same surface with AsyncIO semantics.
19
+ """
20
+
21
+ import asyncio
22
+ import functools
23
+ import warnings
24
+
25
+ from typing import AsyncGenerator, Generic, Iterator, Optional, TypeVar
26
+
27
+ import grpc
28
+ from grpc import aio
29
+
30
+ from google.api_core import exceptions, general_helpers, grpc_helpers
31
+
32
+ # denotes the proto response type for grpc calls
33
+ P = TypeVar("P")
34
+
35
+ # NOTE(lidiz) Alternatively, we can hack "__getattribute__" to perform
36
+ # automatic patching for us. But that means the overhead of creating an
37
+ # extra Python function spreads to every single send and receive.
38
+
39
+
40
+ class _WrappedCall(aio.Call):
41
+ def __init__(self):
42
+ self._call = None
43
+
44
+ def with_call(self, call):
45
+ """Supplies the call object separately to keep __init__ clean."""
46
+ self._call = call
47
+ return self
48
+
49
+ async def initial_metadata(self):
50
+ return await self._call.initial_metadata()
51
+
52
+ async def trailing_metadata(self):
53
+ return await self._call.trailing_metadata()
54
+
55
+ async def code(self):
56
+ return await self._call.code()
57
+
58
+ async def details(self):
59
+ return await self._call.details()
60
+
61
+ def cancelled(self):
62
+ return self._call.cancelled()
63
+
64
+ def done(self):
65
+ return self._call.done()
66
+
67
+ def time_remaining(self):
68
+ return self._call.time_remaining()
69
+
70
+ def cancel(self):
71
+ return self._call.cancel()
72
+
73
+ def add_done_callback(self, callback):
74
+ self._call.add_done_callback(callback)
75
+
76
+ async def wait_for_connection(self):
77
+ try:
78
+ await self._call.wait_for_connection()
79
+ except grpc.RpcError as rpc_error:
80
+ raise exceptions.from_grpc_error(rpc_error) from rpc_error
81
+
82
+
83
+ class _WrappedUnaryResponseMixin(Generic[P], _WrappedCall):
84
+ def __await__(self) -> Iterator[P]:
85
+ try:
86
+ response = yield from self._call.__await__()
87
+ return response
88
+ except grpc.RpcError as rpc_error:
89
+ raise exceptions.from_grpc_error(rpc_error) from rpc_error
90
+
91
+
92
+ class _WrappedStreamResponseMixin(Generic[P], _WrappedCall):
93
+ def __init__(self):
94
+ self._wrapped_async_generator = None
95
+
96
+ async def read(self) -> P:
97
+ try:
98
+ return await self._call.read()
99
+ except grpc.RpcError as rpc_error:
100
+ raise exceptions.from_grpc_error(rpc_error) from rpc_error
101
+
102
+ async def _wrapped_aiter(self) -> AsyncGenerator[P, None]:
103
+ try:
104
+ # NOTE(lidiz) coverage doesn't understand the exception raised from
105
+ # __anext__ method. It is covered by test case:
106
+ # test_wrap_stream_errors_aiter_non_rpc_error
107
+ async for response in self._call: # pragma: no branch
108
+ yield response
109
+ except grpc.RpcError as rpc_error:
110
+ raise exceptions.from_grpc_error(rpc_error) from rpc_error
111
+
112
+ def __aiter__(self) -> AsyncGenerator[P, None]:
113
+ if not self._wrapped_async_generator:
114
+ self._wrapped_async_generator = self._wrapped_aiter()
115
+ return self._wrapped_async_generator
116
+
117
+
118
+ class _WrappedStreamRequestMixin(_WrappedCall):
119
+ async def write(self, request):
120
+ try:
121
+ await self._call.write(request)
122
+ except grpc.RpcError as rpc_error:
123
+ raise exceptions.from_grpc_error(rpc_error) from rpc_error
124
+
125
+ async def done_writing(self):
126
+ try:
127
+ await self._call.done_writing()
128
+ except grpc.RpcError as rpc_error:
129
+ raise exceptions.from_grpc_error(rpc_error) from rpc_error
130
+
131
+
132
+ # NOTE(lidiz) Implementing each individual class separately, so we don't
133
+ # expose any API that should not be seen. E.g., __aiter__ in unary-unary
134
+ # RPC, or __await__ in stream-stream RPC.
135
+ class _WrappedUnaryUnaryCall(_WrappedUnaryResponseMixin[P], aio.UnaryUnaryCall):
136
+ """Wrapped UnaryUnaryCall to map exceptions."""
137
+
138
+
139
+ class _WrappedUnaryStreamCall(_WrappedStreamResponseMixin[P], aio.UnaryStreamCall):
140
+ """Wrapped UnaryStreamCall to map exceptions."""
141
+
142
+
143
+ class _WrappedStreamUnaryCall(
144
+ _WrappedUnaryResponseMixin[P], _WrappedStreamRequestMixin, aio.StreamUnaryCall
145
+ ):
146
+ """Wrapped StreamUnaryCall to map exceptions."""
147
+
148
+
149
+ class _WrappedStreamStreamCall(
150
+ _WrappedStreamRequestMixin, _WrappedStreamResponseMixin[P], aio.StreamStreamCall
151
+ ):
152
+ """Wrapped StreamStreamCall to map exceptions."""
153
+
154
+
155
+ # public type alias denoting the return type of async streaming gapic calls
156
+ GrpcAsyncStream = _WrappedStreamResponseMixin
157
+ # public type alias denoting the return type of unary gapic calls
158
+ AwaitableGrpcCall = _WrappedUnaryResponseMixin
159
+
160
+
161
+ def _wrap_unary_errors(callable_):
162
+ """Map errors for Unary-Unary async callables."""
163
+
164
+ @functools.wraps(callable_)
165
+ def error_remapped_callable(*args, **kwargs):
166
+ call = callable_(*args, **kwargs)
167
+ return _WrappedUnaryUnaryCall().with_call(call)
168
+
169
+ return error_remapped_callable
170
+
171
+
172
+ def _wrap_stream_errors(callable_, wrapper_type):
173
+ """Map errors for streaming RPC async callables."""
174
+
175
+ @functools.wraps(callable_)
176
+ async def error_remapped_callable(*args, **kwargs):
177
+ call = callable_(*args, **kwargs)
178
+ call = wrapper_type().with_call(call)
179
+ await call.wait_for_connection()
180
+ return call
181
+
182
+ return error_remapped_callable
183
+
184
+
185
+ def wrap_errors(callable_):
186
+ """Wrap a gRPC async callable and map :class:`grpc.RpcErrors` to
187
+ friendly error classes.
188
+
189
+ Errors raised by the gRPC callable are mapped to the appropriate
190
+ :class:`google.api_core.exceptions.GoogleAPICallError` subclasses. The
191
+ original `grpc.RpcError` (which is usually also a `grpc.Call`) is
192
+ available from the ``response`` property on the mapped exception. This
193
+ is useful for extracting metadata from the original error.
194
+
195
+ Args:
196
+ callable_ (Callable): A gRPC callable.
197
+
198
+ Returns: Callable: The wrapped gRPC callable.
199
+ """
200
+ grpc_helpers._patch_callable_name(callable_)
201
+
202
+ if isinstance(callable_, aio.UnaryStreamMultiCallable):
203
+ return _wrap_stream_errors(callable_, _WrappedUnaryStreamCall)
204
+ elif isinstance(callable_, aio.StreamUnaryMultiCallable):
205
+ return _wrap_stream_errors(callable_, _WrappedStreamUnaryCall)
206
+ elif isinstance(callable_, aio.StreamStreamMultiCallable):
207
+ return _wrap_stream_errors(callable_, _WrappedStreamStreamCall)
208
+ else:
209
+ return _wrap_unary_errors(callable_)
210
+
211
+
212
+ def create_channel(
213
+ target,
214
+ credentials=None,
215
+ scopes=None,
216
+ ssl_credentials=None,
217
+ credentials_file=None,
218
+ quota_project_id=None,
219
+ default_scopes=None,
220
+ default_host=None,
221
+ compression=None,
222
+ attempt_direct_path: Optional[bool] = False,
223
+ **kwargs,
224
+ ):
225
+ """Create an AsyncIO secure channel with credentials.
226
+
227
+ Args:
228
+ target (str): The target service address in the format 'hostname:port'.
229
+ credentials (google.auth.credentials.Credentials): The credentials. If
230
+ not specified, then this function will attempt to ascertain the
231
+ credentials from the environment using :func:`google.auth.default`.
232
+ scopes (Sequence[str]): A optional list of scopes needed for this
233
+ service. These are only used when credentials are not specified and
234
+ are passed to :func:`google.auth.default`.
235
+ ssl_credentials (grpc.ChannelCredentials): Optional SSL channel
236
+ credentials. This can be used to specify different certificates.
237
+ credentials_file (str): Deprecated. A file with credentials that can be loaded with
238
+ :func:`google.auth.load_credentials_from_file`. This argument is
239
+ mutually exclusive with credentials. This argument will be
240
+ removed in the next major version of `google-api-core`.
241
+
242
+ .. warning::
243
+ Important: If you accept a credential configuration (credential JSON/File/Stream)
244
+ from an external source for authentication to Google Cloud Platform, you must
245
+ validate it before providing it to any Google API or client library. Providing an
246
+ unvalidated credential configuration to Google APIs or libraries can compromise
247
+ the security of your systems and data. For more information, refer to
248
+ `Validate credential configurations from external sources`_.
249
+
250
+ .. _Validate credential configurations from external sources:
251
+
252
+ https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
253
+ quota_project_id (str): An optional project to use for billing and quota.
254
+ default_scopes (Sequence[str]): Default scopes passed by a Google client
255
+ library. Use 'scopes' for user-defined scopes.
256
+ default_host (str): The default endpoint. e.g., "pubsub.googleapis.com".
257
+ compression (grpc.Compression): An optional value indicating the
258
+ compression method to be used over the lifetime of the channel.
259
+ attempt_direct_path (Optional[bool]): If set, Direct Path will be attempted
260
+ when the request is made. Direct Path is only available within a Google
261
+ Compute Engine (GCE) environment and provides a proxyless connection
262
+ which increases the available throughput, reduces latency, and increases
263
+ reliability. Note:
264
+
265
+ - This argument should only be set in a GCE environment and for Services
266
+ that are known to support Direct Path.
267
+ - If this argument is set outside of GCE, then this request will fail
268
+ unless the back-end service happens to have configured fall-back to DNS.
269
+ - If the request causes a `ServiceUnavailable` response, it is recommended
270
+ that the client repeat the request with `attempt_direct_path` set to
271
+ `False` as the Service may not support Direct Path.
272
+ - Using `ssl_credentials` with `attempt_direct_path` set to `True` will
273
+ result in `ValueError` as this combination is not yet supported.
274
+
275
+ kwargs: Additional key-word args passed to :func:`aio.secure_channel`.
276
+
277
+ Returns:
278
+ aio.Channel: The created channel.
279
+
280
+ Raises:
281
+ google.api_core.DuplicateCredentialArgs: If both a credentials object and credentials_file are passed.
282
+ ValueError: If `ssl_credentials` is set and `attempt_direct_path` is set to `True`.
283
+ """
284
+
285
+ if credentials_file is not None:
286
+ warnings.warn(general_helpers._CREDENTIALS_FILE_WARNING, DeprecationWarning)
287
+
288
+ # If `ssl_credentials` is set and `attempt_direct_path` is set to `True`,
289
+ # raise ValueError as this is not yet supported.
290
+ # See https://github.com/googleapis/python-api-core/issues/590
291
+ if ssl_credentials and attempt_direct_path:
292
+ raise ValueError("Using ssl_credentials with Direct Path is not supported")
293
+
294
+ composite_credentials = grpc_helpers._create_composite_credentials(
295
+ credentials=credentials,
296
+ credentials_file=credentials_file,
297
+ scopes=scopes,
298
+ default_scopes=default_scopes,
299
+ ssl_credentials=ssl_credentials,
300
+ quota_project_id=quota_project_id,
301
+ default_host=default_host,
302
+ )
303
+
304
+ if attempt_direct_path:
305
+ target = grpc_helpers._modify_target_for_direct_path(target)
306
+
307
+ return aio.secure_channel(
308
+ target, composite_credentials, compression=compression, **kwargs
309
+ )
310
+
311
+
312
+ class FakeUnaryUnaryCall(_WrappedUnaryUnaryCall):
313
+ """Fake implementation for unary-unary RPCs.
314
+
315
+ It is a dummy object for response message. Supply the intended response
316
+ upon the initialization, and the coroutine will return the exact response
317
+ message.
318
+ """
319
+
320
+ def __init__(self, response=object()):
321
+ self.response = response
322
+ self._future = asyncio.get_event_loop().create_future()
323
+ self._future.set_result(self.response)
324
+
325
+ def __await__(self):
326
+ response = yield from self._future.__await__()
327
+ return response
328
+
329
+
330
+ class FakeStreamUnaryCall(_WrappedStreamUnaryCall):
331
+ """Fake implementation for stream-unary RPCs.
332
+
333
+ It is a dummy object for response message. Supply the intended response
334
+ upon the initialization, and the coroutine will return the exact response
335
+ message.
336
+ """
337
+
338
+ def __init__(self, response=object()):
339
+ self.response = response
340
+ self._future = asyncio.get_event_loop().create_future()
341
+ self._future.set_result(self.response)
342
+
343
+ def __await__(self):
344
+ response = yield from self._future.__await__()
345
+ return response
346
+
347
+ async def wait_for_connection(self):
348
+ pass
py311/lib/python3.11/site-packages/google/api_core/iam.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """Non-API-specific IAM policy definitions
15
+
16
+ For allowed roles / permissions, see:
17
+ https://cloud.google.com/iam/docs/understanding-roles
18
+
19
+ Example usage:
20
+
21
+ .. code-block:: python
22
+
23
+ # ``get_iam_policy`` returns a :class:'~google.api_core.iam.Policy`.
24
+ policy = resource.get_iam_policy(requested_policy_version=3)
25
+
26
+ phred = "user:phred@example.com"
27
+ admin_group = "group:admins@groups.example.com"
28
+ account = "serviceAccount:account-1234@accounts.example.com"
29
+
30
+ policy.version = 3
31
+ policy.bindings = [
32
+ {
33
+ "role": "roles/owner",
34
+ "members": {phred, admin_group, account}
35
+ },
36
+ {
37
+ "role": "roles/editor",
38
+ "members": {"allAuthenticatedUsers"}
39
+ },
40
+ {
41
+ "role": "roles/viewer",
42
+ "members": {"allUsers"}
43
+ "condition": {
44
+ "title": "request_time",
45
+ "description": "Requests made before 2021-01-01T00:00:00Z",
46
+ "expression": "request.time < timestamp(\"2021-01-01T00:00:00Z\")"
47
+ }
48
+ }
49
+ ]
50
+
51
+ resource.set_iam_policy(policy)
52
+ """
53
+
54
+ import collections
55
+ import collections.abc
56
+ import operator
57
+ import warnings
58
+
59
+ # Generic IAM roles
60
+
61
+ OWNER_ROLE = "roles/owner"
62
+ """Generic role implying all rights to an object."""
63
+
64
+ EDITOR_ROLE = "roles/editor"
65
+ """Generic role implying rights to modify an object."""
66
+
67
+ VIEWER_ROLE = "roles/viewer"
68
+ """Generic role implying rights to access an object."""
69
+
70
+ _ASSIGNMENT_DEPRECATED_MSG = """\
71
+ Assigning to '{}' is deprecated. Use the `policy.bindings` property to modify bindings instead."""
72
+
73
+ _DICT_ACCESS_MSG = """\
74
+ Dict access is not supported on policies with version > 1 or with conditional bindings."""
75
+
76
+
77
+ class InvalidOperationException(Exception):
78
+ """Raised when trying to use Policy class as a dict."""
79
+
80
+ pass
81
+
82
+
83
+ class Policy(collections.abc.MutableMapping):
84
+ """IAM Policy
85
+
86
+ Args:
87
+ etag (Optional[str]): ETag used to identify a unique of the policy
88
+ version (Optional[int]): The syntax schema version of the policy.
89
+
90
+ Note:
91
+ Using conditions in bindings requires the policy's version to be set
92
+ to `3` or greater, depending on the versions that are currently supported.
93
+
94
+ Accessing the policy using dict operations will raise InvalidOperationException
95
+ when the policy's version is set to 3.
96
+
97
+ Use the policy.bindings getter/setter to retrieve and modify the policy's bindings.
98
+
99
+ See:
100
+ IAM Policy https://cloud.google.com/iam/reference/rest/v1/Policy
101
+ Policy versions https://cloud.google.com/iam/docs/policies#versions
102
+ Conditions overview https://cloud.google.com/iam/docs/conditions-overview.
103
+ """
104
+
105
+ _OWNER_ROLES = (OWNER_ROLE,)
106
+ """Roles mapped onto our ``owners`` attribute."""
107
+
108
+ _EDITOR_ROLES = (EDITOR_ROLE,)
109
+ """Roles mapped onto our ``editors`` attribute."""
110
+
111
+ _VIEWER_ROLES = (VIEWER_ROLE,)
112
+ """Roles mapped onto our ``viewers`` attribute."""
113
+
114
+ def __init__(self, etag=None, version=None):
115
+ self.etag = etag
116
+ self.version = version
117
+ self._bindings = []
118
+
119
+ def __iter__(self):
120
+ self.__check_version__()
121
+ # Exclude bindings with no members
122
+ return (binding["role"] for binding in self._bindings if binding["members"])
123
+
124
+ def __len__(self):
125
+ self.__check_version__()
126
+ # Exclude bindings with no members
127
+ return len(list(self.__iter__()))
128
+
129
+ def __getitem__(self, key):
130
+ self.__check_version__()
131
+ for b in self._bindings:
132
+ if b["role"] == key:
133
+ return b["members"]
134
+ # If the binding does not yet exist, create one
135
+ # NOTE: This will create bindings with no members
136
+ # which are ignored by __iter__ and __len__
137
+ new_binding = {"role": key, "members": set()}
138
+ self._bindings.append(new_binding)
139
+ return new_binding["members"]
140
+
141
+ def __setitem__(self, key, value):
142
+ self.__check_version__()
143
+ value = set(value)
144
+ for binding in self._bindings:
145
+ if binding["role"] == key:
146
+ binding["members"] = value
147
+ return
148
+ self._bindings.append({"role": key, "members": value})
149
+
150
+ def __delitem__(self, key):
151
+ self.__check_version__()
152
+ for b in self._bindings:
153
+ if b["role"] == key:
154
+ self._bindings.remove(b)
155
+ return
156
+ raise KeyError(key)
157
+
158
+ def __check_version__(self):
159
+ """Raise InvalidOperationException if version is greater than 1 or policy contains conditions."""
160
+ raise_version = self.version is not None and self.version > 1
161
+
162
+ if raise_version or self._contains_conditions():
163
+ raise InvalidOperationException(_DICT_ACCESS_MSG)
164
+
165
+ def _contains_conditions(self):
166
+ for b in self._bindings:
167
+ if b.get("condition") is not None:
168
+ return True
169
+ return False
170
+
171
+ @property
172
+ def bindings(self):
173
+ """The policy's list of bindings.
174
+
175
+ A binding is specified by a dictionary with keys:
176
+
177
+ * role (str): Role that is assigned to `members`.
178
+
179
+ * members (:obj:`set` of str): Specifies the identities associated to this binding.
180
+
181
+ * condition (:obj:`dict` of str:str): Specifies a condition under which this binding will apply.
182
+
183
+ * title (str): Title for the condition.
184
+
185
+ * description (:obj:str, optional): Description of the condition.
186
+
187
+ * expression: A CEL expression.
188
+
189
+ Type:
190
+ :obj:`list` of :obj:`dict`
191
+
192
+ See:
193
+ Policy versions https://cloud.google.com/iam/docs/policies#versions
194
+ Conditions overview https://cloud.google.com/iam/docs/conditions-overview.
195
+
196
+ Example:
197
+
198
+ .. code-block:: python
199
+
200
+ USER = "user:phred@example.com"
201
+ ADMIN_GROUP = "group:admins@groups.example.com"
202
+ SERVICE_ACCOUNT = "serviceAccount:account-1234@accounts.example.com"
203
+ CONDITION = {
204
+ "title": "request_time",
205
+ "description": "Requests made before 2021-01-01T00:00:00Z", # Optional
206
+ "expression": "request.time < timestamp(\"2021-01-01T00:00:00Z\")"
207
+ }
208
+
209
+ # Set policy's version to 3 before setting bindings containing conditions.
210
+ policy.version = 3
211
+
212
+ policy.bindings = [
213
+ {
214
+ "role": "roles/viewer",
215
+ "members": {USER, ADMIN_GROUP, SERVICE_ACCOUNT},
216
+ "condition": CONDITION
217
+ },
218
+ ...
219
+ ]
220
+ """
221
+ return self._bindings
222
+
223
+ @bindings.setter
224
+ def bindings(self, bindings):
225
+ self._bindings = bindings
226
+
227
+ @property
228
+ def owners(self):
229
+ """Legacy access to owner role.
230
+
231
+ Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
232
+
233
+ DEPRECATED: use `policy.bindings` to access bindings instead.
234
+ """
235
+ result = set()
236
+ for role in self._OWNER_ROLES:
237
+ for member in self.get(role, ()):
238
+ result.add(member)
239
+ return frozenset(result)
240
+
241
+ @owners.setter
242
+ def owners(self, value):
243
+ """Update owners.
244
+
245
+ Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
246
+
247
+ DEPRECATED: use `policy.bindings` to access bindings instead.
248
+ """
249
+ warnings.warn(
250
+ _ASSIGNMENT_DEPRECATED_MSG.format("owners", OWNER_ROLE), DeprecationWarning
251
+ )
252
+ self[OWNER_ROLE] = value
253
+
254
+ @property
255
+ def editors(self):
256
+ """Legacy access to editor role.
257
+
258
+ Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
259
+
260
+ DEPRECATED: use `policy.bindings` to access bindings instead.
261
+ """
262
+ result = set()
263
+ for role in self._EDITOR_ROLES:
264
+ for member in self.get(role, ()):
265
+ result.add(member)
266
+ return frozenset(result)
267
+
268
+ @editors.setter
269
+ def editors(self, value):
270
+ """Update editors.
271
+
272
+ Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
273
+
274
+ DEPRECATED: use `policy.bindings` to modify bindings instead.
275
+ """
276
+ warnings.warn(
277
+ _ASSIGNMENT_DEPRECATED_MSG.format("editors", EDITOR_ROLE),
278
+ DeprecationWarning,
279
+ )
280
+ self[EDITOR_ROLE] = value
281
+
282
+ @property
283
+ def viewers(self):
284
+ """Legacy access to viewer role.
285
+
286
+ Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
287
+
288
+ DEPRECATED: use `policy.bindings` to modify bindings instead.
289
+ """
290
+ result = set()
291
+ for role in self._VIEWER_ROLES:
292
+ for member in self.get(role, ()):
293
+ result.add(member)
294
+ return frozenset(result)
295
+
296
+ @viewers.setter
297
+ def viewers(self, value):
298
+ """Update viewers.
299
+
300
+ Raise InvalidOperationException if version is greater than 1 or policy contains conditions.
301
+
302
+ DEPRECATED: use `policy.bindings` to modify bindings instead.
303
+ """
304
+ warnings.warn(
305
+ _ASSIGNMENT_DEPRECATED_MSG.format("viewers", VIEWER_ROLE),
306
+ DeprecationWarning,
307
+ )
308
+ self[VIEWER_ROLE] = value
309
+
310
+ @staticmethod
311
+ def user(email):
312
+ """Factory method for a user member.
313
+
314
+ Args:
315
+ email (str): E-mail for this particular user.
316
+
317
+ Returns:
318
+ str: A member string corresponding to the given user.
319
+ """
320
+ return "user:%s" % (email,)
321
+
322
+ @staticmethod
323
+ def service_account(email):
324
+ """Factory method for a service account member.
325
+
326
+ Args:
327
+ email (str): E-mail for this particular service account.
328
+
329
+ Returns:
330
+ str: A member string corresponding to the given service account.
331
+
332
+ """
333
+ return "serviceAccount:%s" % (email,)
334
+
335
+ @staticmethod
336
+ def group(email):
337
+ """Factory method for a group member.
338
+
339
+ Args:
340
+ email (str): An id or e-mail for this particular group.
341
+
342
+ Returns:
343
+ str: A member string corresponding to the given group.
344
+ """
345
+ return "group:%s" % (email,)
346
+
347
+ @staticmethod
348
+ def domain(domain):
349
+ """Factory method for a domain member.
350
+
351
+ Args:
352
+ domain (str): The domain for this member.
353
+
354
+ Returns:
355
+ str: A member string corresponding to the given domain.
356
+ """
357
+ return "domain:%s" % (domain,)
358
+
359
+ @staticmethod
360
+ def all_users():
361
+ """Factory method for a member representing all users.
362
+
363
+ Returns:
364
+ str: A member string representing all users.
365
+ """
366
+ return "allUsers"
367
+
368
+ @staticmethod
369
+ def authenticated_users():
370
+ """Factory method for a member representing all authenticated users.
371
+
372
+ Returns:
373
+ str: A member string representing all authenticated users.
374
+ """
375
+ return "allAuthenticatedUsers"
376
+
377
+ @classmethod
378
+ def from_api_repr(cls, resource):
379
+ """Factory: create a policy from a JSON resource.
380
+
381
+ Args:
382
+ resource (dict): policy resource returned by ``getIamPolicy`` API.
383
+
384
+ Returns:
385
+ :class:`Policy`: the parsed policy
386
+ """
387
+ version = resource.get("version")
388
+ etag = resource.get("etag")
389
+ policy = cls(etag, version)
390
+ policy.bindings = resource.get("bindings", [])
391
+
392
+ for binding in policy.bindings:
393
+ binding["members"] = set(binding.get("members", ()))
394
+
395
+ return policy
396
+
397
+ def to_api_repr(self):
398
+ """Render a JSON policy resource.
399
+
400
+ Returns:
401
+ dict: a resource to be passed to the ``setIamPolicy`` API.
402
+ """
403
+ resource = {}
404
+
405
+ if self.etag is not None:
406
+ resource["etag"] = self.etag
407
+
408
+ if self.version is not None:
409
+ resource["version"] = self.version
410
+
411
+ if self._bindings and len(self._bindings) > 0:
412
+ bindings = []
413
+ for binding in self._bindings:
414
+ members = binding.get("members")
415
+ if members:
416
+ new_binding = {"role": binding["role"], "members": sorted(members)}
417
+ condition = binding.get("condition")
418
+ if condition:
419
+ new_binding["condition"] = condition
420
+ bindings.append(new_binding)
421
+
422
+ if bindings:
423
+ # Sort bindings by role
424
+ key = operator.itemgetter("role")
425
+ resource["bindings"] = sorted(bindings, key=key)
426
+
427
+ return resource
py311/lib/python3.11/site-packages/google/api_core/operation.py ADDED
@@ -0,0 +1,365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Futures for long-running operations returned from Google Cloud APIs.
16
+
17
+ These futures can be used to synchronously wait for the result of a
18
+ long-running operation using :meth:`Operation.result`:
19
+
20
+
21
+ .. code-block:: python
22
+
23
+ operation = my_api_client.long_running_method()
24
+ result = operation.result()
25
+
26
+ Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:
27
+
28
+ .. code-block:: python
29
+
30
+ operation = my_api_client.long_running_method()
31
+
32
+ def my_callback(future):
33
+ result = future.result()
34
+
35
+ operation.add_done_callback(my_callback)
36
+
37
+ """
38
+
39
+ import functools
40
+ import threading
41
+
42
+ from google.api_core import exceptions
43
+ from google.api_core import protobuf_helpers
44
+ from google.api_core.future import polling
45
+ from google.longrunning import operations_pb2
46
+ from google.protobuf import json_format
47
+ from google.rpc import code_pb2
48
+
49
+
50
+ class Operation(polling.PollingFuture):
51
+ """A Future for interacting with a Google API Long-Running Operation.
52
+
53
+ Args:
54
+ operation (google.longrunning.operations_pb2.Operation): The
55
+ initial operation.
56
+ refresh (Callable[[], ~.api_core.operation.Operation]): A callable that
57
+ returns the latest state of the operation.
58
+ cancel (Callable[[], None]): A callable that tries to cancel
59
+ the operation.
60
+ result_type (func:`type`): The protobuf type for the operation's
61
+ result.
62
+ metadata_type (func:`type`): The protobuf type for the operation's
63
+ metadata.
64
+ polling (google.api_core.retry.Retry): The configuration used for polling.
65
+ This parameter controls how often :meth:`done` is polled. If the
66
+ ``timeout`` argument is specified in the :meth:`result` method, it will
67
+ override the ``polling.timeout`` property.
68
+ retry (google.api_core.retry.Retry): DEPRECATED: use ``polling`` instead.
69
+ If specified it will override ``polling`` parameter to maintain
70
+ backward compatibility.
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ operation,
76
+ refresh,
77
+ cancel,
78
+ result_type,
79
+ metadata_type=None,
80
+ polling=polling.DEFAULT_POLLING,
81
+ **kwargs,
82
+ ):
83
+ super(Operation, self).__init__(polling=polling, **kwargs)
84
+ self._operation = operation
85
+ self._refresh = refresh
86
+ self._cancel = cancel
87
+ self._result_type = result_type
88
+ self._metadata_type = metadata_type
89
+ self._completion_lock = threading.Lock()
90
+ # Invoke this in case the operation came back already complete.
91
+ self._set_result_from_operation()
92
+
93
+ @property
94
+ def operation(self):
95
+ """google.longrunning.Operation: The current long-running operation."""
96
+ return self._operation
97
+
98
+ @property
99
+ def metadata(self):
100
+ """google.protobuf.Message: the current operation metadata."""
101
+ if not self._operation.HasField("metadata"):
102
+ return None
103
+
104
+ return protobuf_helpers.from_any_pb(
105
+ self._metadata_type, self._operation.metadata
106
+ )
107
+
108
+ @classmethod
109
+ def deserialize(self, payload):
110
+ """Deserialize a ``google.longrunning.Operation`` protocol buffer.
111
+
112
+ Args:
113
+ payload (bytes): A serialized operation protocol buffer.
114
+
115
+ Returns:
116
+ ~.operations_pb2.Operation: An Operation protobuf object.
117
+ """
118
+ return operations_pb2.Operation.FromString(payload)
119
+
120
+ def _set_result_from_operation(self):
121
+ """Set the result or exception from the operation if it is complete."""
122
+ # This must be done in a lock to prevent the polling thread
123
+ # and main thread from both executing the completion logic
124
+ # at the same time.
125
+ with self._completion_lock:
126
+ # If the operation isn't complete or if the result has already been
127
+ # set, do not call set_result/set_exception again.
128
+ # Note: self._result_set is set to True in set_result and
129
+ # set_exception, in case those methods are invoked directly.
130
+ if not self._operation.done or self._result_set:
131
+ return
132
+
133
+ if self._operation.HasField("response"):
134
+ response = protobuf_helpers.from_any_pb(
135
+ self._result_type, self._operation.response
136
+ )
137
+ self.set_result(response)
138
+ elif self._operation.HasField("error"):
139
+ exception = exceptions.from_grpc_status(
140
+ status_code=self._operation.error.code,
141
+ message=self._operation.error.message,
142
+ errors=(self._operation.error,),
143
+ response=self._operation,
144
+ )
145
+ self.set_exception(exception)
146
+ else:
147
+ exception = exceptions.GoogleAPICallError(
148
+ "Unexpected state: Long-running operation had neither "
149
+ "response nor error set."
150
+ )
151
+ self.set_exception(exception)
152
+
153
+ def _refresh_and_update(self, retry=None):
154
+ """Refresh the operation and update the result if needed.
155
+
156
+ Args:
157
+ retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
158
+ """
159
+ # If the currently cached operation is done, no need to make another
160
+ # RPC as it will not change once done.
161
+ if not self._operation.done:
162
+ self._operation = self._refresh(retry=retry) if retry else self._refresh()
163
+ self._set_result_from_operation()
164
+
165
+ def done(self, retry=None):
166
+ """Checks to see if the operation is complete.
167
+
168
+ Args:
169
+ retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
170
+
171
+ Returns:
172
+ bool: True if the operation is complete, False otherwise.
173
+ """
174
+ self._refresh_and_update(retry)
175
+ return self._operation.done
176
+
177
+ def cancel(self):
178
+ """Attempt to cancel the operation.
179
+
180
+ Returns:
181
+ bool: True if the cancel RPC was made, False if the operation is
182
+ already complete.
183
+ """
184
+ if self.done():
185
+ return False
186
+
187
+ self._cancel()
188
+ return True
189
+
190
+ def cancelled(self):
191
+ """True if the operation was cancelled."""
192
+ self._refresh_and_update()
193
+ return (
194
+ self._operation.HasField("error")
195
+ and self._operation.error.code == code_pb2.CANCELLED
196
+ )
197
+
198
+
199
+ def _refresh_http(api_request, operation_name, retry=None):
200
+ """Refresh an operation using a JSON/HTTP client.
201
+
202
+ Args:
203
+ api_request (Callable): A callable used to make an API request. This
204
+ should generally be
205
+ :meth:`google.cloud._http.Connection.api_request`.
206
+ operation_name (str): The name of the operation.
207
+ retry (google.api_core.retry.Retry): (Optional) retry policy
208
+
209
+ Returns:
210
+ google.longrunning.operations_pb2.Operation: The operation.
211
+ """
212
+ path = "operations/{}".format(operation_name)
213
+
214
+ if retry is not None:
215
+ api_request = retry(api_request)
216
+
217
+ api_response = api_request(method="GET", path=path)
218
+ return json_format.ParseDict(api_response, operations_pb2.Operation())
219
+
220
+
221
+ def _cancel_http(api_request, operation_name):
222
+ """Cancel an operation using a JSON/HTTP client.
223
+
224
+ Args:
225
+ api_request (Callable): A callable used to make an API request. This
226
+ should generally be
227
+ :meth:`google.cloud._http.Connection.api_request`.
228
+ operation_name (str): The name of the operation.
229
+ """
230
+ path = "operations/{}:cancel".format(operation_name)
231
+ api_request(method="POST", path=path)
232
+
233
+
234
+ def from_http_json(operation, api_request, result_type, **kwargs):
235
+ """Create an operation future using a HTTP/JSON client.
236
+
237
+ This interacts with the long-running operations `service`_ (specific
238
+ to a given API) via `HTTP/JSON`_.
239
+
240
+ .. _HTTP/JSON: https://cloud.google.com/speech/reference/rest/\
241
+ v1beta1/operations#Operation
242
+
243
+ Args:
244
+ operation (dict): Operation as a dictionary.
245
+ api_request (Callable): A callable used to make an API request. This
246
+ should generally be
247
+ :meth:`google.cloud._http.Connection.api_request`.
248
+ result_type (:func:`type`): The protobuf result type.
249
+ kwargs: Keyword args passed into the :class:`Operation` constructor.
250
+
251
+ Returns:
252
+ ~.api_core.operation.Operation: The operation future to track the given
253
+ operation.
254
+ """
255
+ operation_proto = json_format.ParseDict(operation, operations_pb2.Operation())
256
+ refresh = functools.partial(_refresh_http, api_request, operation_proto.name)
257
+ cancel = functools.partial(_cancel_http, api_request, operation_proto.name)
258
+ return Operation(operation_proto, refresh, cancel, result_type, **kwargs)
259
+
260
+
261
+ def _refresh_grpc(operations_stub, operation_name, retry=None):
262
+ """Refresh an operation using a gRPC client.
263
+
264
+ Args:
265
+ operations_stub (google.longrunning.operations_pb2.OperationsStub):
266
+ The gRPC operations stub.
267
+ operation_name (str): The name of the operation.
268
+ retry (google.api_core.retry.Retry): (Optional) retry policy
269
+
270
+ Returns:
271
+ google.longrunning.operations_pb2.Operation: The operation.
272
+ """
273
+ request_pb = operations_pb2.GetOperationRequest(name=operation_name)
274
+
275
+ rpc = operations_stub.GetOperation
276
+ if retry is not None:
277
+ rpc = retry(rpc)
278
+
279
+ return rpc(request_pb)
280
+
281
+
282
+ def _cancel_grpc(operations_stub, operation_name):
283
+ """Cancel an operation using a gRPC client.
284
+
285
+ Args:
286
+ operations_stub (google.longrunning.operations_pb2.OperationsStub):
287
+ The gRPC operations stub.
288
+ operation_name (str): The name of the operation.
289
+ """
290
+ request_pb = operations_pb2.CancelOperationRequest(name=operation_name)
291
+ operations_stub.CancelOperation(request_pb)
292
+
293
+
294
+ def from_grpc(operation, operations_stub, result_type, grpc_metadata=None, **kwargs):
295
+ """Create an operation future using a gRPC client.
296
+
297
+ This interacts with the long-running operations `service`_ (specific
298
+ to a given API) via gRPC.
299
+
300
+ .. _service: https://github.com/googleapis/googleapis/blob/\
301
+ 050400df0fdb16f63b63e9dee53819044bffc857/\
302
+ google/longrunning/operations.proto#L38
303
+
304
+ Args:
305
+ operation (google.longrunning.operations_pb2.Operation): The operation.
306
+ operations_stub (google.longrunning.operations_pb2.OperationsStub):
307
+ The operations stub.
308
+ result_type (:func:`type`): The protobuf result type.
309
+ grpc_metadata (Optional[List[Tuple[str, str]]]): Additional metadata to pass
310
+ to the rpc.
311
+ kwargs: Keyword args passed into the :class:`Operation` constructor.
312
+
313
+ Returns:
314
+ ~.api_core.operation.Operation: The operation future to track the given
315
+ operation.
316
+ """
317
+ refresh = functools.partial(
318
+ _refresh_grpc,
319
+ operations_stub,
320
+ operation.name,
321
+ metadata=grpc_metadata,
322
+ )
323
+ cancel = functools.partial(
324
+ _cancel_grpc,
325
+ operations_stub,
326
+ operation.name,
327
+ metadata=grpc_metadata,
328
+ )
329
+ return Operation(operation, refresh, cancel, result_type, **kwargs)
330
+
331
+
332
+ def from_gapic(operation, operations_client, result_type, grpc_metadata=None, **kwargs):
333
+ """Create an operation future from a gapic client.
334
+
335
+ This interacts with the long-running operations `service`_ (specific
336
+ to a given API) via a gapic client.
337
+
338
+ .. _service: https://github.com/googleapis/googleapis/blob/\
339
+ 050400df0fdb16f63b63e9dee53819044bffc857/\
340
+ google/longrunning/operations.proto#L38
341
+
342
+ Args:
343
+ operation (google.longrunning.operations_pb2.Operation): The operation.
344
+ operations_client (google.api_core.operations_v1.OperationsClient):
345
+ The operations client.
346
+ result_type (:func:`type`): The protobuf result type.
347
+ grpc_metadata (Optional[List[Tuple[str, str]]]): Additional metadata to pass
348
+ to the rpc.
349
+ kwargs: Keyword args passed into the :class:`Operation` constructor.
350
+
351
+ Returns:
352
+ ~.api_core.operation.Operation: The operation future to track the given
353
+ operation.
354
+ """
355
+ refresh = functools.partial(
356
+ operations_client.get_operation,
357
+ operation.name,
358
+ metadata=grpc_metadata,
359
+ )
360
+ cancel = functools.partial(
361
+ operations_client.cancel_operation,
362
+ operation.name,
363
+ metadata=grpc_metadata,
364
+ )
365
+ return Operation(operation, refresh, cancel, result_type, **kwargs)
py311/lib/python3.11/site-packages/google/api_core/operation_async.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """AsyncIO futures for long-running operations returned from Google Cloud APIs.
16
+
17
+ These futures can be used to await for the result of a long-running operation
18
+ using :meth:`AsyncOperation.result`:
19
+
20
+
21
+ .. code-block:: python
22
+
23
+ operation = my_api_client.long_running_method()
24
+ result = await operation.result()
25
+
26
+ Or asynchronously using callbacks and :meth:`Operation.add_done_callback`:
27
+
28
+ .. code-block:: python
29
+
30
+ operation = my_api_client.long_running_method()
31
+
32
+ def my_callback(future):
33
+ result = await future.result()
34
+
35
+ operation.add_done_callback(my_callback)
36
+
37
+ """
38
+
39
+ import functools
40
+ import threading
41
+
42
+ from google.api_core import exceptions
43
+ from google.api_core import protobuf_helpers
44
+ from google.api_core.future import async_future
45
+ from google.longrunning import operations_pb2
46
+ from google.rpc import code_pb2
47
+
48
+
49
+ class AsyncOperation(async_future.AsyncFuture):
50
+ """A Future for interacting with a Google API Long-Running Operation.
51
+
52
+ Args:
53
+ operation (google.longrunning.operations_pb2.Operation): The
54
+ initial operation.
55
+ refresh (Callable[[], ~.api_core.operation.Operation]): A callable that
56
+ returns the latest state of the operation.
57
+ cancel (Callable[[], None]): A callable that tries to cancel
58
+ the operation.
59
+ result_type (func:`type`): The protobuf type for the operation's
60
+ result.
61
+ metadata_type (func:`type`): The protobuf type for the operation's
62
+ metadata.
63
+ retry (google.api_core.retry.Retry): The retry configuration used
64
+ when polling. This can be used to control how often :meth:`done`
65
+ is polled. Regardless of the retry's ``deadline``, it will be
66
+ overridden by the ``timeout`` argument to :meth:`result`.
67
+ """
68
+
69
+ def __init__(
70
+ self,
71
+ operation,
72
+ refresh,
73
+ cancel,
74
+ result_type,
75
+ metadata_type=None,
76
+ retry=async_future.DEFAULT_RETRY,
77
+ ):
78
+ super().__init__(retry=retry)
79
+ self._operation = operation
80
+ self._refresh = refresh
81
+ self._cancel = cancel
82
+ self._result_type = result_type
83
+ self._metadata_type = metadata_type
84
+ self._completion_lock = threading.Lock()
85
+ # Invoke this in case the operation came back already complete.
86
+ self._set_result_from_operation()
87
+
88
+ @property
89
+ def operation(self):
90
+ """google.longrunning.Operation: The current long-running operation."""
91
+ return self._operation
92
+
93
+ @property
94
+ def metadata(self):
95
+ """google.protobuf.Message: the current operation metadata."""
96
+ if not self._operation.HasField("metadata"):
97
+ return None
98
+
99
+ return protobuf_helpers.from_any_pb(
100
+ self._metadata_type, self._operation.metadata
101
+ )
102
+
103
+ @classmethod
104
+ def deserialize(cls, payload):
105
+ """Deserialize a ``google.longrunning.Operation`` protocol buffer.
106
+
107
+ Args:
108
+ payload (bytes): A serialized operation protocol buffer.
109
+
110
+ Returns:
111
+ ~.operations_pb2.Operation: An Operation protobuf object.
112
+ """
113
+ return operations_pb2.Operation.FromString(payload)
114
+
115
+ def _set_result_from_operation(self):
116
+ """Set the result or exception from the operation if it is complete."""
117
+ # This must be done in a lock to prevent the async_future thread
118
+ # and main thread from both executing the completion logic
119
+ # at the same time.
120
+ with self._completion_lock:
121
+ # If the operation isn't complete or if the result has already been
122
+ # set, do not call set_result/set_exception again.
123
+ if not self._operation.done or self._future.done():
124
+ return
125
+
126
+ if self._operation.HasField("response"):
127
+ response = protobuf_helpers.from_any_pb(
128
+ self._result_type, self._operation.response
129
+ )
130
+ self.set_result(response)
131
+ elif self._operation.HasField("error"):
132
+ exception = exceptions.GoogleAPICallError(
133
+ self._operation.error.message,
134
+ errors=(self._operation.error,),
135
+ response=self._operation,
136
+ )
137
+ self.set_exception(exception)
138
+ else:
139
+ exception = exceptions.GoogleAPICallError(
140
+ "Unexpected state: Long-running operation had neither "
141
+ "response nor error set."
142
+ )
143
+ self.set_exception(exception)
144
+
145
+ async def _refresh_and_update(self, retry=async_future.DEFAULT_RETRY):
146
+ """Refresh the operation and update the result if needed.
147
+
148
+ Args:
149
+ retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
150
+ """
151
+ # If the currently cached operation is done, no need to make another
152
+ # RPC as it will not change once done.
153
+ if not self._operation.done:
154
+ self._operation = await self._refresh(retry=retry)
155
+ self._set_result_from_operation()
156
+
157
+ async def done(self, retry=async_future.DEFAULT_RETRY):
158
+ """Checks to see if the operation is complete.
159
+
160
+ Args:
161
+ retry (google.api_core.retry.Retry): (Optional) How to retry the RPC.
162
+
163
+ Returns:
164
+ bool: True if the operation is complete, False otherwise.
165
+ """
166
+ await self._refresh_and_update(retry)
167
+ return self._operation.done
168
+
169
+ async def cancel(self):
170
+ """Attempt to cancel the operation.
171
+
172
+ Returns:
173
+ bool: True if the cancel RPC was made, False if the operation is
174
+ already complete.
175
+ """
176
+ result = await self.done()
177
+ if result:
178
+ return False
179
+ else:
180
+ await self._cancel()
181
+ return True
182
+
183
+ async def cancelled(self):
184
+ """True if the operation was cancelled."""
185
+ await self._refresh_and_update()
186
+ return (
187
+ self._operation.HasField("error")
188
+ and self._operation.error.code == code_pb2.CANCELLED
189
+ )
190
+
191
+
192
+ def from_gapic(operation, operations_client, result_type, grpc_metadata=None, **kwargs):
193
+ """Create an operation future from a gapic client.
194
+
195
+ This interacts with the long-running operations `service`_ (specific
196
+ to a given API) via a gapic client.
197
+
198
+ .. _service: https://github.com/googleapis/googleapis/blob/\
199
+ 050400df0fdb16f63b63e9dee53819044bffc857/\
200
+ google/longrunning/operations.proto#L38
201
+
202
+ Args:
203
+ operation (google.longrunning.operations_pb2.Operation): The operation.
204
+ operations_client (google.api_core.operations_v1.OperationsClient):
205
+ The operations client.
206
+ result_type (:func:`type`): The protobuf result type.
207
+ grpc_metadata (Optional[List[Tuple[str, str]]]): Additional metadata to pass
208
+ to the rpc.
209
+ kwargs: Keyword args passed into the :class:`Operation` constructor.
210
+
211
+ Returns:
212
+ ~.api_core.operation.Operation: The operation future to track the given
213
+ operation.
214
+ """
215
+ refresh = functools.partial(
216
+ operations_client.get_operation,
217
+ operation.name,
218
+ metadata=grpc_metadata,
219
+ )
220
+ cancel = functools.partial(
221
+ operations_client.cancel_operation,
222
+ operation.name,
223
+ metadata=grpc_metadata,
224
+ )
225
+ return AsyncOperation(operation, refresh, cancel, result_type, **kwargs)
py311/lib/python3.11/site-packages/google/api_core/page_iterator.py ADDED
@@ -0,0 +1,571 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Iterators for paging through paged API methods.
16
+
17
+ These iterators simplify the process of paging through API responses
18
+ where the request takes a page token and the response is a list of results with
19
+ a token for the next page. See `list pagination`_ in the Google API Style Guide
20
+ for more details.
21
+
22
+ .. _list pagination:
23
+ https://cloud.google.com/apis/design/design_patterns#list_pagination
24
+
25
+ API clients that have methods that follow the list pagination pattern can
26
+ return an :class:`.Iterator`. You can use this iterator to get **all** of
27
+ the results across all pages::
28
+
29
+ >>> results_iterator = client.list_resources()
30
+ >>> list(results_iterator) # Convert to a list (consumes all values).
31
+
32
+ Or you can walk your way through items and call off the search early if
33
+ you find what you're looking for (resulting in possibly fewer requests)::
34
+
35
+ >>> for resource in results_iterator:
36
+ ... print(resource.name)
37
+ ... if not resource.is_valid:
38
+ ... break
39
+
40
+ At any point, you may check the number of items consumed by referencing the
41
+ ``num_results`` property of the iterator::
42
+
43
+ >>> for my_item in results_iterator:
44
+ ... if results_iterator.num_results >= 10:
45
+ ... break
46
+
47
+ When iterating, not every new item will send a request to the server.
48
+ To iterate based on each page of items (where a page corresponds to
49
+ a request)::
50
+
51
+ >>> for page in results_iterator.pages:
52
+ ... print('=' * 20)
53
+ ... print(' Page number: {:d}'.format(iterator.page_number))
54
+ ... print(' Items in page: {:d}'.format(page.num_items))
55
+ ... print(' First item: {!r}'.format(next(page)))
56
+ ... print('Items remaining: {:d}'.format(page.remaining))
57
+ ... print('Next page token: {}'.format(iterator.next_page_token))
58
+ ====================
59
+ Page number: 1
60
+ Items in page: 1
61
+ First item: <MyItemClass at 0x7f1d3cccf690>
62
+ Items remaining: 0
63
+ Next page token: eav1OzQB0OM8rLdGXOEsyQWSG
64
+ ====================
65
+ Page number: 2
66
+ Items in page: 19
67
+ First item: <MyItemClass at 0x7f1d3cccffd0>
68
+ Items remaining: 18
69
+ Next page token: None
70
+
71
+ Then, for each page you can get all the resources on that page by iterating
72
+ through it or using :func:`list`::
73
+
74
+ >>> list(page)
75
+ [
76
+ <MyItemClass at 0x7fd64a098ad0>,
77
+ <MyItemClass at 0x7fd64a098ed0>,
78
+ <MyItemClass at 0x7fd64a098e90>,
79
+ ]
80
+ """
81
+
82
+ import abc
83
+
84
+
85
+ class Page(object):
86
+ """Single page of results in an iterator.
87
+
88
+ Args:
89
+ parent (google.api_core.page_iterator.Iterator): The iterator that owns
90
+ the current page.
91
+ items (Sequence[Any]): An iterable (that also defines __len__) of items
92
+ from a raw API response.
93
+ item_to_value (Callable[google.api_core.page_iterator.Iterator, Any]):
94
+ Callable to convert an item from the type in the raw API response
95
+ into the native object. Will be called with the iterator and a
96
+ single item.
97
+ raw_page Optional[google.protobuf.message.Message]:
98
+ The raw page response.
99
+ """
100
+
101
+ def __init__(self, parent, items, item_to_value, raw_page=None):
102
+ self._parent = parent
103
+ self._num_items = len(items)
104
+ self._remaining = self._num_items
105
+ self._item_iter = iter(items)
106
+ self._item_to_value = item_to_value
107
+ self._raw_page = raw_page
108
+
109
+ @property
110
+ def raw_page(self):
111
+ """google.protobuf.message.Message"""
112
+ return self._raw_page
113
+
114
+ @property
115
+ def num_items(self):
116
+ """int: Total items in the page."""
117
+ return self._num_items
118
+
119
+ @property
120
+ def remaining(self):
121
+ """int: Remaining items in the page."""
122
+ return self._remaining
123
+
124
+ def __iter__(self):
125
+ """The :class:`Page` is an iterator of items."""
126
+ return self
127
+
128
+ def __next__(self):
129
+ """Get the next value in the page."""
130
+ item = next(self._item_iter)
131
+ result = self._item_to_value(self._parent, item)
132
+ # Since we've successfully got the next value from the
133
+ # iterator, we update the number of remaining.
134
+ self._remaining -= 1
135
+ return result
136
+
137
+
138
+ def _item_to_value_identity(iterator, item):
139
+ """An item to value transformer that returns the item un-changed."""
140
+ # pylint: disable=unused-argument
141
+ # We are conforming to the interface defined by Iterator.
142
+ return item
143
+
144
+
145
+ class Iterator(object, metaclass=abc.ABCMeta):
146
+ """A generic class for iterating through API list responses.
147
+
148
+ Args:
149
+ client(google.cloud.client.Client): The API client.
150
+ item_to_value (Callable[google.api_core.page_iterator.Iterator, Any]):
151
+ Callable to convert an item from the type in the raw API response
152
+ into the native object. Will be called with the iterator and a
153
+ single item.
154
+ page_token (str): A token identifying a page in a result set to start
155
+ fetching results from.
156
+ max_results (int): The maximum number of results to fetch.
157
+ """
158
+
159
+ def __init__(
160
+ self,
161
+ client,
162
+ item_to_value=_item_to_value_identity,
163
+ page_token=None,
164
+ max_results=None,
165
+ ):
166
+ self._started = False
167
+ self.__active_iterator = None
168
+
169
+ self.client = client
170
+ """Optional[Any]: The client that created this iterator."""
171
+ self.item_to_value = item_to_value
172
+ """Callable[Iterator, Any]: Callable to convert an item from the type
173
+ in the raw API response into the native object. Will be called with
174
+ the iterator and a
175
+ single item.
176
+ """
177
+ self.max_results = max_results
178
+ """int: The maximum number of results to fetch"""
179
+
180
+ # The attributes below will change over the life of the iterator.
181
+ self.page_number = 0
182
+ """int: The current page of results."""
183
+ self.next_page_token = page_token
184
+ """str: The token for the next page of results. If this is set before
185
+ the iterator starts, it effectively offsets the iterator to a
186
+ specific starting point."""
187
+ self.num_results = 0
188
+ """int: The total number of results fetched so far."""
189
+
190
+ @property
191
+ def pages(self):
192
+ """Iterator of pages in the response.
193
+
194
+ returns:
195
+ types.GeneratorType[google.api_core.page_iterator.Page]: A
196
+ generator of page instances.
197
+
198
+ raises:
199
+ ValueError: If the iterator has already been started.
200
+ """
201
+ if self._started:
202
+ raise ValueError("Iterator has already started", self)
203
+ self._started = True
204
+ return self._page_iter(increment=True)
205
+
206
+ def _items_iter(self):
207
+ """Iterator for each item returned."""
208
+ for page in self._page_iter(increment=False):
209
+ for item in page:
210
+ self.num_results += 1
211
+ yield item
212
+
213
+ def __iter__(self):
214
+ """Iterator for each item returned.
215
+
216
+ Returns:
217
+ types.GeneratorType[Any]: A generator of items from the API.
218
+
219
+ Raises:
220
+ ValueError: If the iterator has already been started.
221
+ """
222
+ if self._started:
223
+ raise ValueError("Iterator has already started", self)
224
+ self._started = True
225
+ return self._items_iter()
226
+
227
+ def __next__(self):
228
+ if self.__active_iterator is None:
229
+ self.__active_iterator = iter(self)
230
+ return next(self.__active_iterator)
231
+
232
+ def _page_iter(self, increment):
233
+ """Generator of pages of API responses.
234
+
235
+ Args:
236
+ increment (bool): Flag indicating if the total number of results
237
+ should be incremented on each page. This is useful since a page
238
+ iterator will want to increment by results per page while an
239
+ items iterator will want to increment per item.
240
+
241
+ Yields:
242
+ Page: each page of items from the API.
243
+ """
244
+ page = self._next_page()
245
+ while page is not None:
246
+ self.page_number += 1
247
+ if increment:
248
+ self.num_results += page.num_items
249
+ yield page
250
+ page = self._next_page()
251
+
252
+ @abc.abstractmethod
253
+ def _next_page(self):
254
+ """Get the next page in the iterator.
255
+
256
+ This does nothing and is intended to be over-ridden by subclasses
257
+ to return the next :class:`Page`.
258
+
259
+ Raises:
260
+ NotImplementedError: Always, this method is abstract.
261
+ """
262
+ raise NotImplementedError
263
+
264
+
265
+ def _do_nothing_page_start(iterator, page, response):
266
+ """Helper to provide custom behavior after a :class:`Page` is started.
267
+
268
+ This is a do-nothing stand-in as the default value.
269
+
270
+ Args:
271
+ iterator (Iterator): An iterator that holds some request info.
272
+ page (Page): The page that was just created.
273
+ response (Any): The API response for a page.
274
+ """
275
+ # pylint: disable=unused-argument
276
+ pass
277
+
278
+
279
+ class HTTPIterator(Iterator):
280
+ """A generic class for iterating through HTTP/JSON API list responses.
281
+
282
+ To make an iterator work, you'll need to provide a way to convert a JSON
283
+ item returned from the API into the object of your choice (via
284
+ ``item_to_value``). You also may need to specify a custom ``items_key`` so
285
+ that a given response (containing a page of results) can be parsed into an
286
+ iterable page of the actual objects you want.
287
+
288
+ Args:
289
+ client (google.cloud.client.Client): The API client.
290
+ api_request (Callable): The function to use to make API requests.
291
+ Generally, this will be
292
+ :meth:`google.cloud._http.JSONConnection.api_request`.
293
+ path (str): The method path to query for the list of items.
294
+ item_to_value (Callable[google.api_core.page_iterator.Iterator, Any]):
295
+ Callable to convert an item from the type in the JSON response into
296
+ a native object. Will be called with the iterator and a single
297
+ item.
298
+ items_key (str): The key in the API response where the list of items
299
+ can be found.
300
+ page_token (str): A token identifying a page in a result set to start
301
+ fetching results from.
302
+ page_size (int): The maximum number of results to fetch per page
303
+ max_results (int): The maximum number of results to fetch
304
+ extra_params (dict): Extra query string parameters for the
305
+ API call.
306
+ page_start (Callable[
307
+ google.api_core.page_iterator.Iterator,
308
+ google.api_core.page_iterator.Page, dict]): Callable to provide
309
+ any special behavior after a new page has been created. Assumed
310
+ signature takes the :class:`.Iterator` that started the page,
311
+ the :class:`.Page` that was started and the dictionary containing
312
+ the page response.
313
+ next_token (str): The name of the field used in the response for page
314
+ tokens.
315
+
316
+ .. autoattribute:: pages
317
+ """
318
+
319
+ _DEFAULT_ITEMS_KEY = "items"
320
+ _PAGE_TOKEN = "pageToken"
321
+ _MAX_RESULTS = "maxResults"
322
+ _NEXT_TOKEN = "nextPageToken"
323
+ _RESERVED_PARAMS = frozenset([_PAGE_TOKEN])
324
+ _HTTP_METHOD = "GET"
325
+
326
+ def __init__(
327
+ self,
328
+ client,
329
+ api_request,
330
+ path,
331
+ item_to_value,
332
+ items_key=_DEFAULT_ITEMS_KEY,
333
+ page_token=None,
334
+ page_size=None,
335
+ max_results=None,
336
+ extra_params=None,
337
+ page_start=_do_nothing_page_start,
338
+ next_token=_NEXT_TOKEN,
339
+ ):
340
+ super(HTTPIterator, self).__init__(
341
+ client, item_to_value, page_token=page_token, max_results=max_results
342
+ )
343
+ self.api_request = api_request
344
+ self.path = path
345
+ self._items_key = items_key
346
+ self.extra_params = extra_params
347
+ self._page_size = page_size
348
+ self._page_start = page_start
349
+ self._next_token = next_token
350
+ # Verify inputs / provide defaults.
351
+ if self.extra_params is None:
352
+ self.extra_params = {}
353
+ self._verify_params()
354
+
355
+ def _verify_params(self):
356
+ """Verifies the parameters don't use any reserved parameter.
357
+
358
+ Raises:
359
+ ValueError: If a reserved parameter is used.
360
+ """
361
+ reserved_in_use = self._RESERVED_PARAMS.intersection(self.extra_params)
362
+ if reserved_in_use:
363
+ raise ValueError("Using a reserved parameter", reserved_in_use)
364
+
365
+ def _next_page(self):
366
+ """Get the next page in the iterator.
367
+
368
+ Returns:
369
+ Optional[Page]: The next page in the iterator or :data:`None` if
370
+ there are no pages left.
371
+ """
372
+ if self._has_next_page():
373
+ response = self._get_next_page_response()
374
+ items = response.get(self._items_key, ())
375
+ page = Page(self, items, self.item_to_value, raw_page=response)
376
+ self._page_start(self, page, response)
377
+ self.next_page_token = response.get(self._next_token)
378
+ return page
379
+ else:
380
+ return None
381
+
382
+ def _has_next_page(self):
383
+ """Determines whether or not there are more pages with results.
384
+
385
+ Returns:
386
+ bool: Whether the iterator has more pages.
387
+ """
388
+ if self.page_number == 0:
389
+ return True
390
+
391
+ if self.max_results is not None:
392
+ if self.num_results >= self.max_results:
393
+ return False
394
+
395
+ return self.next_page_token is not None
396
+
397
+ def _get_query_params(self):
398
+ """Getter for query parameters for the next request.
399
+
400
+ Returns:
401
+ dict: A dictionary of query parameters.
402
+ """
403
+ result = {}
404
+ if self.next_page_token is not None:
405
+ result[self._PAGE_TOKEN] = self.next_page_token
406
+
407
+ page_size = None
408
+ if self.max_results is not None:
409
+ page_size = self.max_results - self.num_results
410
+ if self._page_size is not None:
411
+ page_size = min(page_size, self._page_size)
412
+ elif self._page_size is not None:
413
+ page_size = self._page_size
414
+
415
+ if page_size is not None:
416
+ result[self._MAX_RESULTS] = page_size
417
+
418
+ result.update(self.extra_params)
419
+ return result
420
+
421
+ def _get_next_page_response(self):
422
+ """Requests the next page from the path provided.
423
+
424
+ Returns:
425
+ dict: The parsed JSON response of the next page's contents.
426
+
427
+ Raises:
428
+ ValueError: If the HTTP method is not ``GET`` or ``POST``.
429
+ """
430
+ params = self._get_query_params()
431
+ if self._HTTP_METHOD == "GET":
432
+ return self.api_request(
433
+ method=self._HTTP_METHOD, path=self.path, query_params=params
434
+ )
435
+ elif self._HTTP_METHOD == "POST":
436
+ return self.api_request(
437
+ method=self._HTTP_METHOD, path=self.path, data=params
438
+ )
439
+ else:
440
+ raise ValueError("Unexpected HTTP method", self._HTTP_METHOD)
441
+
442
+
443
+ class _GAXIterator(Iterator):
444
+ """A generic class for iterating through Cloud gRPC APIs list responses.
445
+
446
+ Any:
447
+ client (google.cloud.client.Client): The API client.
448
+ page_iter (google.gax.PageIterator): A GAX page iterator to be wrapped
449
+ to conform to the :class:`Iterator` interface.
450
+ item_to_value (Callable[Iterator, Any]): Callable to convert an item
451
+ from the protobuf response into a native object. Will
452
+ be called with the iterator and a single item.
453
+ max_results (int): The maximum number of results to fetch.
454
+
455
+ .. autoattribute:: pages
456
+ """
457
+
458
+ def __init__(self, client, page_iter, item_to_value, max_results=None):
459
+ super(_GAXIterator, self).__init__(
460
+ client,
461
+ item_to_value,
462
+ page_token=page_iter.page_token,
463
+ max_results=max_results,
464
+ )
465
+ self._gax_page_iter = page_iter
466
+
467
+ def _next_page(self):
468
+ """Get the next page in the iterator.
469
+
470
+ Wraps the response from the :class:`~google.gax.PageIterator` in a
471
+ :class:`Page` instance and captures some state at each page.
472
+
473
+ Returns:
474
+ Optional[Page]: The next page in the iterator or :data:`None` if
475
+ there are no pages left.
476
+ """
477
+ try:
478
+ items = next(self._gax_page_iter)
479
+ page = Page(self, items, self.item_to_value)
480
+ self.next_page_token = self._gax_page_iter.page_token or None
481
+ return page
482
+ except StopIteration:
483
+ return None
484
+
485
+
486
+ class GRPCIterator(Iterator):
487
+ """A generic class for iterating through gRPC list responses.
488
+
489
+ .. note:: The class does not take a ``page_token`` argument because it can
490
+ just be specified in the ``request``.
491
+
492
+ Args:
493
+ client (google.cloud.client.Client): The API client. This unused by
494
+ this class, but kept to satisfy the :class:`Iterator` interface.
495
+ method (Callable[protobuf.Message]): A bound gRPC method that should
496
+ take a single message for the request.
497
+ request (protobuf.Message): The request message.
498
+ items_field (str): The field in the response message that has the
499
+ items for the page.
500
+ item_to_value (Callable[GRPCIterator, Any]): Callable to convert an
501
+ item from the type in the JSON response into a native object. Will
502
+ be called with the iterator and a single item.
503
+ request_token_field (str): The field in the request message used to
504
+ specify the page token.
505
+ response_token_field (str): The field in the response message that has
506
+ the token for the next page.
507
+ max_results (int): The maximum number of results to fetch.
508
+
509
+ .. autoattribute:: pages
510
+ """
511
+
512
+ _DEFAULT_REQUEST_TOKEN_FIELD = "page_token"
513
+ _DEFAULT_RESPONSE_TOKEN_FIELD = "next_page_token"
514
+
515
+ def __init__(
516
+ self,
517
+ client,
518
+ method,
519
+ request,
520
+ items_field,
521
+ item_to_value=_item_to_value_identity,
522
+ request_token_field=_DEFAULT_REQUEST_TOKEN_FIELD,
523
+ response_token_field=_DEFAULT_RESPONSE_TOKEN_FIELD,
524
+ max_results=None,
525
+ ):
526
+ super(GRPCIterator, self).__init__(
527
+ client, item_to_value, max_results=max_results
528
+ )
529
+ self._method = method
530
+ self._request = request
531
+ self._items_field = items_field
532
+ self._request_token_field = request_token_field
533
+ self._response_token_field = response_token_field
534
+
535
+ def _next_page(self):
536
+ """Get the next page in the iterator.
537
+
538
+ Returns:
539
+ Page: The next page in the iterator or :data:`None` if
540
+ there are no pages left.
541
+ """
542
+ if not self._has_next_page():
543
+ return None
544
+
545
+ if self.next_page_token is not None:
546
+ setattr(self._request, self._request_token_field, self.next_page_token)
547
+
548
+ response = self._method(self._request)
549
+
550
+ self.next_page_token = getattr(response, self._response_token_field)
551
+ items = getattr(response, self._items_field)
552
+ page = Page(self, items, self.item_to_value, raw_page=response)
553
+
554
+ return page
555
+
556
+ def _has_next_page(self):
557
+ """Determines whether or not there are more pages with results.
558
+
559
+ Returns:
560
+ bool: Whether the iterator has more pages.
561
+ """
562
+ if self.page_number == 0:
563
+ return True
564
+
565
+ if self.max_results is not None:
566
+ if self.num_results >= self.max_results:
567
+ return False
568
+
569
+ # Note: intentionally a falsy check instead of a None check. The RPC
570
+ # can return an empty string indicating no more pages.
571
+ return True if self.next_page_token else False
py311/lib/python3.11/site-packages/google/api_core/page_iterator_async.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """AsyncIO iterators for paging through paged API methods.
16
+
17
+ These iterators simplify the process of paging through API responses
18
+ where the request takes a page token and the response is a list of results with
19
+ a token for the next page. See `list pagination`_ in the Google API Style Guide
20
+ for more details.
21
+
22
+ .. _list pagination:
23
+ https://cloud.google.com/apis/design/design_patterns#list_pagination
24
+
25
+ API clients that have methods that follow the list pagination pattern can
26
+ return an :class:`.AsyncIterator`:
27
+
28
+ >>> results_iterator = await client.list_resources()
29
+
30
+ Or you can walk your way through items and call off the search early if
31
+ you find what you're looking for (resulting in possibly fewer requests)::
32
+
33
+ >>> async for resource in results_iterator:
34
+ ... print(resource.name)
35
+ ... if not resource.is_valid:
36
+ ... break
37
+
38
+ At any point, you may check the number of items consumed by referencing the
39
+ ``num_results`` property of the iterator::
40
+
41
+ >>> async for my_item in results_iterator:
42
+ ... if results_iterator.num_results >= 10:
43
+ ... break
44
+
45
+ When iterating, not every new item will send a request to the server.
46
+ To iterate based on each page of items (where a page corresponds to
47
+ a request)::
48
+
49
+ >>> async for page in results_iterator.pages:
50
+ ... print('=' * 20)
51
+ ... print(' Page number: {:d}'.format(iterator.page_number))
52
+ ... print(' Items in page: {:d}'.format(page.num_items))
53
+ ... print(' First item: {!r}'.format(next(page)))
54
+ ... print('Items remaining: {:d}'.format(page.remaining))
55
+ ... print('Next page token: {}'.format(iterator.next_page_token))
56
+ ====================
57
+ Page number: 1
58
+ Items in page: 1
59
+ First item: <MyItemClass at 0x7f1d3cccf690>
60
+ Items remaining: 0
61
+ Next page token: eav1OzQB0OM8rLdGXOEsyQWSG
62
+ ====================
63
+ Page number: 2
64
+ Items in page: 19
65
+ First item: <MyItemClass at 0x7f1d3cccffd0>
66
+ Items remaining: 18
67
+ Next page token: None
68
+ """
69
+
70
+ import abc
71
+
72
+ from google.api_core.page_iterator import Page
73
+
74
+
75
+ def _item_to_value_identity(iterator, item):
76
+ """An item to value transformer that returns the item un-changed."""
77
+ # pylint: disable=unused-argument
78
+ # We are conforming to the interface defined by Iterator.
79
+ return item
80
+
81
+
82
+ class AsyncIterator(abc.ABC):
83
+ """A generic class for iterating through API list responses.
84
+
85
+ Args:
86
+ client(google.cloud.client.Client): The API client.
87
+ item_to_value (Callable[google.api_core.page_iterator_async.AsyncIterator, Any]):
88
+ Callable to convert an item from the type in the raw API response
89
+ into the native object. Will be called with the iterator and a
90
+ single item.
91
+ page_token (str): A token identifying a page in a result set to start
92
+ fetching results from.
93
+ max_results (int): The maximum number of results to fetch.
94
+ """
95
+
96
+ def __init__(
97
+ self,
98
+ client,
99
+ item_to_value=_item_to_value_identity,
100
+ page_token=None,
101
+ max_results=None,
102
+ ):
103
+ self._started = False
104
+ self.__active_aiterator = None
105
+
106
+ self.client = client
107
+ """Optional[Any]: The client that created this iterator."""
108
+ self.item_to_value = item_to_value
109
+ """Callable[Iterator, Any]: Callable to convert an item from the type
110
+ in the raw API response into the native object. Will be called with
111
+ the iterator and a
112
+ single item.
113
+ """
114
+ self.max_results = max_results
115
+ """int: The maximum number of results to fetch."""
116
+
117
+ # The attributes below will change over the life of the iterator.
118
+ self.page_number = 0
119
+ """int: The current page of results."""
120
+ self.next_page_token = page_token
121
+ """str: The token for the next page of results. If this is set before
122
+ the iterator starts, it effectively offsets the iterator to a
123
+ specific starting point."""
124
+ self.num_results = 0
125
+ """int: The total number of results fetched so far."""
126
+
127
+ @property
128
+ def pages(self):
129
+ """Iterator of pages in the response.
130
+
131
+ returns:
132
+ types.GeneratorType[google.api_core.page_iterator.Page]: A
133
+ generator of page instances.
134
+
135
+ raises:
136
+ ValueError: If the iterator has already been started.
137
+ """
138
+ if self._started:
139
+ raise ValueError("Iterator has already started", self)
140
+ self._started = True
141
+ return self._page_aiter(increment=True)
142
+
143
+ async def _items_aiter(self):
144
+ """Iterator for each item returned."""
145
+ async for page in self._page_aiter(increment=False):
146
+ for item in page:
147
+ self.num_results += 1
148
+ yield item
149
+
150
+ def __aiter__(self):
151
+ """Iterator for each item returned.
152
+
153
+ Returns:
154
+ types.GeneratorType[Any]: A generator of items from the API.
155
+
156
+ Raises:
157
+ ValueError: If the iterator has already been started.
158
+ """
159
+ if self._started:
160
+ raise ValueError("Iterator has already started", self)
161
+ self._started = True
162
+ return self._items_aiter()
163
+
164
+ async def __anext__(self):
165
+ if self.__active_aiterator is None:
166
+ self.__active_aiterator = self.__aiter__()
167
+ return await self.__active_aiterator.__anext__()
168
+
169
+ async def _page_aiter(self, increment):
170
+ """Generator of pages of API responses.
171
+
172
+ Args:
173
+ increment (bool): Flag indicating if the total number of results
174
+ should be incremented on each page. This is useful since a page
175
+ iterator will want to increment by results per page while an
176
+ items iterator will want to increment per item.
177
+
178
+ Yields:
179
+ Page: each page of items from the API.
180
+ """
181
+ page = await self._next_page()
182
+ while page is not None:
183
+ self.page_number += 1
184
+ if increment:
185
+ self.num_results += page.num_items
186
+ yield page
187
+ page = await self._next_page()
188
+
189
+ @abc.abstractmethod
190
+ async def _next_page(self):
191
+ """Get the next page in the iterator.
192
+
193
+ This does nothing and is intended to be over-ridden by subclasses
194
+ to return the next :class:`Page`.
195
+
196
+ Raises:
197
+ NotImplementedError: Always, this method is abstract.
198
+ """
199
+ raise NotImplementedError
200
+
201
+
202
+ class AsyncGRPCIterator(AsyncIterator):
203
+ """A generic class for iterating through gRPC list responses.
204
+
205
+ .. note:: The class does not take a ``page_token`` argument because it can
206
+ just be specified in the ``request``.
207
+
208
+ Args:
209
+ client (google.cloud.client.Client): The API client. This unused by
210
+ this class, but kept to satisfy the :class:`Iterator` interface.
211
+ method (Callable[protobuf.Message]): A bound gRPC method that should
212
+ take a single message for the request.
213
+ request (protobuf.Message): The request message.
214
+ items_field (str): The field in the response message that has the
215
+ items for the page.
216
+ item_to_value (Callable[GRPCIterator, Any]): Callable to convert an
217
+ item from the type in the JSON response into a native object. Will
218
+ be called with the iterator and a single item.
219
+ request_token_field (str): The field in the request message used to
220
+ specify the page token.
221
+ response_token_field (str): The field in the response message that has
222
+ the token for the next page.
223
+ max_results (int): The maximum number of results to fetch.
224
+
225
+ .. autoattribute:: pages
226
+ """
227
+
228
+ _DEFAULT_REQUEST_TOKEN_FIELD = "page_token"
229
+ _DEFAULT_RESPONSE_TOKEN_FIELD = "next_page_token"
230
+
231
+ def __init__(
232
+ self,
233
+ client,
234
+ method,
235
+ request,
236
+ items_field,
237
+ item_to_value=_item_to_value_identity,
238
+ request_token_field=_DEFAULT_REQUEST_TOKEN_FIELD,
239
+ response_token_field=_DEFAULT_RESPONSE_TOKEN_FIELD,
240
+ max_results=None,
241
+ ):
242
+ super().__init__(client, item_to_value, max_results=max_results)
243
+ self._method = method
244
+ self._request = request
245
+ self._items_field = items_field
246
+ self._request_token_field = request_token_field
247
+ self._response_token_field = response_token_field
248
+
249
+ async def _next_page(self):
250
+ """Get the next page in the iterator.
251
+
252
+ Returns:
253
+ Page: The next page in the iterator or :data:`None` if
254
+ there are no pages left.
255
+ """
256
+ if not self._has_next_page():
257
+ return None
258
+
259
+ if self.next_page_token is not None:
260
+ setattr(self._request, self._request_token_field, self.next_page_token)
261
+
262
+ response = await self._method(self._request)
263
+
264
+ self.next_page_token = getattr(response, self._response_token_field)
265
+ items = getattr(response, self._items_field)
266
+ page = Page(self, items, self.item_to_value, raw_page=response)
267
+
268
+ return page
269
+
270
+ def _has_next_page(self):
271
+ """Determines whether or not there are more pages with results.
272
+
273
+ Returns:
274
+ bool: Whether the iterator has more pages.
275
+ """
276
+ if self.page_number == 0:
277
+ return True
278
+
279
+ # Note: intentionally a falsy check instead of a None check. The RPC
280
+ # can return an empty string indicating no more pages.
281
+ if self.max_results is not None:
282
+ if self.num_results >= self.max_results:
283
+ return False
284
+
285
+ return True if self.next_page_token else False
py311/lib/python3.11/site-packages/google/api_core/path_template.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Expand and validate URL path templates.
16
+
17
+ This module provides the :func:`expand` and :func:`validate` functions for
18
+ interacting with Google-style URL `path templates`_ which are commonly used
19
+ in Google APIs for `resource names`_.
20
+
21
+ .. _path templates: https://github.com/googleapis/googleapis/blob
22
+ /57e2d376ac7ef48681554204a3ba78a414f2c533/google/api/http.proto#L212
23
+ .. _resource names: https://cloud.google.com/apis/design/resource_names
24
+ """
25
+
26
+ from __future__ import unicode_literals
27
+
28
+ from collections import deque
29
+ import copy
30
+ import functools
31
+ import re
32
+
33
+ # Regular expression for extracting variable parts from a path template.
34
+ # The variables can be expressed as:
35
+ #
36
+ # - "*": a single-segment positional variable, for example: "books/*"
37
+ # - "**": a multi-segment positional variable, for example: "shelf/**/book/*"
38
+ # - "{name}": a single-segment wildcard named variable, for example
39
+ # "books/{name}"
40
+ # - "{name=*}: same as above.
41
+ # - "{name=**}": a multi-segment wildcard named variable, for example
42
+ # "shelf/{name=**}"
43
+ # - "{name=/path/*/**}": a multi-segment named variable with a sub-template.
44
+ _VARIABLE_RE = re.compile(
45
+ r"""
46
+ ( # Capture the entire variable expression
47
+ (?P<positional>\*\*?) # Match & capture * and ** positional variables.
48
+ |
49
+ # Match & capture named variables {name}
50
+ {
51
+ (?P<name>[^/]+?)
52
+ # Optionally match and capture the named variable's template.
53
+ (?:=(?P<template>.+?))?
54
+ }
55
+ )
56
+ """,
57
+ re.VERBOSE,
58
+ )
59
+
60
+ # Segment expressions used for validating paths against a template.
61
+ _SINGLE_SEGMENT_PATTERN = r"([^/]+)"
62
+ _MULTI_SEGMENT_PATTERN = r"(.+)"
63
+
64
+
65
+ def _expand_variable_match(positional_vars, named_vars, match):
66
+ """Expand a matched variable with its value.
67
+
68
+ Args:
69
+ positional_vars (list): A list of positional variables. This list will
70
+ be modified.
71
+ named_vars (dict): A dictionary of named variables.
72
+ match (re.Match): A regular expression match.
73
+
74
+ Returns:
75
+ str: The expanded variable to replace the match.
76
+
77
+ Raises:
78
+ ValueError: If a positional or named variable is required by the
79
+ template but not specified or if an unexpected template expression
80
+ is encountered.
81
+ """
82
+ positional = match.group("positional")
83
+ name = match.group("name")
84
+ if name is not None:
85
+ try:
86
+ return str(named_vars[name])
87
+ except KeyError:
88
+ raise ValueError(
89
+ "Named variable '{}' not specified and needed by template "
90
+ "`{}` at position {}".format(name, match.string, match.start())
91
+ )
92
+ elif positional is not None:
93
+ try:
94
+ return str(positional_vars.pop(0))
95
+ except IndexError:
96
+ raise ValueError(
97
+ "Positional variable not specified and needed by template "
98
+ "`{}` at position {}".format(match.string, match.start())
99
+ )
100
+ else:
101
+ raise ValueError("Unknown template expression {}".format(match.group(0)))
102
+
103
+
104
+ def expand(tmpl, *args, **kwargs):
105
+ """Expand a path template with the given variables.
106
+
107
+ .. code-block:: python
108
+
109
+ >>> expand('users/*/messages/*', 'me', '123')
110
+ users/me/messages/123
111
+ >>> expand('/v1/{name=shelves/*/books/*}', name='shelves/1/books/3')
112
+ /v1/shelves/1/books/3
113
+
114
+ Args:
115
+ tmpl (str): The path template.
116
+ args: The positional variables for the path.
117
+ kwargs: The named variables for the path.
118
+
119
+ Returns:
120
+ str: The expanded path
121
+
122
+ Raises:
123
+ ValueError: If a positional or named variable is required by the
124
+ template but not specified or if an unexpected template expression
125
+ is encountered.
126
+ """
127
+ replacer = functools.partial(_expand_variable_match, list(args), kwargs)
128
+ return _VARIABLE_RE.sub(replacer, tmpl)
129
+
130
+
131
+ def _replace_variable_with_pattern(match):
132
+ """Replace a variable match with a pattern that can be used to validate it.
133
+
134
+ Args:
135
+ match (re.Match): A regular expression match
136
+
137
+ Returns:
138
+ str: A regular expression pattern that can be used to validate the
139
+ variable in an expanded path.
140
+
141
+ Raises:
142
+ ValueError: If an unexpected template expression is encountered.
143
+ """
144
+ positional = match.group("positional")
145
+ name = match.group("name")
146
+ template = match.group("template")
147
+ if name is not None:
148
+ if not template:
149
+ return _SINGLE_SEGMENT_PATTERN.format(name)
150
+ elif template == "**":
151
+ return _MULTI_SEGMENT_PATTERN.format(name)
152
+ else:
153
+ return _generate_pattern_for_template(template)
154
+ elif positional == "*":
155
+ return _SINGLE_SEGMENT_PATTERN
156
+ elif positional == "**":
157
+ return _MULTI_SEGMENT_PATTERN
158
+ else:
159
+ raise ValueError("Unknown template expression {}".format(match.group(0)))
160
+
161
+
162
+ def _generate_pattern_for_template(tmpl):
163
+ """Generate a pattern that can validate a path template.
164
+
165
+ Args:
166
+ tmpl (str): The path template
167
+
168
+ Returns:
169
+ str: A regular expression pattern that can be used to validate an
170
+ expanded path template.
171
+ """
172
+ return _VARIABLE_RE.sub(_replace_variable_with_pattern, tmpl)
173
+
174
+
175
+ def get_field(request, field):
176
+ """Get the value of a field from a given dictionary.
177
+
178
+ Args:
179
+ request (dict | Message): A dictionary or a Message object.
180
+ field (str): The key to the request in dot notation.
181
+
182
+ Returns:
183
+ The value of the field.
184
+ """
185
+ parts = field.split(".")
186
+ value = request
187
+
188
+ for part in parts:
189
+ if not isinstance(value, dict):
190
+ value = getattr(value, part, None)
191
+ else:
192
+ value = value.get(part)
193
+ if isinstance(value, dict):
194
+ return
195
+ return value
196
+
197
+
198
+ def delete_field(request, field):
199
+ """Delete the value of a field from a given dictionary.
200
+
201
+ Args:
202
+ request (dict | Message): A dictionary object or a Message.
203
+ field (str): The key to the request in dot notation.
204
+ """
205
+ parts = deque(field.split("."))
206
+ while len(parts) > 1:
207
+ part = parts.popleft()
208
+ if not isinstance(request, dict):
209
+ if hasattr(request, part):
210
+ request = getattr(request, part, None)
211
+ else:
212
+ return
213
+ else:
214
+ request = request.get(part)
215
+ part = parts.popleft()
216
+ if not isinstance(request, dict):
217
+ if hasattr(request, part):
218
+ request.ClearField(part)
219
+ else:
220
+ return
221
+ else:
222
+ request.pop(part, None)
223
+
224
+
225
+ def validate(tmpl, path):
226
+ """Validate a path against the path template.
227
+
228
+ .. code-block:: python
229
+
230
+ >>> validate('users/*/messages/*', 'users/me/messages/123')
231
+ True
232
+ >>> validate('users/*/messages/*', 'users/me/drafts/123')
233
+ False
234
+ >>> validate('/v1/{name=shelves/*/books/*}', /v1/shelves/1/books/3)
235
+ True
236
+ >>> validate('/v1/{name=shelves/*/books/*}', /v1/shelves/1/tapes/3)
237
+ False
238
+
239
+ Args:
240
+ tmpl (str): The path template.
241
+ path (str): The expanded path.
242
+
243
+ Returns:
244
+ bool: True if the path matches.
245
+ """
246
+ pattern = _generate_pattern_for_template(tmpl) + "$"
247
+ return True if re.match(pattern, path) is not None else False
248
+
249
+
250
+ def transcode(http_options, message=None, **request_kwargs):
251
+ """Transcodes a grpc request pattern into a proper HTTP request following the rules outlined here,
252
+ https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L44-L312
253
+
254
+ Args:
255
+ http_options (list(dict)): A list of dicts which consist of these keys,
256
+ 'method' (str): The http method
257
+ 'uri' (str): The path template
258
+ 'body' (str): The body field name (optional)
259
+ (This is a simplified representation of the proto option `google.api.http`)
260
+
261
+ message (Message) : A request object (optional)
262
+ request_kwargs (dict) : A dict representing the request object
263
+
264
+ Returns:
265
+ dict: The transcoded request with these keys,
266
+ 'method' (str) : The http method
267
+ 'uri' (str) : The expanded uri
268
+ 'body' (dict | Message) : A dict or a Message representing the body (optional)
269
+ 'query_params' (dict | Message) : A dict or Message mapping query parameter variables and values
270
+
271
+ Raises:
272
+ ValueError: If the request does not match the given template.
273
+ """
274
+ transcoded_value = message or request_kwargs
275
+ bindings = []
276
+ for http_option in http_options:
277
+ request = {}
278
+
279
+ # Assign path
280
+ uri_template = http_option["uri"]
281
+ fields = [
282
+ (m.group("name"), m.group("template"))
283
+ for m in _VARIABLE_RE.finditer(uri_template)
284
+ ]
285
+ bindings.append((uri_template, fields))
286
+
287
+ path_args = {field: get_field(transcoded_value, field) for field, _ in fields}
288
+ request["uri"] = expand(uri_template, **path_args)
289
+
290
+ if not validate(uri_template, request["uri"]) or not all(path_args.values()):
291
+ continue
292
+
293
+ # Remove fields used in uri path from request
294
+ leftovers = copy.deepcopy(transcoded_value)
295
+ for path_field, _ in fields:
296
+ delete_field(leftovers, path_field)
297
+
298
+ # Assign body and query params
299
+ body = http_option.get("body")
300
+
301
+ if body:
302
+ if body == "*":
303
+ request["body"] = leftovers
304
+ if message:
305
+ request["query_params"] = message.__class__()
306
+ else:
307
+ request["query_params"] = {}
308
+ else:
309
+ try:
310
+ if message:
311
+ request["body"] = getattr(leftovers, body)
312
+ delete_field(leftovers, body)
313
+ else:
314
+ request["body"] = leftovers.pop(body)
315
+ except (KeyError, AttributeError):
316
+ continue
317
+ request["query_params"] = leftovers
318
+ else:
319
+ request["query_params"] = leftovers
320
+ request["method"] = http_option["method"]
321
+ return request
322
+
323
+ bindings_description = [
324
+ '\n\tURI: "{}"'
325
+ "\n\tRequired request fields:\n\t\t{}".format(
326
+ uri,
327
+ "\n\t\t".join(
328
+ [
329
+ 'field: "{}", pattern: "{}"'.format(n, p if p else "*")
330
+ for n, p in fields
331
+ ]
332
+ ),
333
+ )
334
+ for uri, fields in bindings
335
+ ]
336
+
337
+ raise ValueError(
338
+ "Invalid request."
339
+ "\nSome of the fields of the request message are either not initialized or "
340
+ "initialized with an invalid value."
341
+ "\nPlease make sure your request matches at least one accepted HTTP binding."
342
+ "\nTo match a binding the request message must have all the required fields "
343
+ "initialized with values matching their patterns as listed below:{}".format(
344
+ "\n".join(bindings_description)
345
+ )
346
+ )
py311/lib/python3.11/site-packages/google/api_core/protobuf_helpers.py ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2017 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Helpers for :mod:`protobuf`."""
16
+
17
+ import collections
18
+ import collections.abc
19
+ import copy
20
+ import inspect
21
+
22
+ from google.protobuf import field_mask_pb2
23
+ from google.protobuf import message
24
+ from google.protobuf import wrappers_pb2
25
+
26
+
27
+ _SENTINEL = object()
28
+ _WRAPPER_TYPES = (
29
+ wrappers_pb2.BoolValue,
30
+ wrappers_pb2.BytesValue,
31
+ wrappers_pb2.DoubleValue,
32
+ wrappers_pb2.FloatValue,
33
+ wrappers_pb2.Int32Value,
34
+ wrappers_pb2.Int64Value,
35
+ wrappers_pb2.StringValue,
36
+ wrappers_pb2.UInt32Value,
37
+ wrappers_pb2.UInt64Value,
38
+ )
39
+
40
+
41
+ def from_any_pb(pb_type, any_pb):
42
+ """Converts an ``Any`` protobuf to the specified message type.
43
+
44
+ Args:
45
+ pb_type (type): the type of the message that any_pb stores an instance
46
+ of.
47
+ any_pb (google.protobuf.any_pb2.Any): the object to be converted.
48
+
49
+ Returns:
50
+ pb_type: An instance of the pb_type message.
51
+
52
+ Raises:
53
+ TypeError: if the message could not be converted.
54
+ """
55
+ msg = pb_type()
56
+
57
+ # Unwrap proto-plus wrapped messages.
58
+ if callable(getattr(pb_type, "pb", None)):
59
+ msg_pb = pb_type.pb(msg)
60
+ else:
61
+ msg_pb = msg
62
+
63
+ # Unpack the Any object and populate the protobuf message instance.
64
+ if not any_pb.Unpack(msg_pb):
65
+ raise TypeError(
66
+ f"Could not convert `{any_pb.TypeName()}` with underlying type `google.protobuf.any_pb2.Any` to `{msg_pb.DESCRIPTOR.full_name}`"
67
+ )
68
+
69
+ # Done; return the message.
70
+ return msg
71
+
72
+
73
+ def check_oneof(**kwargs):
74
+ """Raise ValueError if more than one keyword argument is not ``None``.
75
+
76
+ Args:
77
+ kwargs (dict): The keyword arguments sent to the function.
78
+
79
+ Raises:
80
+ ValueError: If more than one entry in ``kwargs`` is not ``None``.
81
+ """
82
+ # Sanity check: If no keyword arguments were sent, this is fine.
83
+ if not kwargs:
84
+ return
85
+
86
+ not_nones = [val for val in kwargs.values() if val is not None]
87
+ if len(not_nones) > 1:
88
+ raise ValueError(
89
+ "Only one of {fields} should be set.".format(
90
+ fields=", ".join(sorted(kwargs.keys()))
91
+ )
92
+ )
93
+
94
+
95
+ def get_messages(module):
96
+ """Discovers all protobuf Message classes in a given import module.
97
+
98
+ Args:
99
+ module (module): A Python module; :func:`dir` will be run against this
100
+ module to find Message subclasses.
101
+
102
+ Returns:
103
+ dict[str, google.protobuf.message.Message]: A dictionary with the
104
+ Message class names as keys, and the Message subclasses themselves
105
+ as values.
106
+ """
107
+ answer = collections.OrderedDict()
108
+ for name in dir(module):
109
+ candidate = getattr(module, name)
110
+ if inspect.isclass(candidate) and issubclass(candidate, message.Message):
111
+ answer[name] = candidate
112
+ return answer
113
+
114
+
115
+ def _resolve_subkeys(key, separator="."):
116
+ """Resolve a potentially nested key.
117
+
118
+ If the key contains the ``separator`` (e.g. ``.``) then the key will be
119
+ split on the first instance of the subkey::
120
+
121
+ >>> _resolve_subkeys('a.b.c')
122
+ ('a', 'b.c')
123
+ >>> _resolve_subkeys('d|e|f', separator='|')
124
+ ('d', 'e|f')
125
+
126
+ If not, the subkey will be :data:`None`::
127
+
128
+ >>> _resolve_subkeys('foo')
129
+ ('foo', None)
130
+
131
+ Args:
132
+ key (str): A string that may or may not contain the separator.
133
+ separator (str): The namespace separator. Defaults to `.`.
134
+
135
+ Returns:
136
+ Tuple[str, str]: The key and subkey(s).
137
+ """
138
+ parts = key.split(separator, 1)
139
+
140
+ if len(parts) > 1:
141
+ return parts
142
+ else:
143
+ return parts[0], None
144
+
145
+
146
+ def get(msg_or_dict, key, default=_SENTINEL):
147
+ """Retrieve a key's value from a protobuf Message or dictionary.
148
+
149
+ Args:
150
+ mdg_or_dict (Union[~google.protobuf.message.Message, Mapping]): the
151
+ object.
152
+ key (str): The key to retrieve from the object.
153
+ default (Any): If the key is not present on the object, and a default
154
+ is set, returns that default instead. A type-appropriate falsy
155
+ default is generally recommended, as protobuf messages almost
156
+ always have default values for unset values and it is not always
157
+ possible to tell the difference between a falsy value and an
158
+ unset one. If no default is set then :class:`KeyError` will be
159
+ raised if the key is not present in the object.
160
+
161
+ Returns:
162
+ Any: The return value from the underlying Message or dict.
163
+
164
+ Raises:
165
+ KeyError: If the key is not found. Note that, for unset values,
166
+ messages and dictionaries may not have consistent behavior.
167
+ TypeError: If ``msg_or_dict`` is not a Message or Mapping.
168
+ """
169
+ # We may need to get a nested key. Resolve this.
170
+ key, subkey = _resolve_subkeys(key)
171
+
172
+ # Attempt to get the value from the two types of objects we know about.
173
+ # If we get something else, complain.
174
+ if isinstance(msg_or_dict, message.Message):
175
+ answer = getattr(msg_or_dict, key, default)
176
+ elif isinstance(msg_or_dict, collections.abc.Mapping):
177
+ answer = msg_or_dict.get(key, default)
178
+ else:
179
+ raise TypeError(
180
+ "get() expected a dict or protobuf message, got {!r}.".format(
181
+ type(msg_or_dict)
182
+ )
183
+ )
184
+
185
+ # If the object we got back is our sentinel, raise KeyError; this is
186
+ # a "not found" case.
187
+ if answer is _SENTINEL:
188
+ raise KeyError(key)
189
+
190
+ # If a subkey exists, call this method recursively against the answer.
191
+ if subkey is not None and answer is not default:
192
+ return get(answer, subkey, default=default)
193
+
194
+ return answer
195
+
196
+
197
+ def _set_field_on_message(msg, key, value):
198
+ """Set helper for protobuf Messages."""
199
+ # Attempt to set the value on the types of objects we know how to deal
200
+ # with.
201
+ if isinstance(value, (collections.abc.MutableSequence, tuple)):
202
+ # Clear the existing repeated protobuf message of any elements
203
+ # currently inside it.
204
+ while getattr(msg, key):
205
+ getattr(msg, key).pop()
206
+
207
+ # Write our new elements to the repeated field.
208
+ for item in value:
209
+ if isinstance(item, collections.abc.Mapping):
210
+ getattr(msg, key).add(**item)
211
+ else:
212
+ # protobuf's RepeatedCompositeContainer doesn't support
213
+ # append.
214
+ getattr(msg, key).extend([item])
215
+ elif isinstance(value, collections.abc.Mapping):
216
+ # Assign the dictionary values to the protobuf message.
217
+ for item_key, item_value in value.items():
218
+ set(getattr(msg, key), item_key, item_value)
219
+ elif isinstance(value, message.Message):
220
+ getattr(msg, key).CopyFrom(value)
221
+ else:
222
+ setattr(msg, key, value)
223
+
224
+
225
+ def set(msg_or_dict, key, value):
226
+ """Set a key's value on a protobuf Message or dictionary.
227
+
228
+ Args:
229
+ msg_or_dict (Union[~google.protobuf.message.Message, Mapping]): the
230
+ object.
231
+ key (str): The key to set.
232
+ value (Any): The value to set.
233
+
234
+ Raises:
235
+ TypeError: If ``msg_or_dict`` is not a Message or dictionary.
236
+ """
237
+ # Sanity check: Is our target object valid?
238
+ if not isinstance(msg_or_dict, (collections.abc.MutableMapping, message.Message)):
239
+ raise TypeError(
240
+ "set() expected a dict or protobuf message, got {!r}.".format(
241
+ type(msg_or_dict)
242
+ )
243
+ )
244
+
245
+ # We may be setting a nested key. Resolve this.
246
+ basekey, subkey = _resolve_subkeys(key)
247
+
248
+ # If a subkey exists, then get that object and call this method
249
+ # recursively against it using the subkey.
250
+ if subkey is not None:
251
+ if isinstance(msg_or_dict, collections.abc.MutableMapping):
252
+ msg_or_dict.setdefault(basekey, {})
253
+ set(get(msg_or_dict, basekey), subkey, value)
254
+ return
255
+
256
+ if isinstance(msg_or_dict, collections.abc.MutableMapping):
257
+ msg_or_dict[key] = value
258
+ else:
259
+ _set_field_on_message(msg_or_dict, key, value)
260
+
261
+
262
+ def setdefault(msg_or_dict, key, value):
263
+ """Set the key on a protobuf Message or dictionary to a given value if the
264
+ current value is falsy.
265
+
266
+ Because protobuf Messages do not distinguish between unset values and
267
+ falsy ones particularly well (by design), this method treats any falsy
268
+ value (e.g. 0, empty list) as a target to be overwritten, on both Messages
269
+ and dictionaries.
270
+
271
+ Args:
272
+ msg_or_dict (Union[~google.protobuf.message.Message, Mapping]): the
273
+ object.
274
+ key (str): The key on the object in question.
275
+ value (Any): The value to set.
276
+
277
+ Raises:
278
+ TypeError: If ``msg_or_dict`` is not a Message or dictionary.
279
+ """
280
+ if not get(msg_or_dict, key, default=None):
281
+ set(msg_or_dict, key, value)
282
+
283
+
284
+ def field_mask(original, modified):
285
+ """Create a field mask by comparing two messages.
286
+
287
+ Args:
288
+ original (~google.protobuf.message.Message): the original message.
289
+ If set to None, this field will be interpreted as an empty
290
+ message.
291
+ modified (~google.protobuf.message.Message): the modified message.
292
+ If set to None, this field will be interpreted as an empty
293
+ message.
294
+
295
+ Returns:
296
+ google.protobuf.field_mask_pb2.FieldMask: field mask that contains
297
+ the list of field names that have different values between the two
298
+ messages. If the messages are equivalent, then the field mask is empty.
299
+
300
+ Raises:
301
+ ValueError: If the ``original`` or ``modified`` are not the same type.
302
+ """
303
+ if original is None and modified is None:
304
+ return field_mask_pb2.FieldMask()
305
+
306
+ if original is None and modified is not None:
307
+ original = copy.deepcopy(modified)
308
+ original.Clear()
309
+
310
+ if modified is None and original is not None:
311
+ modified = copy.deepcopy(original)
312
+ modified.Clear()
313
+
314
+ if not isinstance(original, type(modified)):
315
+ raise ValueError(
316
+ "expected that both original and modified should be of the "
317
+ 'same type, received "{!r}" and "{!r}".'.format(
318
+ type(original), type(modified)
319
+ )
320
+ )
321
+
322
+ return field_mask_pb2.FieldMask(paths=_field_mask_helper(original, modified))
323
+
324
+
325
+ def _field_mask_helper(original, modified, current=""):
326
+ answer = []
327
+
328
+ for name in original.DESCRIPTOR.fields_by_name:
329
+ field_path = _get_path(current, name)
330
+
331
+ original_val = getattr(original, name)
332
+ modified_val = getattr(modified, name)
333
+
334
+ if _is_message(original_val) or _is_message(modified_val):
335
+ if original_val != modified_val:
336
+ # Wrapper types do not need to include the .value part of the
337
+ # path.
338
+ if _is_wrapper(original_val) or _is_wrapper(modified_val):
339
+ answer.append(field_path)
340
+ elif not modified_val.ListFields():
341
+ answer.append(field_path)
342
+ else:
343
+ answer.extend(
344
+ _field_mask_helper(original_val, modified_val, field_path)
345
+ )
346
+ else:
347
+ if original_val != modified_val:
348
+ answer.append(field_path)
349
+
350
+ return answer
351
+
352
+
353
+ def _get_path(current, name):
354
+ # gapic-generator-python appends underscores to field names
355
+ # that collide with python keywords.
356
+ # `_` is stripped away as it is not possible to
357
+ # natively define a field with a trailing underscore in protobuf.
358
+ # APIs will reject field masks if fields have trailing underscores.
359
+ # See https://github.com/googleapis/python-api-core/issues/227
360
+ name = name.rstrip("_")
361
+ if not current:
362
+ return name
363
+ return "%s.%s" % (current, name)
364
+
365
+
366
+ def _is_message(value):
367
+ return isinstance(value, message.Message)
368
+
369
+
370
+ def _is_wrapper(value):
371
+ return type(value) in _WRAPPER_TYPES
py311/lib/python3.11/site-packages/google/api_core/py.typed ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Marker file for PEP 561.
2
+ # The google-api-core package uses inline types.