koichi12 commited on
Commit
4adbfd5
·
verified ·
1 Parent(s): 7d4012e

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. .venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/INSTALLER +1 -0
  2. .venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/LICENSE.txt +13 -0
  3. .venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/METADATA +250 -0
  4. .venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/RECORD +131 -0
  5. .venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/WHEEL +6 -0
  6. .venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/top_level.txt +1 -0
  7. .venv/lib/python3.11/site-packages/aiohttp_cors/__about__.py +28 -0
  8. .venv/lib/python3.11/site-packages/aiohttp_cors/__init__.py +67 -0
  9. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/__about__.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/__init__.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/abc.cpython-311.pyc +0 -0
  12. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/cors_config.cpython-311.pyc +0 -0
  13. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/mixin.cpython-311.pyc +0 -0
  14. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/preflight_handler.cpython-311.pyc +0 -0
  15. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/resource_options.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/urldispatcher_router_adapter.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/aiohttp_cors/abc.py +100 -0
  18. .venv/lib/python3.11/site-packages/aiohttp_cors/cors_config.py +263 -0
  19. .venv/lib/python3.11/site-packages/aiohttp_cors/mixin.py +47 -0
  20. .venv/lib/python3.11/site-packages/aiohttp_cors/preflight_handler.py +130 -0
  21. .venv/lib/python3.11/site-packages/aiohttp_cors/resource_options.py +153 -0
  22. .venv/lib/python3.11/site-packages/aiohttp_cors/urldispatcher_router_adapter.py +324 -0
  23. .venv/lib/python3.11/site-packages/frozenlist/__init__.py +98 -0
  24. .venv/lib/python3.11/site-packages/frozenlist/__init__.pyi +47 -0
  25. .venv/lib/python3.11/site-packages/frozenlist/__pycache__/__init__.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/frozenlist/_frozenlist.pyx +123 -0
  27. .venv/lib/python3.11/site-packages/frozenlist/py.typed +1 -0
  28. .venv/lib/python3.11/site-packages/googleapiclient/__init__.py +27 -0
  29. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/__init__.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/_auth.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/_helpers.cpython-311.pyc +0 -0
  32. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/channel.cpython-311.pyc +0 -0
  33. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/discovery.cpython-311.pyc +0 -0
  34. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/errors.cpython-311.pyc +0 -0
  35. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/http.cpython-311.pyc +0 -0
  36. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/mimeparse.cpython-311.pyc +0 -0
  37. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/model.cpython-311.pyc +0 -0
  38. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/sample_tools.cpython-311.pyc +0 -0
  39. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/schema.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/googleapiclient/__pycache__/version.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/googleapiclient/_auth.py +167 -0
  42. .venv/lib/python3.11/site-packages/googleapiclient/_helpers.py +207 -0
  43. .venv/lib/python3.11/site-packages/googleapiclient/channel.py +315 -0
  44. .venv/lib/python3.11/site-packages/googleapiclient/discovery.py +1662 -0
  45. .venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__init__.py +78 -0
  46. .venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/appengine_memcache.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/base.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/file_cache.cpython-311.pyc +0 -0
  50. .venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/appengine_memcache.py +55 -0
.venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
.venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright aio-libs contributors.
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.
.venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/METADATA ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.2
2
+ Name: aiohttp
3
+ Version: 3.11.12
4
+ Summary: Async http client/server framework (asyncio)
5
+ Home-page: https://github.com/aio-libs/aiohttp
6
+ Maintainer: aiohttp team <team@aiohttp.org>
7
+ Maintainer-email: team@aiohttp.org
8
+ License: Apache-2.0
9
+ Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org
10
+ Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org
11
+ Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
12
+ Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiohttp
13
+ Project-URL: Docs: Changelog, https://docs.aiohttp.org/en/stable/changes.html
14
+ Project-URL: Docs: RTD, https://docs.aiohttp.org
15
+ Project-URL: GitHub: issues, https://github.com/aio-libs/aiohttp/issues
16
+ Project-URL: GitHub: repo, https://github.com/aio-libs/aiohttp
17
+ Classifier: Development Status :: 5 - Production/Stable
18
+ Classifier: Framework :: AsyncIO
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: License :: OSI Approved :: Apache Software License
21
+ Classifier: Operating System :: POSIX
22
+ Classifier: Operating System :: MacOS :: MacOS X
23
+ Classifier: Operating System :: Microsoft :: Windows
24
+ Classifier: Programming Language :: Python
25
+ Classifier: Programming Language :: Python :: 3
26
+ Classifier: Programming Language :: Python :: 3.9
27
+ Classifier: Programming Language :: Python :: 3.10
28
+ Classifier: Programming Language :: Python :: 3.11
29
+ Classifier: Programming Language :: Python :: 3.12
30
+ Classifier: Programming Language :: Python :: 3.13
31
+ Classifier: Topic :: Internet :: WWW/HTTP
32
+ Requires-Python: >=3.9
33
+ Description-Content-Type: text/x-rst
34
+ License-File: LICENSE.txt
35
+ Requires-Dist: aiohappyeyeballs>=2.3.0
36
+ Requires-Dist: aiosignal>=1.1.2
37
+ Requires-Dist: async-timeout<6.0,>=4.0; python_version < "3.11"
38
+ Requires-Dist: attrs>=17.3.0
39
+ Requires-Dist: frozenlist>=1.1.1
40
+ Requires-Dist: multidict<7.0,>=4.5
41
+ Requires-Dist: propcache>=0.2.0
42
+ Requires-Dist: yarl<2.0,>=1.17.0
43
+ Provides-Extra: speedups
44
+ Requires-Dist: aiodns>=3.2.0; (sys_platform == "linux" or sys_platform == "darwin") and extra == "speedups"
45
+ Requires-Dist: Brotli; platform_python_implementation == "CPython" and extra == "speedups"
46
+ Requires-Dist: brotlicffi; platform_python_implementation != "CPython" and extra == "speedups"
47
+
48
+ ==================================
49
+ Async http client/server framework
50
+ ==================================
51
+
52
+ .. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/aiohttp-plain.svg
53
+ :height: 64px
54
+ :width: 64px
55
+ :alt: aiohttp logo
56
+
57
+ |
58
+
59
+ .. image:: https://github.com/aio-libs/aiohttp/workflows/CI/badge.svg
60
+ :target: https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
61
+ :alt: GitHub Actions status for master branch
62
+
63
+ .. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg
64
+ :target: https://codecov.io/gh/aio-libs/aiohttp
65
+ :alt: codecov.io status for master branch
66
+
67
+ .. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
68
+ :target: https://codspeed.io/aio-libs/aiohttp
69
+ :alt: Codspeed.io status for aiohttp
70
+
71
+ .. image:: https://badge.fury.io/py/aiohttp.svg
72
+ :target: https://pypi.org/project/aiohttp
73
+ :alt: Latest PyPI package version
74
+
75
+ .. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest
76
+ :target: https://docs.aiohttp.org/
77
+ :alt: Latest Read The Docs
78
+
79
+ .. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
80
+ :target: https://matrix.to/#/%23aio-libs:matrix.org
81
+ :alt: Matrix Room — #aio-libs:matrix.org
82
+
83
+ .. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
84
+ :target: https://matrix.to/#/%23aio-libs-space:matrix.org
85
+ :alt: Matrix Space — #aio-libs-space:matrix.org
86
+
87
+
88
+ Key Features
89
+ ============
90
+
91
+ - Supports both client and server side of HTTP protocol.
92
+ - Supports both client and server Web-Sockets out-of-the-box and avoids
93
+ Callback Hell.
94
+ - Provides Web-server with middleware and pluggable routing.
95
+
96
+
97
+ Getting started
98
+ ===============
99
+
100
+ Client
101
+ ------
102
+
103
+ To get something from the web:
104
+
105
+ .. code-block:: python
106
+
107
+ import aiohttp
108
+ import asyncio
109
+
110
+ async def main():
111
+
112
+ async with aiohttp.ClientSession() as session:
113
+ async with session.get('http://python.org') as response:
114
+
115
+ print("Status:", response.status)
116
+ print("Content-type:", response.headers['content-type'])
117
+
118
+ html = await response.text()
119
+ print("Body:", html[:15], "...")
120
+
121
+ asyncio.run(main())
122
+
123
+ This prints:
124
+
125
+ .. code-block::
126
+
127
+ Status: 200
128
+ Content-type: text/html; charset=utf-8
129
+ Body: <!doctype html> ...
130
+
131
+ Coming from `requests <https://requests.readthedocs.io/>`_ ? Read `why we need so many lines <https://aiohttp.readthedocs.io/en/latest/http_request_lifecycle.html>`_.
132
+
133
+ Server
134
+ ------
135
+
136
+ An example using a simple server:
137
+
138
+ .. code-block:: python
139
+
140
+ # examples/server_simple.py
141
+ from aiohttp import web
142
+
143
+ async def handle(request):
144
+ name = request.match_info.get('name', "Anonymous")
145
+ text = "Hello, " + name
146
+ return web.Response(text=text)
147
+
148
+ async def wshandle(request):
149
+ ws = web.WebSocketResponse()
150
+ await ws.prepare(request)
151
+
152
+ async for msg in ws:
153
+ if msg.type == web.WSMsgType.text:
154
+ await ws.send_str("Hello, {}".format(msg.data))
155
+ elif msg.type == web.WSMsgType.binary:
156
+ await ws.send_bytes(msg.data)
157
+ elif msg.type == web.WSMsgType.close:
158
+ break
159
+
160
+ return ws
161
+
162
+
163
+ app = web.Application()
164
+ app.add_routes([web.get('/', handle),
165
+ web.get('/echo', wshandle),
166
+ web.get('/{name}', handle)])
167
+
168
+ if __name__ == '__main__':
169
+ web.run_app(app)
170
+
171
+
172
+ Documentation
173
+ =============
174
+
175
+ https://aiohttp.readthedocs.io/
176
+
177
+
178
+ Demos
179
+ =====
180
+
181
+ https://github.com/aio-libs/aiohttp-demos
182
+
183
+
184
+ External links
185
+ ==============
186
+
187
+ * `Third party libraries
188
+ <http://aiohttp.readthedocs.io/en/latest/third_party.html>`_
189
+ * `Built with aiohttp
190
+ <http://aiohttp.readthedocs.io/en/latest/built_with.html>`_
191
+ * `Powered by aiohttp
192
+ <http://aiohttp.readthedocs.io/en/latest/powered_by.html>`_
193
+
194
+ Feel free to make a Pull Request for adding your link to these pages!
195
+
196
+
197
+ Communication channels
198
+ ======================
199
+
200
+ *aio-libs Discussions*: https://github.com/aio-libs/aiohttp/discussions
201
+
202
+ *Matrix*: `#aio-libs:matrix.org <https://matrix.to/#/#aio-libs:matrix.org>`_
203
+
204
+ We support `Stack Overflow
205
+ <https://stackoverflow.com/questions/tagged/aiohttp>`_.
206
+ Please add *aiohttp* tag to your question there.
207
+
208
+ Requirements
209
+ ============
210
+
211
+ - attrs_
212
+ - multidict_
213
+ - yarl_
214
+ - frozenlist_
215
+
216
+ Optionally you may install the aiodns_ library (highly recommended for sake of speed).
217
+
218
+ .. _aiodns: https://pypi.python.org/pypi/aiodns
219
+ .. _attrs: https://github.com/python-attrs/attrs
220
+ .. _multidict: https://pypi.python.org/pypi/multidict
221
+ .. _frozenlist: https://pypi.org/project/frozenlist/
222
+ .. _yarl: https://pypi.python.org/pypi/yarl
223
+ .. _async-timeout: https://pypi.python.org/pypi/async_timeout
224
+
225
+ License
226
+ =======
227
+
228
+ ``aiohttp`` is offered under the Apache 2 license.
229
+
230
+
231
+ Keepsafe
232
+ ========
233
+
234
+ The aiohttp community would like to thank Keepsafe
235
+ (https://www.getkeepsafe.com) for its support in the early days of
236
+ the project.
237
+
238
+
239
+ Source code
240
+ ===========
241
+
242
+ The latest developer version is available in a GitHub repository:
243
+ https://github.com/aio-libs/aiohttp
244
+
245
+ Benchmarks
246
+ ==========
247
+
248
+ If you are interested in efficiency, the AsyncIO community maintains a
249
+ list of benchmarks on the official wiki:
250
+ https://github.com/python/asyncio/wiki/Benchmarks
.venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/RECORD ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohttp-3.11.12.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ aiohttp-3.11.12.dist-info/LICENSE.txt,sha256=n4DQ2311WpQdtFchcsJw7L2PCCuiFd3QlZhZQu2Uqes,588
3
+ aiohttp-3.11.12.dist-info/METADATA,sha256=d7xgT5HQPw4R1cWYIK6lQbOoCkFYV5505hAhDZWALcA,7712
4
+ aiohttp-3.11.12.dist-info/RECORD,,
5
+ aiohttp-3.11.12.dist-info/WHEEL,sha256=bWgrYGwmr1oqID2askDfuFruhD_BtRXYD33vDqPTClc,151
6
+ aiohttp-3.11.12.dist-info/top_level.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8
7
+ aiohttp/.hash/_cparser.pxd.hash,sha256=hYa9Vje-oMs2eh_7MfCPOh2QW_1x1yCjcZuc7AmwLd0,121
8
+ aiohttp/.hash/_find_header.pxd.hash,sha256=_mbpD6vM-CVCKq3ulUvsOAz5Wdo88wrDzfpOsMQaMNA,125
9
+ aiohttp/.hash/_http_parser.pyx.hash,sha256=GBgZjCNbtZApPhf9-gHpS5Z2WMIzM-vgp5VSZIEvZfk,125
10
+ aiohttp/.hash/_http_writer.pyx.hash,sha256=-UgSF82qclpxjP0og_gcFEsstXRKF9e3Ou4wziAyDvI,125
11
+ aiohttp/.hash/hdrs.py.hash,sha256=v6IaKbsxjsdQxBzhb5AjP0x_9G3rUe84D7avf7AI4cs,116
12
+ aiohttp/__init__.py,sha256=NFDgrak_EkgNNzRQ9Ka63rIzkN_IYocFcIERyltx5ys,7840
13
+ aiohttp/__pycache__/__init__.cpython-311.pyc,,
14
+ aiohttp/__pycache__/abc.cpython-311.pyc,,
15
+ aiohttp/__pycache__/base_protocol.cpython-311.pyc,,
16
+ aiohttp/__pycache__/client.cpython-311.pyc,,
17
+ aiohttp/__pycache__/client_exceptions.cpython-311.pyc,,
18
+ aiohttp/__pycache__/client_proto.cpython-311.pyc,,
19
+ aiohttp/__pycache__/client_reqrep.cpython-311.pyc,,
20
+ aiohttp/__pycache__/client_ws.cpython-311.pyc,,
21
+ aiohttp/__pycache__/compression_utils.cpython-311.pyc,,
22
+ aiohttp/__pycache__/connector.cpython-311.pyc,,
23
+ aiohttp/__pycache__/cookiejar.cpython-311.pyc,,
24
+ aiohttp/__pycache__/formdata.cpython-311.pyc,,
25
+ aiohttp/__pycache__/hdrs.cpython-311.pyc,,
26
+ aiohttp/__pycache__/helpers.cpython-311.pyc,,
27
+ aiohttp/__pycache__/http.cpython-311.pyc,,
28
+ aiohttp/__pycache__/http_exceptions.cpython-311.pyc,,
29
+ aiohttp/__pycache__/http_parser.cpython-311.pyc,,
30
+ aiohttp/__pycache__/http_websocket.cpython-311.pyc,,
31
+ aiohttp/__pycache__/http_writer.cpython-311.pyc,,
32
+ aiohttp/__pycache__/log.cpython-311.pyc,,
33
+ aiohttp/__pycache__/multipart.cpython-311.pyc,,
34
+ aiohttp/__pycache__/payload.cpython-311.pyc,,
35
+ aiohttp/__pycache__/payload_streamer.cpython-311.pyc,,
36
+ aiohttp/__pycache__/pytest_plugin.cpython-311.pyc,,
37
+ aiohttp/__pycache__/resolver.cpython-311.pyc,,
38
+ aiohttp/__pycache__/streams.cpython-311.pyc,,
39
+ aiohttp/__pycache__/tcp_helpers.cpython-311.pyc,,
40
+ aiohttp/__pycache__/test_utils.cpython-311.pyc,,
41
+ aiohttp/__pycache__/tracing.cpython-311.pyc,,
42
+ aiohttp/__pycache__/typedefs.cpython-311.pyc,,
43
+ aiohttp/__pycache__/web.cpython-311.pyc,,
44
+ aiohttp/__pycache__/web_app.cpython-311.pyc,,
45
+ aiohttp/__pycache__/web_exceptions.cpython-311.pyc,,
46
+ aiohttp/__pycache__/web_fileresponse.cpython-311.pyc,,
47
+ aiohttp/__pycache__/web_log.cpython-311.pyc,,
48
+ aiohttp/__pycache__/web_middlewares.cpython-311.pyc,,
49
+ aiohttp/__pycache__/web_protocol.cpython-311.pyc,,
50
+ aiohttp/__pycache__/web_request.cpython-311.pyc,,
51
+ aiohttp/__pycache__/web_response.cpython-311.pyc,,
52
+ aiohttp/__pycache__/web_routedef.cpython-311.pyc,,
53
+ aiohttp/__pycache__/web_runner.cpython-311.pyc,,
54
+ aiohttp/__pycache__/web_server.cpython-311.pyc,,
55
+ aiohttp/__pycache__/web_urldispatcher.cpython-311.pyc,,
56
+ aiohttp/__pycache__/web_ws.cpython-311.pyc,,
57
+ aiohttp/__pycache__/worker.cpython-311.pyc,,
58
+ aiohttp/_cparser.pxd,sha256=8jGIg-VJ9p3llwCakUYDsPGxA4HiZe9dmK9Jmtlz-5g,4318
59
+ aiohttp/_find_header.pxd,sha256=0GfwFCPN2zxEKTO1_MA5sYq2UfzsG8kcV3aTqvwlz3g,68
60
+ aiohttp/_headers.pxi,sha256=n701k28dVPjwRnx5j6LpJhLTfj7dqu2vJt7f0O60Oyg,2007
61
+ aiohttp/_http_parser.cpython-311-x86_64-linux-gnu.so,sha256=rulksFv5PZ7AOPoQuR2P1THFflfukMumxkEfRfdFk0Y,2826344
62
+ aiohttp/_http_parser.pyx,sha256=wQdADj5LizwC_7nFGr8nIlk6GpoaQeQ0359H0HMKGuM,28241
63
+ aiohttp/_http_writer.cpython-311-x86_64-linux-gnu.so,sha256=zMYCtCYo-xjYJpSCcVx6UdBpGSHm_pMgnpM7JUmsWXA,463752
64
+ aiohttp/_http_writer.pyx,sha256=fiCck_EVgRiTX7VtAoV2AldjuesJMFPev4TWd9Fx8jo,4597
65
+ aiohttp/_websocket/.hash/mask.pxd.hash,sha256=Y0zBddk_ck3pi9-BFzMcpkcvCKvwvZ4GTtZFb9u1nxQ,128
66
+ aiohttp/_websocket/.hash/mask.pyx.hash,sha256=90owpXYM8_kIma4KUcOxhWSk-Uv4NVMBoCYeFM1B3d0,128
67
+ aiohttp/_websocket/.hash/reader_c.pxd.hash,sha256=EoZjkF_tAFEbGvV0oRY2GZOSuAfWFWFjMhXgq6mQExo,132
68
+ aiohttp/_websocket/__init__.py,sha256=Mar3R9_vBN_Ea4lsW7iTAVXD7OKswKPGqF5xgSyt77k,44
69
+ aiohttp/_websocket/__pycache__/__init__.cpython-311.pyc,,
70
+ aiohttp/_websocket/__pycache__/helpers.cpython-311.pyc,,
71
+ aiohttp/_websocket/__pycache__/models.cpython-311.pyc,,
72
+ aiohttp/_websocket/__pycache__/reader.cpython-311.pyc,,
73
+ aiohttp/_websocket/__pycache__/reader_c.cpython-311.pyc,,
74
+ aiohttp/_websocket/__pycache__/reader_py.cpython-311.pyc,,
75
+ aiohttp/_websocket/__pycache__/writer.cpython-311.pyc,,
76
+ aiohttp/_websocket/helpers.py,sha256=P-XLv8IUaihKzDenVUqfKU5DJbWE5HvG8uhvUZK8Ic4,5038
77
+ aiohttp/_websocket/mask.cpython-311-x86_64-linux-gnu.so,sha256=hq3aHe5ZVl5ENFRtaXjZcSrbS-ITBwqGgEneGVphY1w,245952
78
+ aiohttp/_websocket/mask.pxd,sha256=sBmZ1Amym9kW4Ge8lj1fLZ7mPPya4LzLdpkQExQXv5M,112
79
+ aiohttp/_websocket/mask.pyx,sha256=BHjOtV0O0w7xp9p0LNADRJvGmgfPn9sGeJvSs0fL__4,1397
80
+ aiohttp/_websocket/models.py,sha256=XAzjs_8JYszWXIgZ6R3ZRrF-tX9Q_6LiD49WRYojopM,2121
81
+ aiohttp/_websocket/reader.py,sha256=eC4qS0c5sOeQ2ebAHLaBpIaTVFaSKX79pY2xvh3Pqyw,1030
82
+ aiohttp/_websocket/reader_c.cpython-311-x86_64-linux-gnu.so,sha256=bnETOmm4gSxKkarM68sAEhBjSd_bQt0GgDD2W7V8cUQ,1906008
83
+ aiohttp/_websocket/reader_c.pxd,sha256=9rMWCpAC1jng7_gtqLjRlqQv9q7UkOn63tIQfq2k8Gc,2444
84
+ aiohttp/_websocket/reader_c.py,sha256=anZsBKZWlL8SO8gArsZMDstH37qBuZOvJA7jtj0Z95M,17975
85
+ aiohttp/_websocket/reader_py.py,sha256=anZsBKZWlL8SO8gArsZMDstH37qBuZOvJA7jtj0Z95M,17975
86
+ aiohttp/_websocket/writer.py,sha256=T3P36iMrzVPPC2XeScserHMD5vd9an6yizWzqDUkRZ0,7077
87
+ aiohttp/abc.py,sha256=JLMOxrKLGTDaPRLfraY1pl-xka53YiHhAH9yaF9QRXQ,6512
88
+ aiohttp/base_protocol.py,sha256=Tp8cxUPQvv9kUPk3w6lAzk6d2MAzV3scwI_3Go3C47c,3025
89
+ aiohttp/client.py,sha256=isdfGlM4O5ILr4F4gBABlybxo4MQ1tNaMm7zjMcrfrM,54309
90
+ aiohttp/client_exceptions.py,sha256=uyKbxI2peZhKl7lELBMx3UeusNkfpemPWpGFq0r6JeM,11367
91
+ aiohttp/client_proto.py,sha256=dV7u9floGWG-_xtD2fLUYqiANG6VsJtq0HMlTjf1g-g,10015
92
+ aiohttp/client_reqrep.py,sha256=VAgh0NxP2HvYWx6nX1Pr8FINc1m-W8-5q2zKeZV68n8,43925
93
+ aiohttp/client_ws.py,sha256=_n4hVk71H5rK8TFOIYT0bPTIHOmMCQ3FDFSrU7ctpfI,15031
94
+ aiohttp/compression_utils.py,sha256=0J3EAOR-0HehlYIudJXRu_Kr6hrYCY0IfuJ1px9MhQs,5681
95
+ aiohttp/connector.py,sha256=yW8vyZz4cmXbScbBkCneMF0giSl0WZPJ2NnNw-TegbQ,60225
96
+ aiohttp/cookiejar.py,sha256=PYR1K1mkLa24Hm6c9UEJnAitccNzz97CbsJyQ2ULAlU,17615
97
+ aiohttp/formdata.py,sha256=CUJnCWDNHFcXSYZ_TupaT6rHkY-Q7ghssvWzaYBPIo0,6552
98
+ aiohttp/hdrs.py,sha256=2rj5MyA-6yRdYPhW5UKkW4iNWhEAlGIOSBH5D4FmKNE,5111
99
+ aiohttp/helpers.py,sha256=KqPQECeiJ_EhA93k7-5ZoVdZH0sk_4n0tCoM_E-iMnE,29091
100
+ aiohttp/http.py,sha256=8o8j8xH70OWjnfTWA9V44NR785QPxEPrUtzMXiAVpwc,1842
101
+ aiohttp/http_exceptions.py,sha256=RYmBycJvvPerKkgXXm8v145I1N-fbsgSpcsbNIC-gdE,2961
102
+ aiohttp/http_parser.py,sha256=UqerYPJzA1MqLmeG1jURhTNO1YhwUASK3QVcNEz0me8,36851
103
+ aiohttp/http_websocket.py,sha256=8VXFKw6KQUEmPg48GtRMB37v0gTK7A0inoxXuDxMZEc,842
104
+ aiohttp/http_writer.py,sha256=pRIyfOmL3cZmdWDWBBJ2cZEwEJzLWzlPPAJInaPLThI,7595
105
+ aiohttp/log.py,sha256=BbNKx9e3VMIm0xYjZI0IcBBoS7wjdeIeSaiJE7-qK2g,325
106
+ aiohttp/multipart.py,sha256=SABIvo3vhXzG4bLDZ0C4V3yG_86vAb-3Zb9Li7BVmI8,36944
107
+ aiohttp/payload.py,sha256=rCA9JJI_RMCik_7qNIaC1Bh21aXhABGYK2tsYeaHRQ4,15793
108
+ aiohttp/payload_streamer.py,sha256=ZzEYyfzcjGWkVkK3XR2pBthSCSIykYvY3Wr5cGQ2eTc,2211
109
+ aiohttp/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
110
+ aiohttp/pytest_plugin.py,sha256=AfJ6VIWzsp5KgpXRREsX3yqGUZrJyfb7zzcMqzWxz7I,12768
111
+ aiohttp/resolver.py,sha256=sJ8-LYCtl_g9f6gn_5X2NFQ9FQ72Q2Mr4_rLxo9NVeI,6375
112
+ aiohttp/streams.py,sha256=U-qTkuAqIfpJChuKEy-vYn8nQ_Z1MVcW0WO2DHiJz_o,22329
113
+ aiohttp/tcp_helpers.py,sha256=BSadqVWaBpMFDRWnhaaR941N9MiDZ7bdTrxgCb0CW-M,961
114
+ aiohttp/test_utils.py,sha256=r7kBasmZtC3tQY5OmyMaIl1B9P8Bnnq1oM3npVcAPKs,22811
115
+ aiohttp/tracing.py,sha256=66XQwtdR5DHv8p953eeNL0l8o6iHDaNwH9bBaybHXD4,15137
116
+ aiohttp/typedefs.py,sha256=wUlqwe9Mw9W8jT3HsYJcYk00qP3EMPz3nTkYXmeNN48,1657
117
+ aiohttp/web.py,sha256=As5nqGQy4QXWMXSaOsh0JudSVVJVIt_nr3n0b8CaMb0,18422
118
+ aiohttp/web_app.py,sha256=Zre0QHM9JAp4d7jrj5NRxlPnfTrKLNuA42Rdsh8Q2TI,19554
119
+ aiohttp/web_exceptions.py,sha256=7nIuiwhZ39vJJ9KrWqArA5QcWbUdqkz2CLwEpJapeN8,10360
120
+ aiohttp/web_fileresponse.py,sha256=FRsS0p9r1KU5y8ceG0QXBYnrL6xggjbxcXSmI6qIR4k,16504
121
+ aiohttp/web_log.py,sha256=rX5D7xLOX2B6BMdiZ-chme_KfJfW5IXEoFwLfkfkajs,7865
122
+ aiohttp/web_middlewares.py,sha256=sFI0AgeNjdyAjuz92QtMIpngmJSOxrqe2Jfbs4BNUu0,4165
123
+ aiohttp/web_protocol.py,sha256=PMCLcrUlC_-2U9ZxnMyWFiaekNvbswmXiIdmhRcMLHM,25630
124
+ aiohttp/web_request.py,sha256=j_SSX9s-d3ZeNyqUTpFIaPUaNdSqHwb7yfc0ufL8xFA,29750
125
+ aiohttp/web_response.py,sha256=65aliDETi7rZ8P76ksuHQI0ZTu1cKpclCSailNu105M,28696
126
+ aiohttp/web_routedef.py,sha256=VT1GAx6BrawoDh5RwBwBu5wSABSqgWwAe74AUCyZAEo,6110
127
+ aiohttp/web_runner.py,sha256=v1G1nKiOOQgFnTSR4IMc6I9ReEFDMaHtMLvO_roDM-A,11786
128
+ aiohttp/web_server.py,sha256=-9WDKUAiR9ll-rSdwXSqG6YjaoW79d1R4y0BGSqgUMA,2888
129
+ aiohttp/web_urldispatcher.py,sha256=TIMxFmhLjERseG0xcZv2Ef9Xuo_GTBRqBqeMkCgL0K8,43825
130
+ aiohttp/web_ws.py,sha256=EOQX3LYqlrkNQHlFTaNpZkQpOYRCZfR-m1bHT4Iseq8,22488
131
+ aiohttp/worker.py,sha256=0lvxRNMjGM47ddlQWtci53ri9YN42Su1Vdd_Z7zMMH0,8040
.venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/WHEEL ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp311-cp311-manylinux_2_17_x86_64
5
+ Tag: cp311-cp311-manylinux2014_x86_64
6
+
.venv/lib/python3.11/site-packages/aiohttp-3.11.12.dist-info/top_level.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ aiohttp
.venv/lib/python3.11/site-packages/aiohttp_cors/__about__.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
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
+ """
16
+ Library meta information.
17
+
18
+ This module must be stand-alone executable.
19
+ """
20
+
21
+ __title__ = "aiohttp-cors"
22
+ __version__ = "0.7.0"
23
+ __author__ = "Vladimir Rutsky and aio-libs team"
24
+ __email__ = "vladimir@rutsky.org"
25
+ __summary__ = "CORS support for aiohttp"
26
+ __uri__ = "https://github.com/aio-libs/aiohttp-cors"
27
+ __license__ = "Apache License, Version 2.0"
28
+ __copyright__ = "2015-2018 {}".format(__author__)
.venv/lib/python3.11/site-packages/aiohttp_cors/__init__.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
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
+ """CORS support for aiohttp.
16
+ """
17
+
18
+ from typing import Mapping, Union, Any
19
+
20
+ from aiohttp import web
21
+
22
+ from .__about__ import (
23
+ __title__, __version__, __author__, __email__, __summary__, __uri__,
24
+ __license__, __copyright__,
25
+ )
26
+ from .resource_options import ResourceOptions
27
+ from .cors_config import CorsConfig
28
+ from .mixin import CorsViewMixin, custom_cors
29
+
30
+ __all__ = (
31
+ "__title__", "__version__", "__author__", "__email__", "__summary__",
32
+ "__uri__", "__license__", "__copyright__",
33
+ "setup", "CorsConfig", "ResourceOptions", "CorsViewMixin", "custom_cors"
34
+ )
35
+
36
+
37
+ APP_CONFIG_KEY = "aiohttp_cors"
38
+
39
+
40
+ def setup(app: web.Application, *,
41
+ defaults: Mapping[str, Union[ResourceOptions,
42
+ Mapping[str, Any]]]=None) -> CorsConfig:
43
+ """Setup CORS processing for the application.
44
+
45
+ To enable CORS for a resource you need to explicitly add route for
46
+ that resource using `CorsConfig.add()` method::
47
+
48
+ app = aiohttp.web.Application()
49
+ cors = aiohttp_cors.setup(app)
50
+ cors.add(
51
+ app.router.add_route("GET", "/resource", handler),
52
+ {
53
+ "*": aiohttp_cors.ResourceOptions(
54
+ allow_credentials=True,
55
+ expose_headers="*",
56
+ allow_headers="*"),
57
+ })
58
+
59
+ :param app:
60
+ The application for which CORS will be configured.
61
+ :param defaults:
62
+ Default settings for origins.
63
+ )
64
+ """
65
+ cors = CorsConfig(app, defaults=defaults)
66
+ app[APP_CONFIG_KEY] = cors
67
+ return cors
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/__about__.cpython-311.pyc ADDED
Binary file (693 Bytes). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (2.15 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/abc.cpython-311.pyc ADDED
Binary file (4.33 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/cors_config.cpython-311.pyc ADDED
Binary file (9.28 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/mixin.cpython-311.pyc ADDED
Binary file (2.63 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/preflight_handler.cpython-311.pyc ADDED
Binary file (5.25 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/resource_options.cpython-311.pyc ADDED
Binary file (6.04 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/__pycache__/urldispatcher_router_adapter.cpython-311.pyc ADDED
Binary file (11.6 kB). View file
 
.venv/lib/python3.11/site-packages/aiohttp_cors/abc.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
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
+ """Abstract base classes.
16
+ """
17
+
18
+ from abc import ABCMeta, abstractmethod
19
+
20
+ from aiohttp import web
21
+
22
+
23
+ __all__ = ("AbstractRouterAdapter",)
24
+
25
+
26
+ class AbstractRouterAdapter(metaclass=ABCMeta):
27
+ """Router adapter for handling CORS configuration interface.
28
+
29
+ `AbstractRouter` doesn't specify how HTTP requests are delivered
30
+ to handlers, and aiohttp_cors doesn't rely on specific implementation
31
+ details.
32
+
33
+ In general Router can be seen as a substance that allows to setup handlers
34
+ for specific HTTP methods and requests paths, lets call these Router's
35
+ items routing entities.
36
+ Generic Router is configured with set of routing entities and their
37
+ handlers.
38
+
39
+ This adapter assumes that its reasonable to configure CORS for same
40
+ routing entities as used in `AbstractRouter`.
41
+ Routing entities will be added to CorsConfig to enable CORS for them.
42
+
43
+ For example, for aiohttp < 0.21.0 routing entity would be
44
+ `aiohttp.web.Route` — tuple of (HTTP method, URI path).
45
+ And CORS can be configured for each `aiohttp.web.Route`.
46
+
47
+ In aiohttp >= 0.21.0 there are two routing entities: Resource and Route.
48
+ You can configure CORS for Resource (which will be interpreted as default
49
+ for all Routes on Resoures), and configure CORS for specific Route.
50
+ """
51
+
52
+ @abstractmethod
53
+ def add_preflight_handler(self,
54
+ routing_entity,
55
+ handler,
56
+ webview: bool=False):
57
+ """Add OPTIONS handler for all routes defined by `routing_entity`.
58
+
59
+ Does nothing if CORS handler already handles routing entity.
60
+ Should fail if there are conflicting user-defined OPTIONS handlers.
61
+ """
62
+
63
+ @abstractmethod
64
+ def is_preflight_request(self, request: web.Request) -> bool:
65
+ """Is `request` is a CORS preflight request."""
66
+
67
+ @abstractmethod
68
+ def is_cors_enabled_on_request(self, request: web.Request) -> bool:
69
+ """Is `request` is a request for CORS-enabled resource."""
70
+
71
+ @abstractmethod
72
+ def set_config_for_routing_entity(self,
73
+ routing_entity,
74
+ config):
75
+ """Record configuration for routing entity.
76
+
77
+ If router implements hierarchical routing entities, stored config
78
+ can be used in hierarchical manner too.
79
+
80
+ Should raise if there is conflicting configuration for the routing
81
+ entity.
82
+ """
83
+
84
+ @abstractmethod
85
+ async def get_preflight_request_config(
86
+ self,
87
+ preflight_request: web.Request,
88
+ origin: str,
89
+ requested_method: str):
90
+ """Get stored CORS configuration for specified HTTP method and origin
91
+ that corresponds to preflight request.
92
+
93
+ Should raise KeyError if CORS is not configured or not enabled
94
+ for specified HTTP method.
95
+ """
96
+
97
+ @abstractmethod
98
+ def get_non_preflight_request_config(self, request: web.Request):
99
+ """Get stored CORS configuration for routing entity that handles
100
+ specified request."""
.venv/lib/python3.11/site-packages/aiohttp_cors/cors_config.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
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
+ """CORS configuration container class definition.
16
+ """
17
+
18
+ import collections
19
+ import warnings
20
+ from typing import Mapping, Union, Any
21
+
22
+ from aiohttp import hdrs, web
23
+
24
+ from .urldispatcher_router_adapter import ResourcesUrlDispatcherRouterAdapter
25
+ from .abc import AbstractRouterAdapter
26
+ from .resource_options import ResourceOptions
27
+ from .preflight_handler import _PreflightHandler
28
+
29
+ __all__ = (
30
+ "CorsConfig",
31
+ )
32
+
33
+ # Positive response to Access-Control-Allow-Credentials
34
+ _TRUE = "true"
35
+ # CORS simple response headers:
36
+ # <http://www.w3.org/TR/cors/#simple-response-header>
37
+ _SIMPLE_RESPONSE_HEADERS = frozenset([
38
+ hdrs.CACHE_CONTROL,
39
+ hdrs.CONTENT_LANGUAGE,
40
+ hdrs.CONTENT_TYPE,
41
+ hdrs.EXPIRES,
42
+ hdrs.LAST_MODIFIED,
43
+ hdrs.PRAGMA
44
+ ])
45
+
46
+
47
+ def _parse_config_options(
48
+ config: Mapping[str, Union[ResourceOptions, Mapping[str, Any]]]=None):
49
+ """Parse CORS configuration (default or per-route)
50
+
51
+ :param config:
52
+ Mapping from Origin to Resource configuration (allowed headers etc)
53
+ defined either as mapping or `ResourceOptions` instance.
54
+
55
+ Raises `ValueError` if configuration is not correct.
56
+ """
57
+
58
+ if config is None:
59
+ return {}
60
+
61
+ if not isinstance(config, collections.abc.Mapping):
62
+ raise ValueError(
63
+ "Config must be mapping, got '{}'".format(config))
64
+
65
+ parsed = {}
66
+
67
+ options_keys = {
68
+ "allow_credentials", "expose_headers", "allow_headers", "max_age"
69
+ }
70
+
71
+ for origin, options in config.items():
72
+ # TODO: check that all origins are properly formatted.
73
+ # This is not a security issue, since origin is compared as strings.
74
+ if not isinstance(origin, str):
75
+ raise ValueError(
76
+ "Origin must be string, got '{}'".format(origin))
77
+
78
+ if isinstance(options, ResourceOptions):
79
+ resource_options = options
80
+
81
+ else:
82
+ if not isinstance(options, collections.abc.Mapping):
83
+ raise ValueError(
84
+ "Origin options must be either "
85
+ "aiohttp_cors.ResourceOptions instance or mapping, "
86
+ "got '{}'".format(options))
87
+
88
+ unexpected_args = frozenset(options.keys()) - options_keys
89
+ if unexpected_args:
90
+ raise ValueError(
91
+ "Unexpected keywords in resource options: {}".format(
92
+ # pylint: disable=bad-builtin
93
+ ",".join(map(str, unexpected_args))))
94
+
95
+ resource_options = ResourceOptions(**options)
96
+
97
+ parsed[origin] = resource_options
98
+
99
+ return parsed
100
+
101
+
102
+ _ConfigType = Mapping[str, Union[ResourceOptions, Mapping[str, Any]]]
103
+
104
+
105
+ class _CorsConfigImpl(_PreflightHandler):
106
+
107
+ def __init__(self,
108
+ app: web.Application,
109
+ router_adapter: AbstractRouterAdapter):
110
+ self._app = app
111
+
112
+ self._router_adapter = router_adapter
113
+
114
+ # Register hook for all responses. This hook handles CORS-related
115
+ # headers on non-preflight requests.
116
+ self._app.on_response_prepare.append(self._on_response_prepare)
117
+
118
+ def add(self,
119
+ routing_entity,
120
+ config: _ConfigType=None):
121
+ """Enable CORS for specific route or resource.
122
+
123
+ If route is passed CORS is enabled for route's resource.
124
+
125
+ :param routing_entity:
126
+ Route or Resource for which CORS should be enabled.
127
+ :param config:
128
+ CORS options for the route.
129
+ :return: `routing_entity`.
130
+ """
131
+
132
+ parsed_config = _parse_config_options(config)
133
+
134
+ self._router_adapter.add_preflight_handler(
135
+ routing_entity, self._preflight_handler)
136
+ self._router_adapter.set_config_for_routing_entity(
137
+ routing_entity, parsed_config)
138
+
139
+ return routing_entity
140
+
141
+ async def _on_response_prepare(self,
142
+ request: web.Request,
143
+ response: web.StreamResponse):
144
+ """Non-preflight CORS request response processor.
145
+
146
+ If request is done on CORS-enabled route, process request parameters
147
+ and set appropriate CORS response headers.
148
+ """
149
+ if (not self._router_adapter.is_cors_enabled_on_request(request) or
150
+ self._router_adapter.is_preflight_request(request)):
151
+ # Either not CORS enabled route, or preflight request which is
152
+ # handled in its own handler.
153
+ return
154
+
155
+ # Processing response of non-preflight CORS-enabled request.
156
+
157
+ config = self._router_adapter.get_non_preflight_request_config(request)
158
+
159
+ # Handle according to part 6.1 of the CORS specification.
160
+
161
+ origin = request.headers.get(hdrs.ORIGIN)
162
+ if origin is None:
163
+ # Terminate CORS according to CORS 6.1.1.
164
+ return
165
+
166
+ options = config.get(origin, config.get("*"))
167
+ if options is None:
168
+ # Terminate CORS according to CORS 6.1.2.
169
+ return
170
+
171
+ assert hdrs.ACCESS_CONTROL_ALLOW_ORIGIN not in response.headers
172
+ assert hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS not in response.headers
173
+ assert hdrs.ACCESS_CONTROL_EXPOSE_HEADERS not in response.headers
174
+
175
+ # Process according to CORS 6.1.4.
176
+ # Set exposed headers (server headers exposed to client) before
177
+ # setting any other headers.
178
+ if options.expose_headers == "*":
179
+ # Expose all headers that are set in response.
180
+ exposed_headers = \
181
+ frozenset(response.headers.keys()) - _SIMPLE_RESPONSE_HEADERS
182
+ response.headers[hdrs.ACCESS_CONTROL_EXPOSE_HEADERS] = \
183
+ ",".join(exposed_headers)
184
+
185
+ elif options.expose_headers:
186
+ # Expose predefined list of headers.
187
+ response.headers[hdrs.ACCESS_CONTROL_EXPOSE_HEADERS] = \
188
+ ",".join(options.expose_headers)
189
+
190
+ # Process according to CORS 6.1.3.
191
+ # Set allowed origin.
192
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin
193
+ if options.allow_credentials:
194
+ # Set allowed credentials.
195
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE
196
+
197
+ async def _get_config(self, request, origin, request_method):
198
+ config = \
199
+ await self._router_adapter.get_preflight_request_config(
200
+ request, origin, request_method)
201
+ return config
202
+
203
+
204
+ class CorsConfig:
205
+ """CORS configuration instance.
206
+
207
+ The instance holds default CORS parameters and per-route options specified
208
+ in `add()` method.
209
+
210
+ Each `aiohttp.web.Application` can have exactly one instance of this class.
211
+ """
212
+
213
+ def __init__(self, app: web.Application, *,
214
+ defaults: _ConfigType=None,
215
+ router_adapter: AbstractRouterAdapter=None):
216
+ """Construct CORS configuration.
217
+
218
+ :param app:
219
+ Application for which CORS configuration is built.
220
+ :param defaults:
221
+ Default CORS settings for origins.
222
+ :param router_adapter:
223
+ Router adapter. Required if application uses non-default router.
224
+ """
225
+
226
+ self.defaults = _parse_config_options(defaults)
227
+
228
+ self._cors_impl = None
229
+
230
+ self._resources_router_adapter = None
231
+ self._resources_cors_impl = None
232
+
233
+ self._old_routes_cors_impl = None
234
+
235
+ if router_adapter is None:
236
+ router_adapter = \
237
+ ResourcesUrlDispatcherRouterAdapter(app.router, self.defaults)
238
+
239
+ self._cors_impl = _CorsConfigImpl(app, router_adapter)
240
+
241
+ def add(self,
242
+ routing_entity,
243
+ config: _ConfigType = None,
244
+ webview: bool=False):
245
+ """Enable CORS for specific route or resource.
246
+
247
+ If route is passed CORS is enabled for route's resource.
248
+
249
+ :param routing_entity:
250
+ Route or Resource for which CORS should be enabled.
251
+ :param config:
252
+ CORS options for the route.
253
+ :return: `routing_entity`.
254
+ """
255
+
256
+ if webview:
257
+ warnings.warn('webview argument is deprecated, '
258
+ 'views are handled authomatically without '
259
+ 'extra settings',
260
+ DeprecationWarning,
261
+ stacklevel=2)
262
+
263
+ return self._cors_impl.add(routing_entity, config)
.venv/lib/python3.11/site-packages/aiohttp_cors/mixin.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+
3
+ from .preflight_handler import _PreflightHandler
4
+
5
+
6
+ def custom_cors(config):
7
+ def wrapper(function):
8
+ name = "{}_cors_config".format(function.__name__)
9
+ setattr(function, name, config)
10
+ return function
11
+ return wrapper
12
+
13
+
14
+ class CorsViewMixin(_PreflightHandler):
15
+ cors_config = None
16
+
17
+ @classmethod
18
+ def get_request_config(cls, request, request_method):
19
+ try:
20
+ from . import APP_CONFIG_KEY
21
+ cors = request.app[APP_CONFIG_KEY]
22
+ except KeyError:
23
+ raise ValueError("aiohttp-cors is not configured.")
24
+
25
+ method = getattr(cls, request_method.lower(), None)
26
+
27
+ if not method:
28
+ raise KeyError()
29
+
30
+ config_property_key = "{}_cors_config".format(request_method.lower())
31
+
32
+ custom_config = getattr(method, config_property_key, None)
33
+ if not custom_config:
34
+ custom_config = {}
35
+
36
+ class_config = cls.cors_config
37
+ if not class_config:
38
+ class_config = {}
39
+
40
+ return collections.ChainMap(custom_config, class_config, cors.defaults)
41
+
42
+ async def _get_config(self, request, origin, request_method):
43
+ return self.get_request_config(request, request_method)
44
+
45
+ async def options(self):
46
+ response = await self._preflight_handler(self.request)
47
+ return response
.venv/lib/python3.11/site-packages/aiohttp_cors/preflight_handler.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from aiohttp import hdrs, web
2
+
3
+ # Positive response to Access-Control-Allow-Credentials
4
+ _TRUE = "true"
5
+
6
+
7
+ class _PreflightHandler:
8
+
9
+ @staticmethod
10
+ def _parse_request_method(request: web.Request):
11
+ """Parse Access-Control-Request-Method header of the preflight request
12
+ """
13
+ method = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_METHOD)
14
+ if method is None:
15
+ raise web.HTTPForbidden(
16
+ text="CORS preflight request failed: "
17
+ "'Access-Control-Request-Method' header is not specified")
18
+
19
+ # FIXME: validate method string (ABNF: method = token), if parsing
20
+ # fails, raise HTTPForbidden.
21
+
22
+ return method
23
+
24
+ @staticmethod
25
+ def _parse_request_headers(request: web.Request):
26
+ """Parse Access-Control-Request-Headers header or the preflight request
27
+
28
+ Returns set of headers in upper case.
29
+ """
30
+ headers = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_HEADERS)
31
+ if headers is None:
32
+ return frozenset()
33
+
34
+ # FIXME: validate each header string, if parsing fails, raise
35
+ # HTTPForbidden.
36
+ # FIXME: check, that headers split and stripped correctly (according
37
+ # to ABNF).
38
+ headers = (h.strip(" \t").upper() for h in headers.split(","))
39
+ # pylint: disable=bad-builtin
40
+ return frozenset(filter(None, headers))
41
+
42
+ async def _get_config(self, request, origin, request_method):
43
+ raise NotImplementedError()
44
+
45
+ async def _preflight_handler(self, request: web.Request):
46
+ """CORS preflight request handler"""
47
+
48
+ # Handle according to part 6.2 of the CORS specification.
49
+
50
+ origin = request.headers.get(hdrs.ORIGIN)
51
+ if origin is None:
52
+ # Terminate CORS according to CORS 6.2.1.
53
+ raise web.HTTPForbidden(
54
+ text="CORS preflight request failed: "
55
+ "origin header is not specified in the request")
56
+
57
+ # CORS 6.2.3. Doing it out of order is not an error.
58
+ request_method = self._parse_request_method(request)
59
+
60
+ # CORS 6.2.5. Doing it out of order is not an error.
61
+
62
+ try:
63
+ config = \
64
+ await self._get_config(request, origin, request_method)
65
+ except KeyError:
66
+ raise web.HTTPForbidden(
67
+ text="CORS preflight request failed: "
68
+ "request method {!r} is not allowed "
69
+ "for {!r} origin".format(request_method, origin))
70
+
71
+ if not config:
72
+ # No allowed origins for the route.
73
+ # Terminate CORS according to CORS 6.2.1.
74
+ raise web.HTTPForbidden(
75
+ text="CORS preflight request failed: "
76
+ "no origins are allowed")
77
+
78
+ options = config.get(origin, config.get("*"))
79
+ if options is None:
80
+ # No configuration for the origin - deny.
81
+ # Terminate CORS according to CORS 6.2.2.
82
+ raise web.HTTPForbidden(
83
+ text="CORS preflight request failed: "
84
+ "origin '{}' is not allowed".format(origin))
85
+
86
+ # CORS 6.2.4
87
+ request_headers = self._parse_request_headers(request)
88
+
89
+ # CORS 6.2.6
90
+ if options.allow_headers == "*":
91
+ pass
92
+ else:
93
+ disallowed_headers = request_headers - options.allow_headers
94
+ if disallowed_headers:
95
+ raise web.HTTPForbidden(
96
+ text="CORS preflight request failed: "
97
+ "headers are not allowed: {}".format(
98
+ ", ".join(disallowed_headers)))
99
+
100
+ # Ok, CORS actual request with specified in the preflight request
101
+ # parameters is allowed.
102
+ # Set appropriate headers and return 200 response.
103
+
104
+ response = web.Response()
105
+
106
+ # CORS 6.2.7
107
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin
108
+ if options.allow_credentials:
109
+ # Set allowed credentials.
110
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE
111
+
112
+ # CORS 6.2.8
113
+ if options.max_age is not None:
114
+ response.headers[hdrs.ACCESS_CONTROL_MAX_AGE] = \
115
+ str(options.max_age)
116
+
117
+ # CORS 6.2.9
118
+ # TODO: more optimal for client preflight request cache would be to
119
+ # respond with ALL allowed methods.
120
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_METHODS] = request_method
121
+
122
+ # CORS 6.2.10
123
+ if request_headers:
124
+ # Note: case of the headers in the request is changed, but this
125
+ # shouldn't be a problem, since the headers should be compared in
126
+ # the case-insensitive way.
127
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS] = \
128
+ ",".join(request_headers)
129
+
130
+ return response
.venv/lib/python3.11/site-packages/aiohttp_cors/resource_options.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
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
+ """Resource CORS options class definition.
16
+ """
17
+
18
+ import numbers
19
+ import collections
20
+ import collections.abc
21
+
22
+ __all__ = ("ResourceOptions",)
23
+
24
+
25
+ def _is_proper_sequence(seq):
26
+ """Returns is seq is sequence and not string."""
27
+ return (isinstance(seq, collections.abc.Sequence) and
28
+ not isinstance(seq, str))
29
+
30
+
31
+ class ResourceOptions(collections.namedtuple(
32
+ "Base",
33
+ ("allow_credentials", "expose_headers", "allow_headers", "max_age",
34
+ "allow_methods"))):
35
+ """Resource CORS options."""
36
+
37
+ __slots__ = ()
38
+
39
+ def __init__(self, *, allow_credentials=False, expose_headers=(),
40
+ allow_headers=(), max_age=None, allow_methods=None):
41
+ """Construct resource CORS options.
42
+
43
+ Options will be normalized.
44
+
45
+ :param allow_credentials:
46
+ Is passing client credentials to the resource from other origin
47
+ is allowed.
48
+ See <http://www.w3.org/TR/cors/#user-credentials> for
49
+ the definition.
50
+ :type allow_credentials: bool
51
+ Is passing client credentials to the resource from other origin
52
+ is allowed.
53
+
54
+ :param expose_headers:
55
+ Server headers that are allowed to be exposed to the client.
56
+ Simple response headers are excluded from this set, see
57
+ <http://www.w3.org/TR/cors/#list-of-exposed-headers>.
58
+ :type expose_headers: sequence of strings or ``*`` string.
59
+
60
+ :param allow_headers:
61
+ Client headers that are allowed to be passed to the resource.
62
+ See <http://www.w3.org/TR/cors/#list-of-headers>.
63
+ :type allow_headers: sequence of strings or ``*`` string.
64
+
65
+ :param max_age:
66
+ How long the results of a preflight request can be cached in a
67
+ preflight result cache (in seconds).
68
+ See <http://www.w3.org/TR/cors/#http-access-control-max-age>.
69
+
70
+ :param allow_methods:
71
+ List of allowed methods or ``*``string. Can be used in resource or
72
+ global defaults, but not in specific route.
73
+
74
+ It's not required to specify all allowed methods for specific
75
+ resource, routes that have explicit CORS configuration will be
76
+ treated as if their methods are allowed.
77
+ """
78
+ super().__init__()
79
+
80
+ def __new__(cls, *, allow_credentials=False, expose_headers=(),
81
+ allow_headers=(), max_age=None, allow_methods=None):
82
+ """Normalize source parameters and store them in namedtuple."""
83
+
84
+ if not isinstance(allow_credentials, bool):
85
+ raise ValueError(
86
+ "'allow_credentials' must be boolean, "
87
+ "got '{!r}'".format(allow_credentials))
88
+ _allow_credentials = allow_credentials
89
+
90
+ # `expose_headers` is either "*", or sequence of strings.
91
+ if expose_headers == "*":
92
+ _expose_headers = expose_headers
93
+ elif not _is_proper_sequence(expose_headers):
94
+ raise ValueError(
95
+ "'expose_headers' must be either '*', or sequence of strings, "
96
+ "got '{!r}'".format(expose_headers))
97
+ elif expose_headers:
98
+ # "Access-Control-Expose-Headers" ":" #field-name
99
+ # TODO: Check that headers are valid.
100
+ # TODO: Remove headers that in the _SIMPLE_RESPONSE_HEADERS set
101
+ # according to
102
+ # <http://www.w3.org/TR/cors/#list-of-exposed-headers>.
103
+ _expose_headers = frozenset(expose_headers)
104
+ else:
105
+ # No headers exposed.
106
+ _expose_headers = frozenset()
107
+
108
+ # `allow_headers` is either "*", or set of headers in upper case.
109
+ if allow_headers == "*":
110
+ _allow_headers = allow_headers
111
+ elif not _is_proper_sequence(allow_headers):
112
+ raise ValueError(
113
+ "'allow_headers' must be either '*', or sequence of strings, "
114
+ "got '{!r}'".format(allow_headers))
115
+ else:
116
+ # TODO: Check that headers are valid.
117
+ _allow_headers = frozenset(h.upper() for h in allow_headers)
118
+
119
+ if max_age is None:
120
+ _max_age = None
121
+ else:
122
+ if not isinstance(max_age, numbers.Integral) or max_age < 0:
123
+ raise ValueError(
124
+ "'max_age' must be non-negative integer, "
125
+ "got '{!r}'".format(max_age))
126
+ _max_age = max_age
127
+
128
+ if allow_methods is None or allow_methods == "*":
129
+ _allow_methods = allow_methods
130
+ elif not _is_proper_sequence(allow_methods):
131
+ raise ValueError(
132
+ "'allow_methods' must be either '*', or sequence of strings, "
133
+ "got '{!r}'".format(allow_methods))
134
+ else:
135
+ # TODO: Check that methods are valid.
136
+ _allow_methods = frozenset(m.upper() for m in allow_methods)
137
+
138
+ return super().__new__(
139
+ cls,
140
+ allow_credentials=_allow_credentials,
141
+ expose_headers=_expose_headers,
142
+ allow_headers=_allow_headers,
143
+ max_age=_max_age,
144
+ allow_methods=_allow_methods)
145
+
146
+ def is_method_allowed(self, method):
147
+ if self.allow_methods is None:
148
+ return False
149
+
150
+ if self.allow_methods == '*':
151
+ return True
152
+
153
+ return method.upper() in self.allow_methods
.venv/lib/python3.11/site-packages/aiohttp_cors/urldispatcher_router_adapter.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
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
+ """AbstractRouterAdapter for aiohttp.web.UrlDispatcher.
16
+ """
17
+ import collections
18
+
19
+ from typing import Union
20
+
21
+ from aiohttp import web
22
+ from aiohttp import hdrs
23
+
24
+ from .abc import AbstractRouterAdapter
25
+ from .mixin import CorsViewMixin
26
+
27
+
28
+ # There several usage patterns of routes which should be handled
29
+ # differently.
30
+ #
31
+ # 1. Using new Resources:
32
+ #
33
+ # resource = app.router.add_resource(path)
34
+ # cors.add(resource, resource_defaults=...)
35
+ # cors.add(resource.add_route(method1, handler1), config=...)
36
+ # cors.add(resource.add_route(method2, handler2), config=...)
37
+ # cors.add(resource.add_route(method3, handler3), config=...)
38
+ #
39
+ # Here all related Routes (i.e. routes with the same path) are in
40
+ # a single Resource.
41
+ #
42
+ # 2. Using `router.add_static()`:
43
+ #
44
+ # route1 = app.router.add_static(
45
+ # "/images", "/usr/share/app/images/")
46
+ # cors.add(route1, config=...)
47
+ #
48
+ # Here old-style `web.StaticRoute` is created and wrapped with
49
+ # `web.ResourceAdapter`.
50
+ #
51
+ # 3. Using old `router.add_route()`:
52
+ #
53
+ # cors.add(app.router.add_route(method1, path, hand1), config=...)
54
+ # cors.add(app.router.add_route(method2, path, hand2), config=...)
55
+ # cors.add(app.router.add_route(method3, path, hand3), config=...)
56
+ #
57
+ # This creates three Resources with single Route in each.
58
+ #
59
+ # 4. Using deprecated `register_route` with manually created
60
+ # `web.Route`:
61
+ #
62
+ # route1 = RouteSubclass(...)
63
+ # app.router.register_route(route1)
64
+ # cors.add(route1, config=...)
65
+ #
66
+ # Here old-style route is wrapped with `web.ResourceAdapter`.
67
+ #
68
+ # Preflight requests is roughly an OPTIONS request with query
69
+ # "is specific HTTP method is allowed".
70
+ # In order to properly handle preflight request we need to know which
71
+ # routes have enabled CORS on the request path and CORS configuration
72
+ # for requested HTTP method.
73
+ #
74
+ # In case of new usage pattern it's simple: we need to take a look at
75
+ # self._resource_config[resource][method] for the processing resource.
76
+ #
77
+ # In case of old usage pattern we need to iterate over routes with
78
+ # enabled CORS and check is requested path and HTTP method is accepted
79
+ # by a route.
80
+
81
+
82
+ class _ResourceConfig:
83
+ def __init__(self, default_config):
84
+ # Resource default config.
85
+ self.default_config = default_config
86
+
87
+ # HTTP method to route configuration.
88
+ self.method_config = {}
89
+
90
+
91
+ def _is_web_view(entity, strict=True):
92
+ webview = False
93
+ if isinstance(entity, web.AbstractRoute):
94
+ handler = entity.handler
95
+ if isinstance(handler, type) and issubclass(handler, web.View):
96
+ webview = True
97
+ if not issubclass(handler, CorsViewMixin):
98
+ if strict:
99
+ raise ValueError("web view should be derived from "
100
+ "aiohttp_cors.WebViewMixig for working "
101
+ "with the library")
102
+ else:
103
+ return False
104
+ return webview
105
+
106
+
107
+ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
108
+ """Adapter for `UrlDispatcher` for Resources-based routing only.
109
+
110
+ Should be used with routes added in the following way:
111
+
112
+ resource = app.router.add_resource(path)
113
+ cors.add(resource, resource_defaults=...)
114
+ cors.add(resource.add_route(method1, handler1), config=...)
115
+ cors.add(resource.add_route(method2, handler2), config=...)
116
+ cors.add(resource.add_route(method3, handler3), config=...)
117
+ """
118
+
119
+ def __init__(self,
120
+ router: web.UrlDispatcher,
121
+ defaults):
122
+ """
123
+ :param defaults:
124
+ Default CORS configuration.
125
+ """
126
+ self._router = router
127
+
128
+ # Default configuration for all routes.
129
+ self._default_config = defaults
130
+
131
+ # Mapping from Resource to _ResourceConfig.
132
+ self._resource_config = {}
133
+
134
+ self._resources_with_preflight_handlers = set()
135
+ self._preflight_routes = set()
136
+
137
+ def add_preflight_handler(
138
+ self,
139
+ routing_entity: Union[web.Resource, web.StaticResource,
140
+ web.ResourceRoute],
141
+ handler):
142
+ """Add OPTIONS handler for all routes defined by `routing_entity`.
143
+
144
+ Does nothing if CORS handler already handles routing entity.
145
+ Should fail if there are conflicting user-defined OPTIONS handlers.
146
+ """
147
+
148
+ if isinstance(routing_entity, web.Resource):
149
+ resource = routing_entity
150
+
151
+ # Add preflight handler for Resource, if not yet added.
152
+
153
+ if resource in self._resources_with_preflight_handlers:
154
+ # Preflight handler already added for this resource.
155
+ return
156
+ for route_obj in resource:
157
+ if route_obj.method == hdrs.METH_OPTIONS:
158
+ if route_obj.handler is handler:
159
+ return # already added
160
+ else:
161
+ raise ValueError(
162
+ "{!r} already has OPTIONS handler {!r}"
163
+ .format(resource, route_obj.handler))
164
+ elif route_obj.method == hdrs.METH_ANY:
165
+ if _is_web_view(route_obj):
166
+ self._preflight_routes.add(route_obj)
167
+ self._resources_with_preflight_handlers.add(resource)
168
+ return
169
+ else:
170
+ raise ValueError("{!r} already has a '*' handler "
171
+ "for all methods".format(resource))
172
+
173
+ preflight_route = resource.add_route(hdrs.METH_OPTIONS, handler)
174
+ self._preflight_routes.add(preflight_route)
175
+ self._resources_with_preflight_handlers.add(resource)
176
+
177
+ elif isinstance(routing_entity, web.StaticResource):
178
+ resource = routing_entity
179
+
180
+ # Add preflight handler for Resource, if not yet added.
181
+
182
+ if resource in self._resources_with_preflight_handlers:
183
+ # Preflight handler already added for this resource.
184
+ return
185
+
186
+ resource.set_options_route(handler)
187
+ preflight_route = resource._routes[hdrs.METH_OPTIONS]
188
+ self._preflight_routes.add(preflight_route)
189
+ self._resources_with_preflight_handlers.add(resource)
190
+
191
+ elif isinstance(routing_entity, web.ResourceRoute):
192
+ route = routing_entity
193
+
194
+ if not self.is_cors_for_resource(route.resource):
195
+ self.add_preflight_handler(route.resource, handler)
196
+
197
+ else:
198
+ raise ValueError(
199
+ "Resource or ResourceRoute expected, got {!r}".format(
200
+ routing_entity))
201
+
202
+ def is_cors_for_resource(self, resource: web.Resource) -> bool:
203
+ """Is CORS is configured for the resource"""
204
+ return resource in self._resources_with_preflight_handlers
205
+
206
+ def _request_route(self, request: web.Request) -> web.ResourceRoute:
207
+ match_info = request.match_info
208
+ assert isinstance(match_info, web.UrlMappingMatchInfo)
209
+ return match_info.route
210
+
211
+ def _request_resource(self, request: web.Request) -> web.Resource:
212
+ return self._request_route(request).resource
213
+
214
+ def is_preflight_request(self, request: web.Request) -> bool:
215
+ """Is `request` is a CORS preflight request."""
216
+ route = self._request_route(request)
217
+ if _is_web_view(route, strict=False):
218
+ return request.method == 'OPTIONS'
219
+ return route in self._preflight_routes
220
+
221
+ def is_cors_enabled_on_request(self, request: web.Request) -> bool:
222
+ """Is `request` is a request for CORS-enabled resource."""
223
+
224
+ return self._request_resource(request) in self._resource_config
225
+
226
+ def set_config_for_routing_entity(
227
+ self,
228
+ routing_entity: Union[web.Resource, web.StaticResource,
229
+ web.ResourceRoute],
230
+ config):
231
+ """Record configuration for resource or it's route."""
232
+
233
+ if isinstance(routing_entity, (web.Resource, web.StaticResource)):
234
+ resource = routing_entity
235
+
236
+ # Add resource configuration or fail if it's already added.
237
+ if resource in self._resource_config:
238
+ raise ValueError(
239
+ "CORS is already configured for {!r} resource.".format(
240
+ resource))
241
+
242
+ self._resource_config[resource] = _ResourceConfig(
243
+ default_config=config)
244
+
245
+ elif isinstance(routing_entity, web.ResourceRoute):
246
+ route = routing_entity
247
+
248
+ # Add resource's route configuration or fail if it's already added.
249
+ if route.resource not in self._resource_config:
250
+ self.set_config_for_routing_entity(route.resource, config)
251
+
252
+ if route.resource not in self._resource_config:
253
+ raise ValueError(
254
+ "Can't setup CORS for {!r} request, "
255
+ "CORS must be enabled for route's resource first.".format(
256
+ route))
257
+
258
+ resource_config = self._resource_config[route.resource]
259
+
260
+ if route.method in resource_config.method_config:
261
+ raise ValueError(
262
+ "Can't setup CORS for {!r} route: CORS already "
263
+ "configured on resource {!r} for {} method".format(
264
+ route, route.resource, route.method))
265
+
266
+ resource_config.method_config[route.method] = config
267
+
268
+ else:
269
+ raise ValueError(
270
+ "Resource or ResourceRoute expected, got {!r}".format(
271
+ routing_entity))
272
+
273
+ async def get_preflight_request_config(
274
+ self,
275
+ preflight_request: web.Request,
276
+ origin: str,
277
+ requested_method: str):
278
+ assert self.is_preflight_request(preflight_request)
279
+
280
+ resource = self._request_resource(preflight_request)
281
+ resource_config = self._resource_config[resource]
282
+ defaulted_config = collections.ChainMap(
283
+ resource_config.default_config,
284
+ self._default_config)
285
+
286
+ options = defaulted_config.get(origin, defaulted_config.get("*"))
287
+ if options is not None and options.is_method_allowed(requested_method):
288
+ # Requested method enabled for CORS in defaults, override it with
289
+ # explicit route configuration (if any).
290
+ route_config = resource_config.method_config.get(
291
+ requested_method, {})
292
+
293
+ else:
294
+ # Requested method is not enabled in defaults.
295
+ # Enable CORS for it only if explicit configuration exists.
296
+ route_config = resource_config.method_config[requested_method]
297
+
298
+ defaulted_config = collections.ChainMap(route_config, defaulted_config)
299
+
300
+ return defaulted_config
301
+
302
+ def get_non_preflight_request_config(self, request: web.Request):
303
+ """Get stored CORS configuration for routing entity that handles
304
+ specified request."""
305
+
306
+ assert self.is_cors_enabled_on_request(request)
307
+
308
+ resource = self._request_resource(request)
309
+ resource_config = self._resource_config[resource]
310
+ # Take Route config (if any) with defaults from Resource CORS
311
+ # configuration and global defaults.
312
+ route = request.match_info.route
313
+ if _is_web_view(route, strict=False):
314
+ method_config = request.match_info.handler.get_request_config(
315
+ request, request.method)
316
+ else:
317
+ method_config = resource_config.method_config.get(request.method,
318
+ {})
319
+ defaulted_config = collections.ChainMap(
320
+ method_config,
321
+ resource_config.default_config,
322
+ self._default_config)
323
+
324
+ return defaulted_config
.venv/lib/python3.11/site-packages/frozenlist/__init__.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import types
4
+ from collections.abc import MutableSequence
5
+ from functools import total_ordering
6
+ from typing import Any, Type
7
+
8
+ __version__ = "1.5.0"
9
+
10
+ __all__ = ("FrozenList", "PyFrozenList") # type: Tuple[str, ...]
11
+
12
+
13
+ NO_EXTENSIONS = bool(os.environ.get("FROZENLIST_NO_EXTENSIONS")) # type: bool
14
+
15
+
16
+ @total_ordering
17
+ class FrozenList(MutableSequence):
18
+ __slots__ = ("_frozen", "_items")
19
+
20
+ if sys.version_info >= (3, 9):
21
+ __class_getitem__ = classmethod(types.GenericAlias)
22
+ else:
23
+
24
+ @classmethod
25
+ def __class_getitem__(
26
+ cls: Type["FrozenList"],
27
+ cls_item: Any,
28
+ ) -> Type["FrozenList"]:
29
+ return cls
30
+
31
+ def __init__(self, items=None):
32
+ self._frozen = False
33
+ if items is not None:
34
+ items = list(items)
35
+ else:
36
+ items = []
37
+ self._items = items
38
+
39
+ @property
40
+ def frozen(self):
41
+ return self._frozen
42
+
43
+ def freeze(self):
44
+ self._frozen = True
45
+
46
+ def __getitem__(self, index):
47
+ return self._items[index]
48
+
49
+ def __setitem__(self, index, value):
50
+ if self._frozen:
51
+ raise RuntimeError("Cannot modify frozen list.")
52
+ self._items[index] = value
53
+
54
+ def __delitem__(self, index):
55
+ if self._frozen:
56
+ raise RuntimeError("Cannot modify frozen list.")
57
+ del self._items[index]
58
+
59
+ def __len__(self):
60
+ return self._items.__len__()
61
+
62
+ def __iter__(self):
63
+ return self._items.__iter__()
64
+
65
+ def __reversed__(self):
66
+ return self._items.__reversed__()
67
+
68
+ def __eq__(self, other):
69
+ return list(self) == other
70
+
71
+ def __le__(self, other):
72
+ return list(self) <= other
73
+
74
+ def insert(self, pos, item):
75
+ if self._frozen:
76
+ raise RuntimeError("Cannot modify frozen list.")
77
+ self._items.insert(pos, item)
78
+
79
+ def __repr__(self):
80
+ return f"<FrozenList(frozen={self._frozen}, {self._items!r})>"
81
+
82
+ def __hash__(self):
83
+ if self._frozen:
84
+ return hash(tuple(self))
85
+ else:
86
+ raise RuntimeError("Cannot hash unfrozen list.")
87
+
88
+
89
+ PyFrozenList = FrozenList
90
+
91
+
92
+ if not NO_EXTENSIONS:
93
+ try:
94
+ from ._frozenlist import FrozenList as CFrozenList # type: ignore
95
+ except ImportError: # pragma: no cover
96
+ pass
97
+ else:
98
+ FrozenList = CFrozenList # type: ignore
.venv/lib/python3.11/site-packages/frozenlist/__init__.pyi ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import (
2
+ Generic,
3
+ Iterable,
4
+ Iterator,
5
+ List,
6
+ MutableSequence,
7
+ Optional,
8
+ TypeVar,
9
+ Union,
10
+ overload,
11
+ )
12
+
13
+ _T = TypeVar("_T")
14
+ _Arg = Union[List[_T], Iterable[_T]]
15
+
16
+ class FrozenList(MutableSequence[_T], Generic[_T]):
17
+ def __init__(self, items: Optional[_Arg[_T]] = None) -> None: ...
18
+ @property
19
+ def frozen(self) -> bool: ...
20
+ def freeze(self) -> None: ...
21
+ @overload
22
+ def __getitem__(self, i: int) -> _T: ...
23
+ @overload
24
+ def __getitem__(self, s: slice) -> FrozenList[_T]: ...
25
+ @overload
26
+ def __setitem__(self, i: int, o: _T) -> None: ...
27
+ @overload
28
+ def __setitem__(self, s: slice, o: Iterable[_T]) -> None: ...
29
+ @overload
30
+ def __delitem__(self, i: int) -> None: ...
31
+ @overload
32
+ def __delitem__(self, i: slice) -> None: ...
33
+ def __len__(self) -> int: ...
34
+ def __iter__(self) -> Iterator[_T]: ...
35
+ def __reversed__(self) -> Iterator[_T]: ...
36
+ def __eq__(self, other: object) -> bool: ...
37
+ def __le__(self, other: FrozenList[_T]) -> bool: ...
38
+ def __ne__(self, other: object) -> bool: ...
39
+ def __lt__(self, other: FrozenList[_T]) -> bool: ...
40
+ def __ge__(self, other: FrozenList[_T]) -> bool: ...
41
+ def __gt__(self, other: FrozenList[_T]) -> bool: ...
42
+ def insert(self, pos: int, item: _T) -> None: ...
43
+ def __repr__(self) -> str: ...
44
+ def __hash__(self) -> int: ...
45
+
46
+ # types for C accelerators are the same
47
+ CFrozenList = PyFrozenList = FrozenList
.venv/lib/python3.11/site-packages/frozenlist/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (4.84 kB). View file
 
.venv/lib/python3.11/site-packages/frozenlist/_frozenlist.pyx ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import types
3
+ from collections.abc import MutableSequence
4
+
5
+
6
+ cdef class FrozenList:
7
+
8
+ if sys.version_info >= (3, 9):
9
+ __class_getitem__ = classmethod(types.GenericAlias)
10
+ else:
11
+ @classmethod
12
+ def __class_getitem__(cls, cls_item):
13
+ return cls
14
+
15
+ cdef readonly bint frozen
16
+ cdef list _items
17
+
18
+ def __init__(self, items=None):
19
+ self.frozen = False
20
+ if items is not None:
21
+ items = list(items)
22
+ else:
23
+ items = []
24
+ self._items = items
25
+
26
+ cdef object _check_frozen(self):
27
+ if self.frozen:
28
+ raise RuntimeError("Cannot modify frozen list.")
29
+
30
+ cdef inline object _fast_len(self):
31
+ return len(self._items)
32
+
33
+ def freeze(self):
34
+ self.frozen = True
35
+
36
+ def __getitem__(self, index):
37
+ return self._items[index]
38
+
39
+ def __setitem__(self, index, value):
40
+ self._check_frozen()
41
+ self._items[index] = value
42
+
43
+ def __delitem__(self, index):
44
+ self._check_frozen()
45
+ del self._items[index]
46
+
47
+ def __len__(self):
48
+ return self._fast_len()
49
+
50
+ def __iter__(self):
51
+ return self._items.__iter__()
52
+
53
+ def __reversed__(self):
54
+ return self._items.__reversed__()
55
+
56
+ def __richcmp__(self, other, op):
57
+ if op == 0: # <
58
+ return list(self) < other
59
+ if op == 1: # <=
60
+ return list(self) <= other
61
+ if op == 2: # ==
62
+ return list(self) == other
63
+ if op == 3: # !=
64
+ return list(self) != other
65
+ if op == 4: # >
66
+ return list(self) > other
67
+ if op == 5: # =>
68
+ return list(self) >= other
69
+
70
+ def insert(self, pos, item):
71
+ self._check_frozen()
72
+ self._items.insert(pos, item)
73
+
74
+ def __contains__(self, item):
75
+ return item in self._items
76
+
77
+ def __iadd__(self, items):
78
+ self._check_frozen()
79
+ self._items += list(items)
80
+ return self
81
+
82
+ def index(self, item):
83
+ return self._items.index(item)
84
+
85
+ def remove(self, item):
86
+ self._check_frozen()
87
+ self._items.remove(item)
88
+
89
+ def clear(self):
90
+ self._check_frozen()
91
+ self._items.clear()
92
+
93
+ def extend(self, items):
94
+ self._check_frozen()
95
+ self._items += list(items)
96
+
97
+ def reverse(self):
98
+ self._check_frozen()
99
+ self._items.reverse()
100
+
101
+ def pop(self, index=-1):
102
+ self._check_frozen()
103
+ return self._items.pop(index)
104
+
105
+ def append(self, item):
106
+ self._check_frozen()
107
+ return self._items.append(item)
108
+
109
+ def count(self, item):
110
+ return self._items.count(item)
111
+
112
+ def __repr__(self):
113
+ return '<FrozenList(frozen={}, {!r})>'.format(self.frozen,
114
+ self._items)
115
+
116
+ def __hash__(self):
117
+ if self.frozen:
118
+ return hash(tuple(self._items))
119
+ else:
120
+ raise RuntimeError("Cannot hash unfrozen list.")
121
+
122
+
123
+ MutableSequence.register(FrozenList)
.venv/lib/python3.11/site-packages/frozenlist/py.typed ADDED
@@ -0,0 +1 @@
 
 
1
+ Marker
.venv/lib/python3.11/site-packages/googleapiclient/__init__.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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
+ # Set default logging handler to avoid "No handler found" warnings.
16
+ import logging
17
+
18
+ try: # Python 2.7+
19
+ from logging import NullHandler
20
+ except ImportError:
21
+
22
+ class NullHandler(logging.Handler):
23
+ def emit(self, record):
24
+ pass
25
+
26
+
27
+ logging.getLogger(__name__).addHandler(NullHandler())
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (895 Bytes). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/_auth.cpython-311.pyc ADDED
Binary file (6.27 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/_helpers.cpython-311.pyc ADDED
Binary file (7.56 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/channel.cpython-311.pyc ADDED
Binary file (11.9 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/discovery.cpython-311.pyc ADDED
Binary file (68.2 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/errors.cpython-311.pyc ADDED
Binary file (9.17 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/http.cpython-311.pyc ADDED
Binary file (75.8 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/mimeparse.cpython-311.pyc ADDED
Binary file (9.01 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/model.cpython-311.pyc ADDED
Binary file (17.7 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/sample_tools.cpython-311.pyc ADDED
Binary file (4.24 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/schema.cpython-311.pyc ADDED
Binary file (14 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/__pycache__/version.cpython-311.pyc ADDED
Binary file (211 Bytes). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/_auth.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2016 Google Inc. All Rights Reserved.
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 authentication using oauth2client or google-auth."""
16
+
17
+ import httplib2
18
+
19
+ try:
20
+ import google.auth
21
+ import google.auth.credentials
22
+
23
+ HAS_GOOGLE_AUTH = True
24
+ except ImportError: # pragma: NO COVER
25
+ HAS_GOOGLE_AUTH = False
26
+
27
+ try:
28
+ import google_auth_httplib2
29
+ except ImportError: # pragma: NO COVER
30
+ google_auth_httplib2 = None
31
+
32
+ try:
33
+ import oauth2client
34
+ import oauth2client.client
35
+
36
+ HAS_OAUTH2CLIENT = True
37
+ except ImportError: # pragma: NO COVER
38
+ HAS_OAUTH2CLIENT = False
39
+
40
+
41
+ def credentials_from_file(filename, scopes=None, quota_project_id=None):
42
+ """Returns credentials loaded from a file."""
43
+ if HAS_GOOGLE_AUTH:
44
+ credentials, _ = google.auth.load_credentials_from_file(
45
+ filename, scopes=scopes, quota_project_id=quota_project_id
46
+ )
47
+ return credentials
48
+ else:
49
+ raise EnvironmentError(
50
+ "client_options.credentials_file is only supported in google-auth."
51
+ )
52
+
53
+
54
+ def default_credentials(scopes=None, quota_project_id=None):
55
+ """Returns Application Default Credentials."""
56
+ if HAS_GOOGLE_AUTH:
57
+ credentials, _ = google.auth.default(
58
+ scopes=scopes, quota_project_id=quota_project_id
59
+ )
60
+ return credentials
61
+ elif HAS_OAUTH2CLIENT:
62
+ if scopes is not None or quota_project_id is not None:
63
+ raise EnvironmentError(
64
+ "client_options.scopes and client_options.quota_project_id are not supported in oauth2client."
65
+ "Please install google-auth."
66
+ )
67
+ return oauth2client.client.GoogleCredentials.get_application_default()
68
+ else:
69
+ raise EnvironmentError(
70
+ "No authentication library is available. Please install either "
71
+ "google-auth or oauth2client."
72
+ )
73
+
74
+
75
+ def with_scopes(credentials, scopes):
76
+ """Scopes the credentials if necessary.
77
+
78
+ Args:
79
+ credentials (Union[
80
+ google.auth.credentials.Credentials,
81
+ oauth2client.client.Credentials]): The credentials to scope.
82
+ scopes (Sequence[str]): The list of scopes.
83
+
84
+ Returns:
85
+ Union[google.auth.credentials.Credentials,
86
+ oauth2client.client.Credentials]: The scoped credentials.
87
+ """
88
+ if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
89
+ return google.auth.credentials.with_scopes_if_required(credentials, scopes)
90
+ else:
91
+ try:
92
+ if credentials.create_scoped_required():
93
+ return credentials.create_scoped(scopes)
94
+ else:
95
+ return credentials
96
+ except AttributeError:
97
+ return credentials
98
+
99
+
100
+ def authorized_http(credentials):
101
+ """Returns an http client that is authorized with the given credentials.
102
+
103
+ Args:
104
+ credentials (Union[
105
+ google.auth.credentials.Credentials,
106
+ oauth2client.client.Credentials]): The credentials to use.
107
+
108
+ Returns:
109
+ Union[httplib2.Http, google_auth_httplib2.AuthorizedHttp]: An
110
+ authorized http client.
111
+ """
112
+ from googleapiclient.http import build_http
113
+
114
+ if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
115
+ if google_auth_httplib2 is None:
116
+ raise ValueError(
117
+ "Credentials from google.auth specified, but "
118
+ "google-api-python-client is unable to use these credentials "
119
+ "unless google-auth-httplib2 is installed. Please install "
120
+ "google-auth-httplib2."
121
+ )
122
+ return google_auth_httplib2.AuthorizedHttp(credentials, http=build_http())
123
+ else:
124
+ return credentials.authorize(build_http())
125
+
126
+
127
+ def refresh_credentials(credentials):
128
+ # Refresh must use a new http instance, as the one associated with the
129
+ # credentials could be a AuthorizedHttp or an oauth2client-decorated
130
+ # Http instance which would cause a weird recursive loop of refreshing
131
+ # and likely tear a hole in spacetime.
132
+ refresh_http = httplib2.Http()
133
+ if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
134
+ request = google_auth_httplib2.Request(refresh_http)
135
+ return credentials.refresh(request)
136
+ else:
137
+ return credentials.refresh(refresh_http)
138
+
139
+
140
+ def apply_credentials(credentials, headers):
141
+ # oauth2client and google-auth have the same interface for this.
142
+ if not is_valid(credentials):
143
+ refresh_credentials(credentials)
144
+ return credentials.apply(headers)
145
+
146
+
147
+ def is_valid(credentials):
148
+ if HAS_GOOGLE_AUTH and isinstance(credentials, google.auth.credentials.Credentials):
149
+ return credentials.valid
150
+ else:
151
+ return (
152
+ credentials.access_token is not None
153
+ and not credentials.access_token_expired
154
+ )
155
+
156
+
157
+ def get_credentials_from_http(http):
158
+ if http is None:
159
+ return None
160
+ elif hasattr(http.request, "credentials"):
161
+ return http.request.credentials
162
+ elif hasattr(http, "credentials") and not isinstance(
163
+ http.credentials, httplib2.Credentials
164
+ ):
165
+ return http.credentials
166
+ else:
167
+ return None
.venv/lib/python3.11/site-packages/googleapiclient/_helpers.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2015 Google Inc. All rights reserved.
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
+ """Helper functions for commonly used utilities."""
16
+
17
+ import functools
18
+ import inspect
19
+ import logging
20
+ import urllib
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ POSITIONAL_WARNING = "WARNING"
25
+ POSITIONAL_EXCEPTION = "EXCEPTION"
26
+ POSITIONAL_IGNORE = "IGNORE"
27
+ POSITIONAL_SET = frozenset(
28
+ [POSITIONAL_WARNING, POSITIONAL_EXCEPTION, POSITIONAL_IGNORE]
29
+ )
30
+
31
+ positional_parameters_enforcement = POSITIONAL_WARNING
32
+
33
+ _SYM_LINK_MESSAGE = "File: {0}: Is a symbolic link."
34
+ _IS_DIR_MESSAGE = "{0}: Is a directory"
35
+ _MISSING_FILE_MESSAGE = "Cannot access {0}: No such file or directory"
36
+
37
+
38
+ def positional(max_positional_args):
39
+ """A decorator to declare that only the first N arguments may be positional.
40
+
41
+ This decorator makes it easy to support Python 3 style keyword-only
42
+ parameters. For example, in Python 3 it is possible to write::
43
+
44
+ def fn(pos1, *, kwonly1=None, kwonly2=None):
45
+ ...
46
+
47
+ All named parameters after ``*`` must be a keyword::
48
+
49
+ fn(10, 'kw1', 'kw2') # Raises exception.
50
+ fn(10, kwonly1='kw1') # Ok.
51
+
52
+ Example
53
+ ^^^^^^^
54
+
55
+ To define a function like above, do::
56
+
57
+ @positional(1)
58
+ def fn(pos1, kwonly1=None, kwonly2=None):
59
+ ...
60
+
61
+ If no default value is provided to a keyword argument, it becomes a
62
+ required keyword argument::
63
+
64
+ @positional(0)
65
+ def fn(required_kw):
66
+ ...
67
+
68
+ This must be called with the keyword parameter::
69
+
70
+ fn() # Raises exception.
71
+ fn(10) # Raises exception.
72
+ fn(required_kw=10) # Ok.
73
+
74
+ When defining instance or class methods always remember to account for
75
+ ``self`` and ``cls``::
76
+
77
+ class MyClass(object):
78
+
79
+ @positional(2)
80
+ def my_method(self, pos1, kwonly1=None):
81
+ ...
82
+
83
+ @classmethod
84
+ @positional(2)
85
+ def my_method(cls, pos1, kwonly1=None):
86
+ ...
87
+
88
+ The positional decorator behavior is controlled by
89
+ ``_helpers.positional_parameters_enforcement``, which may be set to
90
+ ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
91
+ ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
92
+ nothing, respectively, if a declaration is violated.
93
+
94
+ Args:
95
+ max_positional_arguments: Maximum number of positional arguments. All
96
+ parameters after this index must be
97
+ keyword only.
98
+
99
+ Returns:
100
+ A decorator that prevents using arguments after max_positional_args
101
+ from being used as positional parameters.
102
+
103
+ Raises:
104
+ TypeError: if a keyword-only argument is provided as a positional
105
+ parameter, but only if
106
+ _helpers.positional_parameters_enforcement is set to
107
+ POSITIONAL_EXCEPTION.
108
+ """
109
+
110
+ def positional_decorator(wrapped):
111
+ @functools.wraps(wrapped)
112
+ def positional_wrapper(*args, **kwargs):
113
+ if len(args) > max_positional_args:
114
+ plural_s = ""
115
+ if max_positional_args != 1:
116
+ plural_s = "s"
117
+ message = (
118
+ "{function}() takes at most {args_max} positional "
119
+ "argument{plural} ({args_given} given)".format(
120
+ function=wrapped.__name__,
121
+ args_max=max_positional_args,
122
+ args_given=len(args),
123
+ plural=plural_s,
124
+ )
125
+ )
126
+ if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
127
+ raise TypeError(message)
128
+ elif positional_parameters_enforcement == POSITIONAL_WARNING:
129
+ logger.warning(message)
130
+ return wrapped(*args, **kwargs)
131
+
132
+ return positional_wrapper
133
+
134
+ if isinstance(max_positional_args, int):
135
+ return positional_decorator
136
+ else:
137
+ args, _, _, defaults, _, _, _ = inspect.getfullargspec(max_positional_args)
138
+ return positional(len(args) - len(defaults))(max_positional_args)
139
+
140
+
141
+ def parse_unique_urlencoded(content):
142
+ """Parses unique key-value parameters from urlencoded content.
143
+
144
+ Args:
145
+ content: string, URL-encoded key-value pairs.
146
+
147
+ Returns:
148
+ dict, The key-value pairs from ``content``.
149
+
150
+ Raises:
151
+ ValueError: if one of the keys is repeated.
152
+ """
153
+ urlencoded_params = urllib.parse.parse_qs(content)
154
+ params = {}
155
+ for key, value in urlencoded_params.items():
156
+ if len(value) != 1:
157
+ msg = "URL-encoded content contains a repeated value:" "%s -> %s" % (
158
+ key,
159
+ ", ".join(value),
160
+ )
161
+ raise ValueError(msg)
162
+ params[key] = value[0]
163
+ return params
164
+
165
+
166
+ def update_query_params(uri, params):
167
+ """Updates a URI with new query parameters.
168
+
169
+ If a given key from ``params`` is repeated in the ``uri``, then
170
+ the URI will be considered invalid and an error will occur.
171
+
172
+ If the URI is valid, then each value from ``params`` will
173
+ replace the corresponding value in the query parameters (if
174
+ it exists).
175
+
176
+ Args:
177
+ uri: string, A valid URI, with potential existing query parameters.
178
+ params: dict, A dictionary of query parameters.
179
+
180
+ Returns:
181
+ The same URI but with the new query parameters added.
182
+ """
183
+ parts = urllib.parse.urlparse(uri)
184
+ query_params = parse_unique_urlencoded(parts.query)
185
+ query_params.update(params)
186
+ new_query = urllib.parse.urlencode(query_params)
187
+ new_parts = parts._replace(query=new_query)
188
+ return urllib.parse.urlunparse(new_parts)
189
+
190
+
191
+ def _add_query_parameter(url, name, value):
192
+ """Adds a query parameter to a url.
193
+
194
+ Replaces the current value if it already exists in the URL.
195
+
196
+ Args:
197
+ url: string, url to add the query parameter to.
198
+ name: string, query parameter name.
199
+ value: string, query parameter value.
200
+
201
+ Returns:
202
+ Updated query parameter. Does not update the url if value is None.
203
+ """
204
+ if value is None:
205
+ return url
206
+ else:
207
+ return update_query_params(url, {name: value})
.venv/lib/python3.11/site-packages/googleapiclient/channel.py ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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
+ """Channel notifications support.
16
+
17
+ Classes and functions to support channel subscriptions and notifications
18
+ on those channels.
19
+
20
+ Notes:
21
+ - This code is based on experimental APIs and is subject to change.
22
+ - Notification does not do deduplication of notification ids, that's up to
23
+ the receiver.
24
+ - Storing the Channel between calls is up to the caller.
25
+
26
+
27
+ Example setting up a channel:
28
+
29
+ # Create a new channel that gets notifications via webhook.
30
+ channel = new_webhook_channel("https://example.com/my_web_hook")
31
+
32
+ # Store the channel, keyed by 'channel.id'. Store it before calling the
33
+ # watch method because notifications may start arriving before the watch
34
+ # method returns.
35
+ ...
36
+
37
+ resp = service.objects().watchAll(
38
+ bucket="some_bucket_id", body=channel.body()).execute()
39
+ channel.update(resp)
40
+
41
+ # Store the channel, keyed by 'channel.id'. Store it after being updated
42
+ # since the resource_id value will now be correct, and that's needed to
43
+ # stop a subscription.
44
+ ...
45
+
46
+
47
+ An example Webhook implementation using webapp2. Note that webapp2 puts
48
+ headers in a case insensitive dictionary, as headers aren't guaranteed to
49
+ always be upper case.
50
+
51
+ id = self.request.headers[X_GOOG_CHANNEL_ID]
52
+
53
+ # Retrieve the channel by id.
54
+ channel = ...
55
+
56
+ # Parse notification from the headers, including validating the id.
57
+ n = notification_from_headers(channel, self.request.headers)
58
+
59
+ # Do app specific stuff with the notification here.
60
+ if n.resource_state == 'sync':
61
+ # Code to handle sync state.
62
+ elif n.resource_state == 'exists':
63
+ # Code to handle the exists state.
64
+ elif n.resource_state == 'not_exists':
65
+ # Code to handle the not exists state.
66
+
67
+
68
+ Example of unsubscribing.
69
+
70
+ service.channels().stop(channel.body()).execute()
71
+ """
72
+ from __future__ import absolute_import
73
+
74
+ import datetime
75
+ import uuid
76
+
77
+ from googleapiclient import _helpers as util
78
+ from googleapiclient import errors
79
+
80
+ # The unix time epoch starts at midnight 1970.
81
+ EPOCH = datetime.datetime(1970, 1, 1)
82
+
83
+ # Map the names of the parameters in the JSON channel description to
84
+ # the parameter names we use in the Channel class.
85
+ CHANNEL_PARAMS = {
86
+ "address": "address",
87
+ "id": "id",
88
+ "expiration": "expiration",
89
+ "params": "params",
90
+ "resourceId": "resource_id",
91
+ "resourceUri": "resource_uri",
92
+ "type": "type",
93
+ "token": "token",
94
+ }
95
+
96
+ X_GOOG_CHANNEL_ID = "X-GOOG-CHANNEL-ID"
97
+ X_GOOG_MESSAGE_NUMBER = "X-GOOG-MESSAGE-NUMBER"
98
+ X_GOOG_RESOURCE_STATE = "X-GOOG-RESOURCE-STATE"
99
+ X_GOOG_RESOURCE_URI = "X-GOOG-RESOURCE-URI"
100
+ X_GOOG_RESOURCE_ID = "X-GOOG-RESOURCE-ID"
101
+
102
+
103
+ def _upper_header_keys(headers):
104
+ new_headers = {}
105
+ for k, v in headers.items():
106
+ new_headers[k.upper()] = v
107
+ return new_headers
108
+
109
+
110
+ class Notification(object):
111
+ """A Notification from a Channel.
112
+
113
+ Notifications are not usually constructed directly, but are returned
114
+ from functions like notification_from_headers().
115
+
116
+ Attributes:
117
+ message_number: int, The unique id number of this notification.
118
+ state: str, The state of the resource being monitored.
119
+ uri: str, The address of the resource being monitored.
120
+ resource_id: str, The unique identifier of the version of the resource at
121
+ this event.
122
+ """
123
+
124
+ @util.positional(5)
125
+ def __init__(self, message_number, state, resource_uri, resource_id):
126
+ """Notification constructor.
127
+
128
+ Args:
129
+ message_number: int, The unique id number of this notification.
130
+ state: str, The state of the resource being monitored. Can be one
131
+ of "exists", "not_exists", or "sync".
132
+ resource_uri: str, The address of the resource being monitored.
133
+ resource_id: str, The identifier of the watched resource.
134
+ """
135
+ self.message_number = message_number
136
+ self.state = state
137
+ self.resource_uri = resource_uri
138
+ self.resource_id = resource_id
139
+
140
+
141
+ class Channel(object):
142
+ """A Channel for notifications.
143
+
144
+ Usually not constructed directly, instead it is returned from helper
145
+ functions like new_webhook_channel().
146
+
147
+ Attributes:
148
+ type: str, The type of delivery mechanism used by this channel. For
149
+ example, 'web_hook'.
150
+ id: str, A UUID for the channel.
151
+ token: str, An arbitrary string associated with the channel that
152
+ is delivered to the target address with each event delivered
153
+ over this channel.
154
+ address: str, The address of the receiving entity where events are
155
+ delivered. Specific to the channel type.
156
+ expiration: int, The time, in milliseconds from the epoch, when this
157
+ channel will expire.
158
+ params: dict, A dictionary of string to string, with additional parameters
159
+ controlling delivery channel behavior.
160
+ resource_id: str, An opaque id that identifies the resource that is
161
+ being watched. Stable across different API versions.
162
+ resource_uri: str, The canonicalized ID of the watched resource.
163
+ """
164
+
165
+ @util.positional(5)
166
+ def __init__(
167
+ self,
168
+ type,
169
+ id,
170
+ token,
171
+ address,
172
+ expiration=None,
173
+ params=None,
174
+ resource_id="",
175
+ resource_uri="",
176
+ ):
177
+ """Create a new Channel.
178
+
179
+ In user code, this Channel constructor will not typically be called
180
+ manually since there are functions for creating channels for each specific
181
+ type with a more customized set of arguments to pass.
182
+
183
+ Args:
184
+ type: str, The type of delivery mechanism used by this channel. For
185
+ example, 'web_hook'.
186
+ id: str, A UUID for the channel.
187
+ token: str, An arbitrary string associated with the channel that
188
+ is delivered to the target address with each event delivered
189
+ over this channel.
190
+ address: str, The address of the receiving entity where events are
191
+ delivered. Specific to the channel type.
192
+ expiration: int, The time, in milliseconds from the epoch, when this
193
+ channel will expire.
194
+ params: dict, A dictionary of string to string, with additional parameters
195
+ controlling delivery channel behavior.
196
+ resource_id: str, An opaque id that identifies the resource that is
197
+ being watched. Stable across different API versions.
198
+ resource_uri: str, The canonicalized ID of the watched resource.
199
+ """
200
+ self.type = type
201
+ self.id = id
202
+ self.token = token
203
+ self.address = address
204
+ self.expiration = expiration
205
+ self.params = params
206
+ self.resource_id = resource_id
207
+ self.resource_uri = resource_uri
208
+
209
+ def body(self):
210
+ """Build a body from the Channel.
211
+
212
+ Constructs a dictionary that's appropriate for passing into watch()
213
+ methods as the value of body argument.
214
+
215
+ Returns:
216
+ A dictionary representation of the channel.
217
+ """
218
+ result = {
219
+ "id": self.id,
220
+ "token": self.token,
221
+ "type": self.type,
222
+ "address": self.address,
223
+ }
224
+ if self.params:
225
+ result["params"] = self.params
226
+ if self.resource_id:
227
+ result["resourceId"] = self.resource_id
228
+ if self.resource_uri:
229
+ result["resourceUri"] = self.resource_uri
230
+ if self.expiration:
231
+ result["expiration"] = self.expiration
232
+
233
+ return result
234
+
235
+ def update(self, resp):
236
+ """Update a channel with information from the response of watch().
237
+
238
+ When a request is sent to watch() a resource, the response returned
239
+ from the watch() request is a dictionary with updated channel information,
240
+ such as the resource_id, which is needed when stopping a subscription.
241
+
242
+ Args:
243
+ resp: dict, The response from a watch() method.
244
+ """
245
+ for json_name, param_name in CHANNEL_PARAMS.items():
246
+ value = resp.get(json_name)
247
+ if value is not None:
248
+ setattr(self, param_name, value)
249
+
250
+
251
+ def notification_from_headers(channel, headers):
252
+ """Parse a notification from the webhook request headers, validate
253
+ the notification, and return a Notification object.
254
+
255
+ Args:
256
+ channel: Channel, The channel that the notification is associated with.
257
+ headers: dict, A dictionary like object that contains the request headers
258
+ from the webhook HTTP request.
259
+
260
+ Returns:
261
+ A Notification object.
262
+
263
+ Raises:
264
+ errors.InvalidNotificationError if the notification is invalid.
265
+ ValueError if the X-GOOG-MESSAGE-NUMBER can't be converted to an int.
266
+ """
267
+ headers = _upper_header_keys(headers)
268
+ channel_id = headers[X_GOOG_CHANNEL_ID]
269
+ if channel.id != channel_id:
270
+ raise errors.InvalidNotificationError(
271
+ "Channel id mismatch: %s != %s" % (channel.id, channel_id)
272
+ )
273
+ else:
274
+ message_number = int(headers[X_GOOG_MESSAGE_NUMBER])
275
+ state = headers[X_GOOG_RESOURCE_STATE]
276
+ resource_uri = headers[X_GOOG_RESOURCE_URI]
277
+ resource_id = headers[X_GOOG_RESOURCE_ID]
278
+ return Notification(message_number, state, resource_uri, resource_id)
279
+
280
+
281
+ @util.positional(2)
282
+ def new_webhook_channel(url, token=None, expiration=None, params=None):
283
+ """Create a new webhook Channel.
284
+
285
+ Args:
286
+ url: str, URL to post notifications to.
287
+ token: str, An arbitrary string associated with the channel that
288
+ is delivered to the target address with each notification delivered
289
+ over this channel.
290
+ expiration: datetime.datetime, A time in the future when the channel
291
+ should expire. Can also be None if the subscription should use the
292
+ default expiration. Note that different services may have different
293
+ limits on how long a subscription lasts. Check the response from the
294
+ watch() method to see the value the service has set for an expiration
295
+ time.
296
+ params: dict, Extra parameters to pass on channel creation. Currently
297
+ not used for webhook channels.
298
+ """
299
+ expiration_ms = 0
300
+ if expiration:
301
+ delta = expiration - EPOCH
302
+ expiration_ms = (
303
+ delta.microseconds / 1000 + (delta.seconds + delta.days * 24 * 3600) * 1000
304
+ )
305
+ if expiration_ms < 0:
306
+ expiration_ms = 0
307
+
308
+ return Channel(
309
+ "web_hook",
310
+ str(uuid.uuid4()),
311
+ token,
312
+ url,
313
+ expiration=expiration_ms,
314
+ params=params,
315
+ )
.venv/lib/python3.11/site-packages/googleapiclient/discovery.py ADDED
@@ -0,0 +1,1662 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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 for discovery based APIs.
16
+
17
+ A client library for Google's discovery based APIs.
18
+ """
19
+ from __future__ import absolute_import
20
+
21
+ __author__ = "jcgregorio@google.com (Joe Gregorio)"
22
+ __all__ = ["build", "build_from_document", "fix_method_name", "key2param"]
23
+
24
+ from collections import OrderedDict
25
+ import collections.abc
26
+
27
+ # Standard library imports
28
+ import copy
29
+ from email.generator import BytesGenerator
30
+ from email.mime.multipart import MIMEMultipart
31
+ from email.mime.nonmultipart import MIMENonMultipart
32
+ import http.client as http_client
33
+ import io
34
+ import json
35
+ import keyword
36
+ import logging
37
+ import mimetypes
38
+ import os
39
+ import re
40
+ import urllib
41
+
42
+ import google.api_core.client_options
43
+ from google.auth.exceptions import MutualTLSChannelError
44
+ from google.auth.transport import mtls
45
+ from google.oauth2 import service_account
46
+
47
+ # Third-party imports
48
+ import httplib2
49
+ import uritemplate
50
+
51
+ try:
52
+ import google_auth_httplib2
53
+ except ImportError: # pragma: NO COVER
54
+ google_auth_httplib2 = None
55
+
56
+ try:
57
+ from google.api_core import universe
58
+
59
+ HAS_UNIVERSE = True
60
+ except ImportError:
61
+ HAS_UNIVERSE = False
62
+
63
+ # Local imports
64
+ from googleapiclient import _auth, mimeparse
65
+ from googleapiclient._helpers import _add_query_parameter, positional
66
+ from googleapiclient.errors import (
67
+ HttpError,
68
+ InvalidJsonError,
69
+ MediaUploadSizeError,
70
+ UnacceptableMimeTypeError,
71
+ UnknownApiNameOrVersion,
72
+ UnknownFileType,
73
+ )
74
+ from googleapiclient.http import (
75
+ BatchHttpRequest,
76
+ HttpMock,
77
+ HttpMockSequence,
78
+ HttpRequest,
79
+ MediaFileUpload,
80
+ MediaUpload,
81
+ build_http,
82
+ )
83
+ from googleapiclient.model import JsonModel, MediaModel, RawModel
84
+ from googleapiclient.schema import Schemas
85
+
86
+ # The client library requires a version of httplib2 that supports RETRIES.
87
+ httplib2.RETRIES = 1
88
+
89
+ logger = logging.getLogger(__name__)
90
+
91
+ URITEMPLATE = re.compile("{[^}]*}")
92
+ VARNAME = re.compile("[a-zA-Z0-9_-]+")
93
+ DISCOVERY_URI = (
94
+ "https://www.googleapis.com/discovery/v1/apis/" "{api}/{apiVersion}/rest"
95
+ )
96
+ V1_DISCOVERY_URI = DISCOVERY_URI
97
+ V2_DISCOVERY_URI = (
98
+ "https://{api}.googleapis.com/$discovery/rest?" "version={apiVersion}"
99
+ )
100
+ DEFAULT_METHOD_DOC = "A description of how to use this function"
101
+ HTTP_PAYLOAD_METHODS = frozenset(["PUT", "POST", "PATCH"])
102
+
103
+ _MEDIA_SIZE_BIT_SHIFTS = {"KB": 10, "MB": 20, "GB": 30, "TB": 40}
104
+ BODY_PARAMETER_DEFAULT_VALUE = {"description": "The request body.", "type": "object"}
105
+ MEDIA_BODY_PARAMETER_DEFAULT_VALUE = {
106
+ "description": (
107
+ "The filename of the media request body, or an instance "
108
+ "of a MediaUpload object."
109
+ ),
110
+ "type": "string",
111
+ "required": False,
112
+ }
113
+ MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE = {
114
+ "description": (
115
+ "The MIME type of the media request body, or an instance "
116
+ "of a MediaUpload object."
117
+ ),
118
+ "type": "string",
119
+ "required": False,
120
+ }
121
+ _PAGE_TOKEN_NAMES = ("pageToken", "nextPageToken")
122
+
123
+ # Parameters controlling mTLS behavior. See https://google.aip.dev/auth/4114.
124
+ GOOGLE_API_USE_CLIENT_CERTIFICATE = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
125
+ GOOGLE_API_USE_MTLS_ENDPOINT = "GOOGLE_API_USE_MTLS_ENDPOINT"
126
+ GOOGLE_CLOUD_UNIVERSE_DOMAIN = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
127
+ DEFAULT_UNIVERSE = "googleapis.com"
128
+ # Parameters accepted by the stack, but not visible via discovery.
129
+ # TODO(dhermes): Remove 'userip' in 'v2'.
130
+ STACK_QUERY_PARAMETERS = frozenset(["trace", "pp", "userip", "strict"])
131
+ STACK_QUERY_PARAMETER_DEFAULT_VALUE = {"type": "string", "location": "query"}
132
+
133
+
134
+ class APICoreVersionError(ValueError):
135
+ def __init__(self):
136
+ message = (
137
+ "google-api-core >= 2.18.0 is required to use the universe domain feature."
138
+ )
139
+ super().__init__(message)
140
+
141
+
142
+ # Library-specific reserved words beyond Python keywords.
143
+ RESERVED_WORDS = frozenset(["body"])
144
+
145
+ # patch _write_lines to avoid munging '\r' into '\n'
146
+ # ( https://bugs.python.org/issue18886 https://bugs.python.org/issue19003 )
147
+ class _BytesGenerator(BytesGenerator):
148
+ _write_lines = BytesGenerator.write
149
+
150
+
151
+ def fix_method_name(name):
152
+ """Fix method names to avoid '$' characters and reserved word conflicts.
153
+
154
+ Args:
155
+ name: string, method name.
156
+
157
+ Returns:
158
+ The name with '_' appended if the name is a reserved word and '$' and '-'
159
+ replaced with '_'.
160
+ """
161
+ name = name.replace("$", "_").replace("-", "_")
162
+ if keyword.iskeyword(name) or name in RESERVED_WORDS:
163
+ return name + "_"
164
+ else:
165
+ return name
166
+
167
+
168
+ def key2param(key):
169
+ """Converts key names into parameter names.
170
+
171
+ For example, converting "max-results" -> "max_results"
172
+
173
+ Args:
174
+ key: string, the method key name.
175
+
176
+ Returns:
177
+ A safe method name based on the key name.
178
+ """
179
+ result = []
180
+ key = list(key)
181
+ if not key[0].isalpha():
182
+ result.append("x")
183
+ for c in key:
184
+ if c.isalnum():
185
+ result.append(c)
186
+ else:
187
+ result.append("_")
188
+
189
+ return "".join(result)
190
+
191
+
192
+ @positional(2)
193
+ def build(
194
+ serviceName,
195
+ version,
196
+ http=None,
197
+ discoveryServiceUrl=None,
198
+ developerKey=None,
199
+ model=None,
200
+ requestBuilder=HttpRequest,
201
+ credentials=None,
202
+ cache_discovery=True,
203
+ cache=None,
204
+ client_options=None,
205
+ adc_cert_path=None,
206
+ adc_key_path=None,
207
+ num_retries=1,
208
+ static_discovery=None,
209
+ always_use_jwt_access=False,
210
+ ):
211
+ """Construct a Resource for interacting with an API.
212
+
213
+ Construct a Resource object for interacting with an API. The serviceName and
214
+ version are the names from the Discovery service.
215
+
216
+ Args:
217
+ serviceName: string, name of the service.
218
+ version: string, the version of the service.
219
+ http: httplib2.Http, An instance of httplib2.Http or something that acts
220
+ like it that HTTP requests will be made through.
221
+ discoveryServiceUrl: string, a URI Template that points to the location of
222
+ the discovery service. It should have two parameters {api} and
223
+ {apiVersion} that when filled in produce an absolute URI to the discovery
224
+ document for that service.
225
+ developerKey: string, key obtained from
226
+ https://code.google.com/apis/console.
227
+ model: googleapiclient.Model, converts to and from the wire format.
228
+ requestBuilder: googleapiclient.http.HttpRequest, encapsulator for an HTTP
229
+ request.
230
+ credentials: oauth2client.Credentials or
231
+ google.auth.credentials.Credentials, credentials to be used for
232
+ authentication.
233
+ cache_discovery: Boolean, whether or not to cache the discovery doc.
234
+ cache: googleapiclient.discovery_cache.base.CacheBase, an optional
235
+ cache object for the discovery documents.
236
+ client_options: Mapping object or google.api_core.client_options, client
237
+ options to set user options on the client.
238
+ (1) The API endpoint should be set through client_options. If API endpoint
239
+ is not set, `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable can be used
240
+ to control which endpoint to use.
241
+ (2) client_cert_source is not supported, client cert should be provided using
242
+ client_encrypted_cert_source instead. In order to use the provided client
243
+ cert, `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be
244
+ set to `true`.
245
+ More details on the environment variables are here:
246
+ https://google.aip.dev/auth/4114
247
+ adc_cert_path: str, client certificate file path to save the application
248
+ default client certificate for mTLS. This field is required if you want to
249
+ use the default client certificate. `GOOGLE_API_USE_CLIENT_CERTIFICATE`
250
+ environment variable must be set to `true` in order to use this field,
251
+ otherwise this field doesn't nothing.
252
+ More details on the environment variables are here:
253
+ https://google.aip.dev/auth/4114
254
+ adc_key_path: str, client encrypted private key file path to save the
255
+ application default client encrypted private key for mTLS. This field is
256
+ required if you want to use the default client certificate.
257
+ `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be set to
258
+ `true` in order to use this field, otherwise this field doesn't nothing.
259
+ More details on the environment variables are here:
260
+ https://google.aip.dev/auth/4114
261
+ num_retries: Integer, number of times to retry discovery with
262
+ randomized exponential backoff in case of intermittent/connection issues.
263
+ static_discovery: Boolean, whether or not to use the static discovery docs
264
+ included in the library. The default value for `static_discovery` depends
265
+ on the value of `discoveryServiceUrl`. `static_discovery` will default to
266
+ `True` when `discoveryServiceUrl` is also not provided, otherwise it will
267
+ default to `False`.
268
+ always_use_jwt_access: Boolean, whether always use self signed JWT for service
269
+ account credentials. This only applies to
270
+ google.oauth2.service_account.Credentials.
271
+
272
+ Returns:
273
+ A Resource object with methods for interacting with the service.
274
+
275
+ Raises:
276
+ google.auth.exceptions.MutualTLSChannelError: if there are any problems
277
+ setting up mutual TLS channel.
278
+ """
279
+ params = {"api": serviceName, "apiVersion": version}
280
+
281
+ # The default value for `static_discovery` depends on the value of
282
+ # `discoveryServiceUrl`. `static_discovery` will default to `True` when
283
+ # `discoveryServiceUrl` is also not provided, otherwise it will default to
284
+ # `False`. This is added for backwards compatability with
285
+ # google-api-python-client 1.x which does not support the `static_discovery`
286
+ # parameter.
287
+ if static_discovery is None:
288
+ if discoveryServiceUrl is None:
289
+ static_discovery = True
290
+ else:
291
+ static_discovery = False
292
+
293
+ if http is None:
294
+ discovery_http = build_http()
295
+ else:
296
+ discovery_http = http
297
+
298
+ service = None
299
+
300
+ for discovery_url in _discovery_service_uri_options(discoveryServiceUrl, version):
301
+ requested_url = uritemplate.expand(discovery_url, params)
302
+
303
+ try:
304
+ content = _retrieve_discovery_doc(
305
+ requested_url,
306
+ discovery_http,
307
+ cache_discovery,
308
+ serviceName,
309
+ version,
310
+ cache,
311
+ developerKey,
312
+ num_retries=num_retries,
313
+ static_discovery=static_discovery,
314
+ )
315
+ service = build_from_document(
316
+ content,
317
+ base=discovery_url,
318
+ http=http,
319
+ developerKey=developerKey,
320
+ model=model,
321
+ requestBuilder=requestBuilder,
322
+ credentials=credentials,
323
+ client_options=client_options,
324
+ adc_cert_path=adc_cert_path,
325
+ adc_key_path=adc_key_path,
326
+ always_use_jwt_access=always_use_jwt_access,
327
+ )
328
+ break # exit if a service was created
329
+ except HttpError as e:
330
+ if e.resp.status == http_client.NOT_FOUND:
331
+ continue
332
+ else:
333
+ raise e
334
+
335
+ # If discovery_http was created by this function, we are done with it
336
+ # and can safely close it
337
+ if http is None:
338
+ discovery_http.close()
339
+
340
+ if service is None:
341
+ raise UnknownApiNameOrVersion("name: %s version: %s" % (serviceName, version))
342
+ else:
343
+ return service
344
+
345
+
346
+ def _discovery_service_uri_options(discoveryServiceUrl, version):
347
+ """
348
+ Returns Discovery URIs to be used for attempting to build the API Resource.
349
+
350
+ Args:
351
+ discoveryServiceUrl:
352
+ string, the Original Discovery Service URL preferred by the customer.
353
+ version:
354
+ string, API Version requested
355
+
356
+ Returns:
357
+ A list of URIs to be tried for the Service Discovery, in order.
358
+ """
359
+
360
+ if discoveryServiceUrl is not None:
361
+ return [discoveryServiceUrl]
362
+ if version is None:
363
+ # V1 Discovery won't work if the requested version is None
364
+ logger.warning(
365
+ "Discovery V1 does not support empty versions. Defaulting to V2..."
366
+ )
367
+ return [V2_DISCOVERY_URI]
368
+ else:
369
+ return [DISCOVERY_URI, V2_DISCOVERY_URI]
370
+
371
+
372
+ def _retrieve_discovery_doc(
373
+ url,
374
+ http,
375
+ cache_discovery,
376
+ serviceName,
377
+ version,
378
+ cache=None,
379
+ developerKey=None,
380
+ num_retries=1,
381
+ static_discovery=True,
382
+ ):
383
+ """Retrieves the discovery_doc from cache or the internet.
384
+
385
+ Args:
386
+ url: string, the URL of the discovery document.
387
+ http: httplib2.Http, An instance of httplib2.Http or something that acts
388
+ like it through which HTTP requests will be made.
389
+ cache_discovery: Boolean, whether or not to cache the discovery doc.
390
+ serviceName: string, name of the service.
391
+ version: string, the version of the service.
392
+ cache: googleapiclient.discovery_cache.base.Cache, an optional cache
393
+ object for the discovery documents.
394
+ developerKey: string, Key for controlling API usage, generated
395
+ from the API Console.
396
+ num_retries: Integer, number of times to retry discovery with
397
+ randomized exponential backoff in case of intermittent/connection issues.
398
+ static_discovery: Boolean, whether or not to use the static discovery docs
399
+ included in the library.
400
+
401
+ Returns:
402
+ A unicode string representation of the discovery document.
403
+ """
404
+ from . import discovery_cache
405
+
406
+ if cache_discovery:
407
+ if cache is None:
408
+ cache = discovery_cache.autodetect()
409
+ if cache:
410
+ content = cache.get(url)
411
+ if content:
412
+ return content
413
+
414
+ # When `static_discovery=True`, use static discovery artifacts included
415
+ # with the library
416
+ if static_discovery:
417
+ content = discovery_cache.get_static_doc(serviceName, version)
418
+ if content:
419
+ return content
420
+ else:
421
+ raise UnknownApiNameOrVersion(
422
+ "name: %s version: %s" % (serviceName, version)
423
+ )
424
+
425
+ actual_url = url
426
+ # REMOTE_ADDR is defined by the CGI spec [RFC3875] as the environment
427
+ # variable that contains the network address of the client sending the
428
+ # request. If it exists then add that to the request for the discovery
429
+ # document to avoid exceeding the quota on discovery requests.
430
+ if "REMOTE_ADDR" in os.environ:
431
+ actual_url = _add_query_parameter(url, "userIp", os.environ["REMOTE_ADDR"])
432
+ if developerKey:
433
+ actual_url = _add_query_parameter(url, "key", developerKey)
434
+ logger.debug("URL being requested: GET %s", actual_url)
435
+
436
+ # Execute this request with retries build into HttpRequest
437
+ # Note that it will already raise an error if we don't get a 2xx response
438
+ req = HttpRequest(http, HttpRequest.null_postproc, actual_url)
439
+ resp, content = req.execute(num_retries=num_retries)
440
+
441
+ try:
442
+ content = content.decode("utf-8")
443
+ except AttributeError:
444
+ pass
445
+
446
+ try:
447
+ service = json.loads(content)
448
+ except ValueError as e:
449
+ logger.error("Failed to parse as JSON: " + content)
450
+ raise InvalidJsonError()
451
+ if cache_discovery and cache:
452
+ cache.set(url, content)
453
+ return content
454
+
455
+
456
+ def _check_api_core_compatible_with_credentials_universe(credentials):
457
+ if not HAS_UNIVERSE:
458
+ credentials_universe = getattr(credentials, "universe_domain", None)
459
+ if credentials_universe and credentials_universe != DEFAULT_UNIVERSE:
460
+ raise APICoreVersionError
461
+
462
+
463
+ @positional(1)
464
+ def build_from_document(
465
+ service,
466
+ base=None,
467
+ future=None,
468
+ http=None,
469
+ developerKey=None,
470
+ model=None,
471
+ requestBuilder=HttpRequest,
472
+ credentials=None,
473
+ client_options=None,
474
+ adc_cert_path=None,
475
+ adc_key_path=None,
476
+ always_use_jwt_access=False,
477
+ ):
478
+ """Create a Resource for interacting with an API.
479
+
480
+ Same as `build()`, but constructs the Resource object from a discovery
481
+ document that is it given, as opposed to retrieving one over HTTP.
482
+
483
+ Args:
484
+ service: string or object, the JSON discovery document describing the API.
485
+ The value passed in may either be the JSON string or the deserialized
486
+ JSON.
487
+ base: string, base URI for all HTTP requests, usually the discovery URI.
488
+ This parameter is no longer used as rootUrl and servicePath are included
489
+ within the discovery document. (deprecated)
490
+ future: string, discovery document with future capabilities (deprecated).
491
+ http: httplib2.Http, An instance of httplib2.Http or something that acts
492
+ like it that HTTP requests will be made through.
493
+ developerKey: string, Key for controlling API usage, generated
494
+ from the API Console.
495
+ model: Model class instance that serializes and de-serializes requests and
496
+ responses.
497
+ requestBuilder: Takes an http request and packages it up to be executed.
498
+ credentials: oauth2client.Credentials or
499
+ google.auth.credentials.Credentials, credentials to be used for
500
+ authentication.
501
+ client_options: Mapping object or google.api_core.client_options, client
502
+ options to set user options on the client.
503
+ (1) The API endpoint should be set through client_options. If API endpoint
504
+ is not set, `GOOGLE_API_USE_MTLS_ENDPOINT` environment variable can be used
505
+ to control which endpoint to use.
506
+ (2) client_cert_source is not supported, client cert should be provided using
507
+ client_encrypted_cert_source instead. In order to use the provided client
508
+ cert, `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be
509
+ set to `true`.
510
+ More details on the environment variables are here:
511
+ https://google.aip.dev/auth/4114
512
+ adc_cert_path: str, client certificate file path to save the application
513
+ default client certificate for mTLS. This field is required if you want to
514
+ use the default client certificate. `GOOGLE_API_USE_CLIENT_CERTIFICATE`
515
+ environment variable must be set to `true` in order to use this field,
516
+ otherwise this field doesn't nothing.
517
+ More details on the environment variables are here:
518
+ https://google.aip.dev/auth/4114
519
+ adc_key_path: str, client encrypted private key file path to save the
520
+ application default client encrypted private key for mTLS. This field is
521
+ required if you want to use the default client certificate.
522
+ `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable must be set to
523
+ `true` in order to use this field, otherwise this field doesn't nothing.
524
+ More details on the environment variables are here:
525
+ https://google.aip.dev/auth/4114
526
+ always_use_jwt_access: Boolean, whether always use self signed JWT for service
527
+ account credentials. This only applies to
528
+ google.oauth2.service_account.Credentials.
529
+
530
+ Returns:
531
+ A Resource object with methods for interacting with the service.
532
+
533
+ Raises:
534
+ google.auth.exceptions.MutualTLSChannelError: if there are any problems
535
+ setting up mutual TLS channel.
536
+ """
537
+
538
+ if client_options is None:
539
+ client_options = google.api_core.client_options.ClientOptions()
540
+ if isinstance(client_options, collections.abc.Mapping):
541
+ client_options = google.api_core.client_options.from_dict(client_options)
542
+
543
+ if http is not None:
544
+ # if http is passed, the user cannot provide credentials
545
+ banned_options = [
546
+ (credentials, "credentials"),
547
+ (client_options.credentials_file, "client_options.credentials_file"),
548
+ ]
549
+ for option, name in banned_options:
550
+ if option is not None:
551
+ raise ValueError(
552
+ "Arguments http and {} are mutually exclusive".format(name)
553
+ )
554
+
555
+ if isinstance(service, str):
556
+ service = json.loads(service)
557
+ elif isinstance(service, bytes):
558
+ service = json.loads(service.decode("utf-8"))
559
+
560
+ if "rootUrl" not in service and isinstance(http, (HttpMock, HttpMockSequence)):
561
+ logger.error(
562
+ "You are using HttpMock or HttpMockSequence without"
563
+ + "having the service discovery doc in cache. Try calling "
564
+ + "build() without mocking once first to populate the "
565
+ + "cache."
566
+ )
567
+ raise InvalidJsonError()
568
+
569
+ # If an API Endpoint is provided on client options, use that as the base URL
570
+ base = urllib.parse.urljoin(service["rootUrl"], service["servicePath"])
571
+ universe_domain = None
572
+ if HAS_UNIVERSE:
573
+ universe_domain_env = os.getenv(GOOGLE_CLOUD_UNIVERSE_DOMAIN, None)
574
+ universe_domain = universe.determine_domain(
575
+ client_options.universe_domain, universe_domain_env
576
+ )
577
+ base = base.replace(universe.DEFAULT_UNIVERSE, universe_domain)
578
+ else:
579
+ client_universe = getattr(client_options, "universe_domain", None)
580
+ if client_universe:
581
+ raise APICoreVersionError
582
+
583
+ audience_for_self_signed_jwt = base
584
+ if client_options.api_endpoint:
585
+ base = client_options.api_endpoint
586
+
587
+ schema = Schemas(service)
588
+
589
+ # If the http client is not specified, then we must construct an http client
590
+ # to make requests. If the service has scopes, then we also need to setup
591
+ # authentication.
592
+ if http is None:
593
+ # Does the service require scopes?
594
+ scopes = list(
595
+ service.get("auth", {}).get("oauth2", {}).get("scopes", {}).keys()
596
+ )
597
+
598
+ # If so, then the we need to setup authentication if no developerKey is
599
+ # specified.
600
+ if scopes and not developerKey:
601
+ # Make sure the user didn't pass multiple credentials
602
+ if client_options.credentials_file and credentials:
603
+ raise google.api_core.exceptions.DuplicateCredentialArgs(
604
+ "client_options.credentials_file and credentials are mutually exclusive."
605
+ )
606
+ # Check for credentials file via client options
607
+ if client_options.credentials_file:
608
+ credentials = _auth.credentials_from_file(
609
+ client_options.credentials_file,
610
+ scopes=client_options.scopes,
611
+ quota_project_id=client_options.quota_project_id,
612
+ )
613
+ # If the user didn't pass in credentials, attempt to acquire application
614
+ # default credentials.
615
+ if credentials is None:
616
+ credentials = _auth.default_credentials(
617
+ scopes=client_options.scopes,
618
+ quota_project_id=client_options.quota_project_id,
619
+ )
620
+
621
+ # Check google-api-core >= 2.18.0 if credentials' universe != "googleapis.com".
622
+ _check_api_core_compatible_with_credentials_universe(credentials)
623
+
624
+ # The credentials need to be scoped.
625
+ # If the user provided scopes via client_options don't override them
626
+ if not client_options.scopes:
627
+ credentials = _auth.with_scopes(credentials, scopes)
628
+
629
+ # For google-auth service account credentials, enable self signed JWT if
630
+ # always_use_jwt_access is true.
631
+ if (
632
+ credentials
633
+ and isinstance(credentials, service_account.Credentials)
634
+ and always_use_jwt_access
635
+ and hasattr(service_account.Credentials, "with_always_use_jwt_access")
636
+ ):
637
+ credentials = credentials.with_always_use_jwt_access(always_use_jwt_access)
638
+ credentials._create_self_signed_jwt(audience_for_self_signed_jwt)
639
+
640
+ # If credentials are provided, create an authorized http instance;
641
+ # otherwise, skip authentication.
642
+ if credentials:
643
+ http = _auth.authorized_http(credentials)
644
+
645
+ # If the service doesn't require scopes then there is no need for
646
+ # authentication.
647
+ else:
648
+ http = build_http()
649
+
650
+ # Obtain client cert and create mTLS http channel if cert exists.
651
+ client_cert_to_use = None
652
+ use_client_cert = os.getenv(GOOGLE_API_USE_CLIENT_CERTIFICATE, "false")
653
+ if not use_client_cert in ("true", "false"):
654
+ raise MutualTLSChannelError(
655
+ "Unsupported GOOGLE_API_USE_CLIENT_CERTIFICATE value. Accepted values: true, false"
656
+ )
657
+ if client_options and client_options.client_cert_source:
658
+ raise MutualTLSChannelError(
659
+ "ClientOptions.client_cert_source is not supported, please use ClientOptions.client_encrypted_cert_source."
660
+ )
661
+ if use_client_cert == "true":
662
+ if (
663
+ client_options
664
+ and hasattr(client_options, "client_encrypted_cert_source")
665
+ and client_options.client_encrypted_cert_source
666
+ ):
667
+ client_cert_to_use = client_options.client_encrypted_cert_source
668
+ elif (
669
+ adc_cert_path and adc_key_path and mtls.has_default_client_cert_source()
670
+ ):
671
+ client_cert_to_use = mtls.default_client_encrypted_cert_source(
672
+ adc_cert_path, adc_key_path
673
+ )
674
+ if client_cert_to_use:
675
+ cert_path, key_path, passphrase = client_cert_to_use()
676
+
677
+ # The http object we built could be google_auth_httplib2.AuthorizedHttp
678
+ # or httplib2.Http. In the first case we need to extract the wrapped
679
+ # httplib2.Http object from google_auth_httplib2.AuthorizedHttp.
680
+ http_channel = (
681
+ http.http
682
+ if google_auth_httplib2
683
+ and isinstance(http, google_auth_httplib2.AuthorizedHttp)
684
+ else http
685
+ )
686
+ http_channel.add_certificate(key_path, cert_path, "", passphrase)
687
+
688
+ # If user doesn't provide api endpoint via client options, decide which
689
+ # api endpoint to use.
690
+ if "mtlsRootUrl" in service and (
691
+ not client_options or not client_options.api_endpoint
692
+ ):
693
+ mtls_endpoint = urllib.parse.urljoin(
694
+ service["mtlsRootUrl"], service["servicePath"]
695
+ )
696
+ use_mtls_endpoint = os.getenv(GOOGLE_API_USE_MTLS_ENDPOINT, "auto")
697
+
698
+ if not use_mtls_endpoint in ("never", "auto", "always"):
699
+ raise MutualTLSChannelError(
700
+ "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always"
701
+ )
702
+
703
+ # Switch to mTLS endpoint, if environment variable is "always", or
704
+ # environment varibable is "auto" and client cert exists.
705
+ if use_mtls_endpoint == "always" or (
706
+ use_mtls_endpoint == "auto" and client_cert_to_use
707
+ ):
708
+ if HAS_UNIVERSE and universe_domain != universe.DEFAULT_UNIVERSE:
709
+ raise MutualTLSChannelError(
710
+ f"mTLS is not supported in any universe other than {universe.DEFAULT_UNIVERSE}."
711
+ )
712
+ base = mtls_endpoint
713
+ else:
714
+ # Check google-api-core >= 2.18.0 if credentials' universe != "googleapis.com".
715
+ http_credentials = getattr(http, "credentials", None)
716
+ _check_api_core_compatible_with_credentials_universe(http_credentials)
717
+
718
+ if model is None:
719
+ features = service.get("features", [])
720
+ model = JsonModel("dataWrapper" in features)
721
+
722
+ return Resource(
723
+ http=http,
724
+ baseUrl=base,
725
+ model=model,
726
+ developerKey=developerKey,
727
+ requestBuilder=requestBuilder,
728
+ resourceDesc=service,
729
+ rootDesc=service,
730
+ schema=schema,
731
+ universe_domain=universe_domain,
732
+ )
733
+
734
+
735
+ def _cast(value, schema_type):
736
+ """Convert value to a string based on JSON Schema type.
737
+
738
+ See http://tools.ietf.org/html/draft-zyp-json-schema-03 for more details on
739
+ JSON Schema.
740
+
741
+ Args:
742
+ value: any, the value to convert
743
+ schema_type: string, the type that value should be interpreted as
744
+
745
+ Returns:
746
+ A string representation of 'value' based on the schema_type.
747
+ """
748
+ if schema_type == "string":
749
+ if type(value) == type("") or type(value) == type(""):
750
+ return value
751
+ else:
752
+ return str(value)
753
+ elif schema_type == "integer":
754
+ return str(int(value))
755
+ elif schema_type == "number":
756
+ return str(float(value))
757
+ elif schema_type == "boolean":
758
+ return str(bool(value)).lower()
759
+ else:
760
+ if type(value) == type("") or type(value) == type(""):
761
+ return value
762
+ else:
763
+ return str(value)
764
+
765
+
766
+ def _media_size_to_long(maxSize):
767
+ """Convert a string media size, such as 10GB or 3TB into an integer.
768
+
769
+ Args:
770
+ maxSize: string, size as a string, such as 2MB or 7GB.
771
+
772
+ Returns:
773
+ The size as an integer value.
774
+ """
775
+ if len(maxSize) < 2:
776
+ return 0
777
+ units = maxSize[-2:].upper()
778
+ bit_shift = _MEDIA_SIZE_BIT_SHIFTS.get(units)
779
+ if bit_shift is not None:
780
+ return int(maxSize[:-2]) << bit_shift
781
+ else:
782
+ return int(maxSize)
783
+
784
+
785
+ def _media_path_url_from_info(root_desc, path_url):
786
+ """Creates an absolute media path URL.
787
+
788
+ Constructed using the API root URI and service path from the discovery
789
+ document and the relative path for the API method.
790
+
791
+ Args:
792
+ root_desc: Dictionary; the entire original deserialized discovery document.
793
+ path_url: String; the relative URL for the API method. Relative to the API
794
+ root, which is specified in the discovery document.
795
+
796
+ Returns:
797
+ String; the absolute URI for media upload for the API method.
798
+ """
799
+ return "%(root)supload/%(service_path)s%(path)s" % {
800
+ "root": root_desc["rootUrl"],
801
+ "service_path": root_desc["servicePath"],
802
+ "path": path_url,
803
+ }
804
+
805
+
806
+ def _fix_up_parameters(method_desc, root_desc, http_method, schema):
807
+ """Updates parameters of an API method with values specific to this library.
808
+
809
+ Specifically, adds whatever global parameters are specified by the API to the
810
+ parameters for the individual method. Also adds parameters which don't
811
+ appear in the discovery document, but are available to all discovery based
812
+ APIs (these are listed in STACK_QUERY_PARAMETERS).
813
+
814
+ SIDE EFFECTS: This updates the parameters dictionary object in the method
815
+ description.
816
+
817
+ Args:
818
+ method_desc: Dictionary with metadata describing an API method. Value comes
819
+ from the dictionary of methods stored in the 'methods' key in the
820
+ deserialized discovery document.
821
+ root_desc: Dictionary; the entire original deserialized discovery document.
822
+ http_method: String; the HTTP method used to call the API method described
823
+ in method_desc.
824
+ schema: Object, mapping of schema names to schema descriptions.
825
+
826
+ Returns:
827
+ The updated Dictionary stored in the 'parameters' key of the method
828
+ description dictionary.
829
+ """
830
+ parameters = method_desc.setdefault("parameters", {})
831
+
832
+ # Add in the parameters common to all methods.
833
+ for name, description in root_desc.get("parameters", {}).items():
834
+ parameters[name] = description
835
+
836
+ # Add in undocumented query parameters.
837
+ for name in STACK_QUERY_PARAMETERS:
838
+ parameters[name] = STACK_QUERY_PARAMETER_DEFAULT_VALUE.copy()
839
+
840
+ # Add 'body' (our own reserved word) to parameters if the method supports
841
+ # a request payload.
842
+ if http_method in HTTP_PAYLOAD_METHODS and "request" in method_desc:
843
+ body = BODY_PARAMETER_DEFAULT_VALUE.copy()
844
+ body.update(method_desc["request"])
845
+ parameters["body"] = body
846
+
847
+ return parameters
848
+
849
+
850
+ def _fix_up_media_upload(method_desc, root_desc, path_url, parameters):
851
+ """Adds 'media_body' and 'media_mime_type' parameters if supported by method.
852
+
853
+ SIDE EFFECTS: If there is a 'mediaUpload' in the method description, adds
854
+ 'media_upload' key to parameters.
855
+
856
+ Args:
857
+ method_desc: Dictionary with metadata describing an API method. Value comes
858
+ from the dictionary of methods stored in the 'methods' key in the
859
+ deserialized discovery document.
860
+ root_desc: Dictionary; the entire original deserialized discovery document.
861
+ path_url: String; the relative URL for the API method. Relative to the API
862
+ root, which is specified in the discovery document.
863
+ parameters: A dictionary describing method parameters for method described
864
+ in method_desc.
865
+
866
+ Returns:
867
+ Triple (accept, max_size, media_path_url) where:
868
+ - accept is a list of strings representing what content types are
869
+ accepted for media upload. Defaults to empty list if not in the
870
+ discovery document.
871
+ - max_size is a long representing the max size in bytes allowed for a
872
+ media upload. Defaults to 0L if not in the discovery document.
873
+ - media_path_url is a String; the absolute URI for media upload for the
874
+ API method. Constructed using the API root URI and service path from
875
+ the discovery document and the relative path for the API method. If
876
+ media upload is not supported, this is None.
877
+ """
878
+ media_upload = method_desc.get("mediaUpload", {})
879
+ accept = media_upload.get("accept", [])
880
+ max_size = _media_size_to_long(media_upload.get("maxSize", ""))
881
+ media_path_url = None
882
+
883
+ if media_upload:
884
+ media_path_url = _media_path_url_from_info(root_desc, path_url)
885
+ parameters["media_body"] = MEDIA_BODY_PARAMETER_DEFAULT_VALUE.copy()
886
+ parameters["media_mime_type"] = MEDIA_MIME_TYPE_PARAMETER_DEFAULT_VALUE.copy()
887
+
888
+ return accept, max_size, media_path_url
889
+
890
+
891
+ def _fix_up_method_description(method_desc, root_desc, schema):
892
+ """Updates a method description in a discovery document.
893
+
894
+ SIDE EFFECTS: Changes the parameters dictionary in the method description with
895
+ extra parameters which are used locally.
896
+
897
+ Args:
898
+ method_desc: Dictionary with metadata describing an API method. Value comes
899
+ from the dictionary of methods stored in the 'methods' key in the
900
+ deserialized discovery document.
901
+ root_desc: Dictionary; the entire original deserialized discovery document.
902
+ schema: Object, mapping of schema names to schema descriptions.
903
+
904
+ Returns:
905
+ Tuple (path_url, http_method, method_id, accept, max_size, media_path_url)
906
+ where:
907
+ - path_url is a String; the relative URL for the API method. Relative to
908
+ the API root, which is specified in the discovery document.
909
+ - http_method is a String; the HTTP method used to call the API method
910
+ described in the method description.
911
+ - method_id is a String; the name of the RPC method associated with the
912
+ API method, and is in the method description in the 'id' key.
913
+ - accept is a list of strings representing what content types are
914
+ accepted for media upload. Defaults to empty list if not in the
915
+ discovery document.
916
+ - max_size is a long representing the max size in bytes allowed for a
917
+ media upload. Defaults to 0L if not in the discovery document.
918
+ - media_path_url is a String; the absolute URI for media upload for the
919
+ API method. Constructed using the API root URI and service path from
920
+ the discovery document and the relative path for the API method. If
921
+ media upload is not supported, this is None.
922
+ """
923
+ path_url = method_desc["path"]
924
+ http_method = method_desc["httpMethod"]
925
+ method_id = method_desc["id"]
926
+
927
+ parameters = _fix_up_parameters(method_desc, root_desc, http_method, schema)
928
+ # Order is important. `_fix_up_media_upload` needs `method_desc` to have a
929
+ # 'parameters' key and needs to know if there is a 'body' parameter because it
930
+ # also sets a 'media_body' parameter.
931
+ accept, max_size, media_path_url = _fix_up_media_upload(
932
+ method_desc, root_desc, path_url, parameters
933
+ )
934
+
935
+ return path_url, http_method, method_id, accept, max_size, media_path_url
936
+
937
+
938
+ def _fix_up_media_path_base_url(media_path_url, base_url):
939
+ """
940
+ Update the media upload base url if its netloc doesn't match base url netloc.
941
+
942
+ This can happen in case the base url was overridden by
943
+ client_options.api_endpoint.
944
+
945
+ Args:
946
+ media_path_url: String; the absolute URI for media upload.
947
+ base_url: string, base URL for the API. All requests are relative to this URI.
948
+
949
+ Returns:
950
+ String; the absolute URI for media upload.
951
+ """
952
+ parsed_media_url = urllib.parse.urlparse(media_path_url)
953
+ parsed_base_url = urllib.parse.urlparse(base_url)
954
+ if parsed_media_url.netloc == parsed_base_url.netloc:
955
+ return media_path_url
956
+ return urllib.parse.urlunparse(
957
+ parsed_media_url._replace(netloc=parsed_base_url.netloc)
958
+ )
959
+
960
+
961
+ def _urljoin(base, url):
962
+ """Custom urljoin replacement supporting : before / in url."""
963
+ # In general, it's unsafe to simply join base and url. However, for
964
+ # the case of discovery documents, we know:
965
+ # * base will never contain params, query, or fragment
966
+ # * url will never contain a scheme or net_loc.
967
+ # In general, this means we can safely join on /; we just need to
968
+ # ensure we end up with precisely one / joining base and url. The
969
+ # exception here is the case of media uploads, where url will be an
970
+ # absolute url.
971
+ if url.startswith("http://") or url.startswith("https://"):
972
+ return urllib.parse.urljoin(base, url)
973
+ new_base = base if base.endswith("/") else base + "/"
974
+ new_url = url[1:] if url.startswith("/") else url
975
+ return new_base + new_url
976
+
977
+
978
+ # TODO(dhermes): Convert this class to ResourceMethod and make it callable
979
+ class ResourceMethodParameters(object):
980
+ """Represents the parameters associated with a method.
981
+
982
+ Attributes:
983
+ argmap: Map from method parameter name (string) to query parameter name
984
+ (string).
985
+ required_params: List of required parameters (represented by parameter
986
+ name as string).
987
+ repeated_params: List of repeated parameters (represented by parameter
988
+ name as string).
989
+ pattern_params: Map from method parameter name (string) to regular
990
+ expression (as a string). If the pattern is set for a parameter, the
991
+ value for that parameter must match the regular expression.
992
+ query_params: List of parameters (represented by parameter name as string)
993
+ that will be used in the query string.
994
+ path_params: Set of parameters (represented by parameter name as string)
995
+ that will be used in the base URL path.
996
+ param_types: Map from method parameter name (string) to parameter type. Type
997
+ can be any valid JSON schema type; valid values are 'any', 'array',
998
+ 'boolean', 'integer', 'number', 'object', or 'string'. Reference:
999
+ http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1
1000
+ enum_params: Map from method parameter name (string) to list of strings,
1001
+ where each list of strings is the list of acceptable enum values.
1002
+ """
1003
+
1004
+ def __init__(self, method_desc):
1005
+ """Constructor for ResourceMethodParameters.
1006
+
1007
+ Sets default values and defers to set_parameters to populate.
1008
+
1009
+ Args:
1010
+ method_desc: Dictionary with metadata describing an API method. Value
1011
+ comes from the dictionary of methods stored in the 'methods' key in
1012
+ the deserialized discovery document.
1013
+ """
1014
+ self.argmap = {}
1015
+ self.required_params = []
1016
+ self.repeated_params = []
1017
+ self.pattern_params = {}
1018
+ self.query_params = []
1019
+ # TODO(dhermes): Change path_params to a list if the extra URITEMPLATE
1020
+ # parsing is gotten rid of.
1021
+ self.path_params = set()
1022
+ self.param_types = {}
1023
+ self.enum_params = {}
1024
+
1025
+ self.set_parameters(method_desc)
1026
+
1027
+ def set_parameters(self, method_desc):
1028
+ """Populates maps and lists based on method description.
1029
+
1030
+ Iterates through each parameter for the method and parses the values from
1031
+ the parameter dictionary.
1032
+
1033
+ Args:
1034
+ method_desc: Dictionary with metadata describing an API method. Value
1035
+ comes from the dictionary of methods stored in the 'methods' key in
1036
+ the deserialized discovery document.
1037
+ """
1038
+ parameters = method_desc.get("parameters", {})
1039
+ sorted_parameters = OrderedDict(sorted(parameters.items()))
1040
+ for arg, desc in sorted_parameters.items():
1041
+ param = key2param(arg)
1042
+ self.argmap[param] = arg
1043
+
1044
+ if desc.get("pattern"):
1045
+ self.pattern_params[param] = desc["pattern"]
1046
+ if desc.get("enum"):
1047
+ self.enum_params[param] = desc["enum"]
1048
+ if desc.get("required"):
1049
+ self.required_params.append(param)
1050
+ if desc.get("repeated"):
1051
+ self.repeated_params.append(param)
1052
+ if desc.get("location") == "query":
1053
+ self.query_params.append(param)
1054
+ if desc.get("location") == "path":
1055
+ self.path_params.add(param)
1056
+ self.param_types[param] = desc.get("type", "string")
1057
+
1058
+ # TODO(dhermes): Determine if this is still necessary. Discovery based APIs
1059
+ # should have all path parameters already marked with
1060
+ # 'location: path'.
1061
+ for match in URITEMPLATE.finditer(method_desc["path"]):
1062
+ for namematch in VARNAME.finditer(match.group(0)):
1063
+ name = key2param(namematch.group(0))
1064
+ self.path_params.add(name)
1065
+ if name in self.query_params:
1066
+ self.query_params.remove(name)
1067
+
1068
+
1069
+ def createMethod(methodName, methodDesc, rootDesc, schema):
1070
+ """Creates a method for attaching to a Resource.
1071
+
1072
+ Args:
1073
+ methodName: string, name of the method to use.
1074
+ methodDesc: object, fragment of deserialized discovery document that
1075
+ describes the method.
1076
+ rootDesc: object, the entire deserialized discovery document.
1077
+ schema: object, mapping of schema names to schema descriptions.
1078
+ """
1079
+ methodName = fix_method_name(methodName)
1080
+ (
1081
+ pathUrl,
1082
+ httpMethod,
1083
+ methodId,
1084
+ accept,
1085
+ maxSize,
1086
+ mediaPathUrl,
1087
+ ) = _fix_up_method_description(methodDesc, rootDesc, schema)
1088
+
1089
+ parameters = ResourceMethodParameters(methodDesc)
1090
+
1091
+ def method(self, **kwargs):
1092
+ # Don't bother with doc string, it will be over-written by createMethod.
1093
+
1094
+ # Validate credentials for the configured universe.
1095
+ self._validate_credentials()
1096
+
1097
+ for name in kwargs:
1098
+ if name not in parameters.argmap:
1099
+ raise TypeError("Got an unexpected keyword argument {}".format(name))
1100
+
1101
+ # Remove args that have a value of None.
1102
+ keys = list(kwargs.keys())
1103
+ for name in keys:
1104
+ if kwargs[name] is None:
1105
+ del kwargs[name]
1106
+
1107
+ for name in parameters.required_params:
1108
+ if name not in kwargs:
1109
+ # temporary workaround for non-paging methods incorrectly requiring
1110
+ # page token parameter (cf. drive.changes.watch vs. drive.changes.list)
1111
+ if name not in _PAGE_TOKEN_NAMES or _findPageTokenName(
1112
+ _methodProperties(methodDesc, schema, "response")
1113
+ ):
1114
+ raise TypeError('Missing required parameter "%s"' % name)
1115
+
1116
+ for name, regex in parameters.pattern_params.items():
1117
+ if name in kwargs:
1118
+ if isinstance(kwargs[name], str):
1119
+ pvalues = [kwargs[name]]
1120
+ else:
1121
+ pvalues = kwargs[name]
1122
+ for pvalue in pvalues:
1123
+ if re.match(regex, pvalue) is None:
1124
+ raise TypeError(
1125
+ 'Parameter "%s" value "%s" does not match the pattern "%s"'
1126
+ % (name, pvalue, regex)
1127
+ )
1128
+
1129
+ for name, enums in parameters.enum_params.items():
1130
+ if name in kwargs:
1131
+ # We need to handle the case of a repeated enum
1132
+ # name differently, since we want to handle both
1133
+ # arg='value' and arg=['value1', 'value2']
1134
+ if name in parameters.repeated_params and not isinstance(
1135
+ kwargs[name], str
1136
+ ):
1137
+ values = kwargs[name]
1138
+ else:
1139
+ values = [kwargs[name]]
1140
+ for value in values:
1141
+ if value not in enums:
1142
+ raise TypeError(
1143
+ 'Parameter "%s" value "%s" is not an allowed value in "%s"'
1144
+ % (name, value, str(enums))
1145
+ )
1146
+
1147
+ actual_query_params = {}
1148
+ actual_path_params = {}
1149
+ for key, value in kwargs.items():
1150
+ to_type = parameters.param_types.get(key, "string")
1151
+ # For repeated parameters we cast each member of the list.
1152
+ if key in parameters.repeated_params and type(value) == type([]):
1153
+ cast_value = [_cast(x, to_type) for x in value]
1154
+ else:
1155
+ cast_value = _cast(value, to_type)
1156
+ if key in parameters.query_params:
1157
+ actual_query_params[parameters.argmap[key]] = cast_value
1158
+ if key in parameters.path_params:
1159
+ actual_path_params[parameters.argmap[key]] = cast_value
1160
+ body_value = kwargs.get("body", None)
1161
+ media_filename = kwargs.get("media_body", None)
1162
+ media_mime_type = kwargs.get("media_mime_type", None)
1163
+
1164
+ if self._developerKey:
1165
+ actual_query_params["key"] = self._developerKey
1166
+
1167
+ model = self._model
1168
+ if methodName.endswith("_media"):
1169
+ model = MediaModel()
1170
+ elif "response" not in methodDesc:
1171
+ model = RawModel()
1172
+
1173
+ api_version = methodDesc.get("apiVersion", None)
1174
+
1175
+ headers = {}
1176
+ headers, params, query, body = model.request(
1177
+ headers, actual_path_params, actual_query_params, body_value, api_version
1178
+ )
1179
+
1180
+ expanded_url = uritemplate.expand(pathUrl, params)
1181
+ url = _urljoin(self._baseUrl, expanded_url + query)
1182
+
1183
+ resumable = None
1184
+ multipart_boundary = ""
1185
+
1186
+ if media_filename:
1187
+ # Ensure we end up with a valid MediaUpload object.
1188
+ if isinstance(media_filename, str):
1189
+ if media_mime_type is None:
1190
+ logger.warning(
1191
+ "media_mime_type argument not specified: trying to auto-detect for %s",
1192
+ media_filename,
1193
+ )
1194
+ media_mime_type, _ = mimetypes.guess_type(media_filename)
1195
+ if media_mime_type is None:
1196
+ raise UnknownFileType(media_filename)
1197
+ if not mimeparse.best_match([media_mime_type], ",".join(accept)):
1198
+ raise UnacceptableMimeTypeError(media_mime_type)
1199
+ media_upload = MediaFileUpload(media_filename, mimetype=media_mime_type)
1200
+ elif isinstance(media_filename, MediaUpload):
1201
+ media_upload = media_filename
1202
+ else:
1203
+ raise TypeError("media_filename must be str or MediaUpload.")
1204
+
1205
+ # Check the maxSize
1206
+ if media_upload.size() is not None and media_upload.size() > maxSize > 0:
1207
+ raise MediaUploadSizeError("Media larger than: %s" % maxSize)
1208
+
1209
+ # Use the media path uri for media uploads
1210
+ expanded_url = uritemplate.expand(mediaPathUrl, params)
1211
+ url = _urljoin(self._baseUrl, expanded_url + query)
1212
+ url = _fix_up_media_path_base_url(url, self._baseUrl)
1213
+ if media_upload.resumable():
1214
+ url = _add_query_parameter(url, "uploadType", "resumable")
1215
+
1216
+ if media_upload.resumable():
1217
+ # This is all we need to do for resumable, if the body exists it gets
1218
+ # sent in the first request, otherwise an empty body is sent.
1219
+ resumable = media_upload
1220
+ else:
1221
+ # A non-resumable upload
1222
+ if body is None:
1223
+ # This is a simple media upload
1224
+ headers["content-type"] = media_upload.mimetype()
1225
+ body = media_upload.getbytes(0, media_upload.size())
1226
+ url = _add_query_parameter(url, "uploadType", "media")
1227
+ else:
1228
+ # This is a multipart/related upload.
1229
+ msgRoot = MIMEMultipart("related")
1230
+ # msgRoot should not write out it's own headers
1231
+ setattr(msgRoot, "_write_headers", lambda self: None)
1232
+
1233
+ # attach the body as one part
1234
+ msg = MIMENonMultipart(*headers["content-type"].split("/"))
1235
+ msg.set_payload(body)
1236
+ msgRoot.attach(msg)
1237
+
1238
+ # attach the media as the second part
1239
+ msg = MIMENonMultipart(*media_upload.mimetype().split("/"))
1240
+ msg["Content-Transfer-Encoding"] = "binary"
1241
+
1242
+ payload = media_upload.getbytes(0, media_upload.size())
1243
+ msg.set_payload(payload)
1244
+ msgRoot.attach(msg)
1245
+ # encode the body: note that we can't use `as_string`, because
1246
+ # it plays games with `From ` lines.
1247
+ fp = io.BytesIO()
1248
+ g = _BytesGenerator(fp, mangle_from_=False)
1249
+ g.flatten(msgRoot, unixfrom=False)
1250
+ body = fp.getvalue()
1251
+
1252
+ multipart_boundary = msgRoot.get_boundary()
1253
+ headers["content-type"] = (
1254
+ "multipart/related; " 'boundary="%s"'
1255
+ ) % multipart_boundary
1256
+ url = _add_query_parameter(url, "uploadType", "multipart")
1257
+
1258
+ logger.debug("URL being requested: %s %s" % (httpMethod, url))
1259
+ return self._requestBuilder(
1260
+ self._http,
1261
+ model.response,
1262
+ url,
1263
+ method=httpMethod,
1264
+ body=body,
1265
+ headers=headers,
1266
+ methodId=methodId,
1267
+ resumable=resumable,
1268
+ )
1269
+
1270
+ docs = [methodDesc.get("description", DEFAULT_METHOD_DOC), "\n\n"]
1271
+ if len(parameters.argmap) > 0:
1272
+ docs.append("Args:\n")
1273
+
1274
+ # Skip undocumented params and params common to all methods.
1275
+ skip_parameters = list(rootDesc.get("parameters", {}).keys())
1276
+ skip_parameters.extend(STACK_QUERY_PARAMETERS)
1277
+
1278
+ all_args = list(parameters.argmap.keys())
1279
+ args_ordered = [key2param(s) for s in methodDesc.get("parameterOrder", [])]
1280
+
1281
+ # Move body to the front of the line.
1282
+ if "body" in all_args:
1283
+ args_ordered.append("body")
1284
+
1285
+ for name in sorted(all_args):
1286
+ if name not in args_ordered:
1287
+ args_ordered.append(name)
1288
+
1289
+ for arg in args_ordered:
1290
+ if arg in skip_parameters:
1291
+ continue
1292
+
1293
+ repeated = ""
1294
+ if arg in parameters.repeated_params:
1295
+ repeated = " (repeated)"
1296
+ required = ""
1297
+ if arg in parameters.required_params:
1298
+ required = " (required)"
1299
+ paramdesc = methodDesc["parameters"][parameters.argmap[arg]]
1300
+ paramdoc = paramdesc.get("description", "A parameter")
1301
+ if "$ref" in paramdesc:
1302
+ docs.append(
1303
+ (" %s: object, %s%s%s\n The object takes the form of:\n\n%s\n\n")
1304
+ % (
1305
+ arg,
1306
+ paramdoc,
1307
+ required,
1308
+ repeated,
1309
+ schema.prettyPrintByName(paramdesc["$ref"]),
1310
+ )
1311
+ )
1312
+ else:
1313
+ paramtype = paramdesc.get("type", "string")
1314
+ docs.append(
1315
+ " %s: %s, %s%s%s\n" % (arg, paramtype, paramdoc, required, repeated)
1316
+ )
1317
+ enum = paramdesc.get("enum", [])
1318
+ enumDesc = paramdesc.get("enumDescriptions", [])
1319
+ if enum and enumDesc:
1320
+ docs.append(" Allowed values\n")
1321
+ for (name, desc) in zip(enum, enumDesc):
1322
+ docs.append(" %s - %s\n" % (name, desc))
1323
+ if "response" in methodDesc:
1324
+ if methodName.endswith("_media"):
1325
+ docs.append("\nReturns:\n The media object as a string.\n\n ")
1326
+ else:
1327
+ docs.append("\nReturns:\n An object of the form:\n\n ")
1328
+ docs.append(schema.prettyPrintSchema(methodDesc["response"]))
1329
+
1330
+ setattr(method, "__doc__", "".join(docs))
1331
+ return (methodName, method)
1332
+
1333
+
1334
+ def createNextMethod(
1335
+ methodName,
1336
+ pageTokenName="pageToken",
1337
+ nextPageTokenName="nextPageToken",
1338
+ isPageTokenParameter=True,
1339
+ ):
1340
+ """Creates any _next methods for attaching to a Resource.
1341
+
1342
+ The _next methods allow for easy iteration through list() responses.
1343
+
1344
+ Args:
1345
+ methodName: string, name of the method to use.
1346
+ pageTokenName: string, name of request page token field.
1347
+ nextPageTokenName: string, name of response page token field.
1348
+ isPageTokenParameter: Boolean, True if request page token is a query
1349
+ parameter, False if request page token is a field of the request body.
1350
+ """
1351
+ methodName = fix_method_name(methodName)
1352
+
1353
+ def methodNext(self, previous_request, previous_response):
1354
+ """Retrieves the next page of results.
1355
+
1356
+ Args:
1357
+ previous_request: The request for the previous page. (required)
1358
+ previous_response: The response from the request for the previous page. (required)
1359
+
1360
+ Returns:
1361
+ A request object that you can call 'execute()' on to request the next
1362
+ page. Returns None if there are no more items in the collection.
1363
+ """
1364
+ # Retrieve nextPageToken from previous_response
1365
+ # Use as pageToken in previous_request to create new request.
1366
+
1367
+ nextPageToken = previous_response.get(nextPageTokenName, None)
1368
+ if not nextPageToken:
1369
+ return None
1370
+
1371
+ request = copy.copy(previous_request)
1372
+
1373
+ if isPageTokenParameter:
1374
+ # Replace pageToken value in URI
1375
+ request.uri = _add_query_parameter(
1376
+ request.uri, pageTokenName, nextPageToken
1377
+ )
1378
+ logger.debug("Next page request URL: %s %s" % (methodName, request.uri))
1379
+ else:
1380
+ # Replace pageToken value in request body
1381
+ model = self._model
1382
+ body = model.deserialize(request.body)
1383
+ body[pageTokenName] = nextPageToken
1384
+ request.body = model.serialize(body)
1385
+ request.body_size = len(request.body)
1386
+ if "content-length" in request.headers:
1387
+ del request.headers["content-length"]
1388
+ logger.debug("Next page request body: %s %s" % (methodName, body))
1389
+
1390
+ return request
1391
+
1392
+ return (methodName, methodNext)
1393
+
1394
+
1395
+ class Resource(object):
1396
+ """A class for interacting with a resource."""
1397
+
1398
+ def __init__(
1399
+ self,
1400
+ http,
1401
+ baseUrl,
1402
+ model,
1403
+ requestBuilder,
1404
+ developerKey,
1405
+ resourceDesc,
1406
+ rootDesc,
1407
+ schema,
1408
+ universe_domain=universe.DEFAULT_UNIVERSE if HAS_UNIVERSE else "",
1409
+ ):
1410
+ """Build a Resource from the API description.
1411
+
1412
+ Args:
1413
+ http: httplib2.Http, Object to make http requests with.
1414
+ baseUrl: string, base URL for the API. All requests are relative to this
1415
+ URI.
1416
+ model: googleapiclient.Model, converts to and from the wire format.
1417
+ requestBuilder: class or callable that instantiates an
1418
+ googleapiclient.HttpRequest object.
1419
+ developerKey: string, key obtained from
1420
+ https://code.google.com/apis/console
1421
+ resourceDesc: object, section of deserialized discovery document that
1422
+ describes a resource. Note that the top level discovery document
1423
+ is considered a resource.
1424
+ rootDesc: object, the entire deserialized discovery document.
1425
+ schema: object, mapping of schema names to schema descriptions.
1426
+ universe_domain: string, the universe for the API. The default universe
1427
+ is "googleapis.com".
1428
+ """
1429
+ self._dynamic_attrs = []
1430
+
1431
+ self._http = http
1432
+ self._baseUrl = baseUrl
1433
+ self._model = model
1434
+ self._developerKey = developerKey
1435
+ self._requestBuilder = requestBuilder
1436
+ self._resourceDesc = resourceDesc
1437
+ self._rootDesc = rootDesc
1438
+ self._schema = schema
1439
+ self._universe_domain = universe_domain
1440
+ self._credentials_validated = False
1441
+
1442
+ self._set_service_methods()
1443
+
1444
+ def _set_dynamic_attr(self, attr_name, value):
1445
+ """Sets an instance attribute and tracks it in a list of dynamic attributes.
1446
+
1447
+ Args:
1448
+ attr_name: string; The name of the attribute to be set
1449
+ value: The value being set on the object and tracked in the dynamic cache.
1450
+ """
1451
+ self._dynamic_attrs.append(attr_name)
1452
+ self.__dict__[attr_name] = value
1453
+
1454
+ def __getstate__(self):
1455
+ """Trim the state down to something that can be pickled.
1456
+
1457
+ Uses the fact that the instance variable _dynamic_attrs holds attrs that
1458
+ will be wiped and restored on pickle serialization.
1459
+ """
1460
+ state_dict = copy.copy(self.__dict__)
1461
+ for dynamic_attr in self._dynamic_attrs:
1462
+ del state_dict[dynamic_attr]
1463
+ del state_dict["_dynamic_attrs"]
1464
+ return state_dict
1465
+
1466
+ def __setstate__(self, state):
1467
+ """Reconstitute the state of the object from being pickled.
1468
+
1469
+ Uses the fact that the instance variable _dynamic_attrs holds attrs that
1470
+ will be wiped and restored on pickle serialization.
1471
+ """
1472
+ self.__dict__.update(state)
1473
+ self._dynamic_attrs = []
1474
+ self._set_service_methods()
1475
+
1476
+ def __enter__(self):
1477
+ return self
1478
+
1479
+ def __exit__(self, exc_type, exc, exc_tb):
1480
+ self.close()
1481
+
1482
+ def close(self):
1483
+ """Close httplib2 connections."""
1484
+ # httplib2 leaves sockets open by default.
1485
+ # Cleanup using the `close` method.
1486
+ # https://github.com/httplib2/httplib2/issues/148
1487
+ self._http.close()
1488
+
1489
+ def _set_service_methods(self):
1490
+ self._add_basic_methods(self._resourceDesc, self._rootDesc, self._schema)
1491
+ self._add_nested_resources(self._resourceDesc, self._rootDesc, self._schema)
1492
+ self._add_next_methods(self._resourceDesc, self._schema)
1493
+
1494
+ def _add_basic_methods(self, resourceDesc, rootDesc, schema):
1495
+ # If this is the root Resource, add a new_batch_http_request() method.
1496
+ if resourceDesc == rootDesc:
1497
+ batch_uri = "%s%s" % (
1498
+ rootDesc["rootUrl"],
1499
+ rootDesc.get("batchPath", "batch"),
1500
+ )
1501
+
1502
+ def new_batch_http_request(callback=None):
1503
+ """Create a BatchHttpRequest object based on the discovery document.
1504
+
1505
+ Args:
1506
+ callback: callable, A callback to be called for each response, of the
1507
+ form callback(id, response, exception). The first parameter is the
1508
+ request id, and the second is the deserialized response object. The
1509
+ third is an apiclient.errors.HttpError exception object if an HTTP
1510
+ error occurred while processing the request, or None if no error
1511
+ occurred.
1512
+
1513
+ Returns:
1514
+ A BatchHttpRequest object based on the discovery document.
1515
+ """
1516
+ return BatchHttpRequest(callback=callback, batch_uri=batch_uri)
1517
+
1518
+ self._set_dynamic_attr("new_batch_http_request", new_batch_http_request)
1519
+
1520
+ # Add basic methods to Resource
1521
+ if "methods" in resourceDesc:
1522
+ for methodName, methodDesc in resourceDesc["methods"].items():
1523
+ fixedMethodName, method = createMethod(
1524
+ methodName, methodDesc, rootDesc, schema
1525
+ )
1526
+ self._set_dynamic_attr(
1527
+ fixedMethodName, method.__get__(self, self.__class__)
1528
+ )
1529
+ # Add in _media methods. The functionality of the attached method will
1530
+ # change when it sees that the method name ends in _media.
1531
+ if methodDesc.get("supportsMediaDownload", False):
1532
+ fixedMethodName, method = createMethod(
1533
+ methodName + "_media", methodDesc, rootDesc, schema
1534
+ )
1535
+ self._set_dynamic_attr(
1536
+ fixedMethodName, method.__get__(self, self.__class__)
1537
+ )
1538
+
1539
+ def _add_nested_resources(self, resourceDesc, rootDesc, schema):
1540
+ # Add in nested resources
1541
+ if "resources" in resourceDesc:
1542
+
1543
+ def createResourceMethod(methodName, methodDesc):
1544
+ """Create a method on the Resource to access a nested Resource.
1545
+
1546
+ Args:
1547
+ methodName: string, name of the method to use.
1548
+ methodDesc: object, fragment of deserialized discovery document that
1549
+ describes the method.
1550
+ """
1551
+ methodName = fix_method_name(methodName)
1552
+
1553
+ def methodResource(self):
1554
+ return Resource(
1555
+ http=self._http,
1556
+ baseUrl=self._baseUrl,
1557
+ model=self._model,
1558
+ developerKey=self._developerKey,
1559
+ requestBuilder=self._requestBuilder,
1560
+ resourceDesc=methodDesc,
1561
+ rootDesc=rootDesc,
1562
+ schema=schema,
1563
+ universe_domain=self._universe_domain,
1564
+ )
1565
+
1566
+ setattr(methodResource, "__doc__", "A collection resource.")
1567
+ setattr(methodResource, "__is_resource__", True)
1568
+
1569
+ return (methodName, methodResource)
1570
+
1571
+ for methodName, methodDesc in resourceDesc["resources"].items():
1572
+ fixedMethodName, method = createResourceMethod(methodName, methodDesc)
1573
+ self._set_dynamic_attr(
1574
+ fixedMethodName, method.__get__(self, self.__class__)
1575
+ )
1576
+
1577
+ def _add_next_methods(self, resourceDesc, schema):
1578
+ # Add _next() methods if and only if one of the names 'pageToken' or
1579
+ # 'nextPageToken' occurs among the fields of both the method's response
1580
+ # type either the method's request (query parameters) or request body.
1581
+ if "methods" not in resourceDesc:
1582
+ return
1583
+ for methodName, methodDesc in resourceDesc["methods"].items():
1584
+ nextPageTokenName = _findPageTokenName(
1585
+ _methodProperties(methodDesc, schema, "response")
1586
+ )
1587
+ if not nextPageTokenName:
1588
+ continue
1589
+ isPageTokenParameter = True
1590
+ pageTokenName = _findPageTokenName(methodDesc.get("parameters", {}))
1591
+ if not pageTokenName:
1592
+ isPageTokenParameter = False
1593
+ pageTokenName = _findPageTokenName(
1594
+ _methodProperties(methodDesc, schema, "request")
1595
+ )
1596
+ if not pageTokenName:
1597
+ continue
1598
+ fixedMethodName, method = createNextMethod(
1599
+ methodName + "_next",
1600
+ pageTokenName,
1601
+ nextPageTokenName,
1602
+ isPageTokenParameter,
1603
+ )
1604
+ self._set_dynamic_attr(
1605
+ fixedMethodName, method.__get__(self, self.__class__)
1606
+ )
1607
+
1608
+ def _validate_credentials(self):
1609
+ """Validates client's and credentials' universe domains are consistent.
1610
+
1611
+ Returns:
1612
+ bool: True iff the configured universe domain is valid.
1613
+
1614
+ Raises:
1615
+ UniverseMismatchError: If the configured universe domain is not valid.
1616
+ """
1617
+ credentials = getattr(self._http, "credentials", None)
1618
+
1619
+ self._credentials_validated = (
1620
+ (
1621
+ self._credentials_validated
1622
+ or universe.compare_domains(self._universe_domain, credentials)
1623
+ )
1624
+ if HAS_UNIVERSE
1625
+ else True
1626
+ )
1627
+ return self._credentials_validated
1628
+
1629
+
1630
+ def _findPageTokenName(fields):
1631
+ """Search field names for one like a page token.
1632
+
1633
+ Args:
1634
+ fields: container of string, names of fields.
1635
+
1636
+ Returns:
1637
+ First name that is either 'pageToken' or 'nextPageToken' if one exists,
1638
+ otherwise None.
1639
+ """
1640
+ return next(
1641
+ (tokenName for tokenName in _PAGE_TOKEN_NAMES if tokenName in fields), None
1642
+ )
1643
+
1644
+
1645
+ def _methodProperties(methodDesc, schema, name):
1646
+ """Get properties of a field in a method description.
1647
+
1648
+ Args:
1649
+ methodDesc: object, fragment of deserialized discovery document that
1650
+ describes the method.
1651
+ schema: object, mapping of schema names to schema descriptions.
1652
+ name: string, name of top-level field in method description.
1653
+
1654
+ Returns:
1655
+ Object representing fragment of deserialized discovery document
1656
+ corresponding to 'properties' field of object corresponding to named field
1657
+ in method description, if it exists, otherwise empty dict.
1658
+ """
1659
+ desc = methodDesc.get(name, {})
1660
+ if "$ref" in desc:
1661
+ desc = schema.get(desc["$ref"], {})
1662
+ return desc.get("properties", {})
.venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__init__.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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
+ """Caching utility for the discovery document."""
16
+
17
+ from __future__ import absolute_import
18
+
19
+ import logging
20
+ import os
21
+
22
+ LOGGER = logging.getLogger(__name__)
23
+
24
+ DISCOVERY_DOC_MAX_AGE = 60 * 60 * 24 # 1 day
25
+ DISCOVERY_DOC_DIR = os.path.join(
26
+ os.path.dirname(os.path.realpath(__file__)), "documents"
27
+ )
28
+
29
+
30
+ def autodetect():
31
+ """Detects an appropriate cache module and returns it.
32
+
33
+ Returns:
34
+ googleapiclient.discovery_cache.base.Cache, a cache object which
35
+ is auto detected, or None if no cache object is available.
36
+ """
37
+ if "GAE_ENV" in os.environ:
38
+ try:
39
+ from . import appengine_memcache
40
+
41
+ return appengine_memcache.cache
42
+ except Exception:
43
+ pass
44
+ try:
45
+ from . import file_cache
46
+
47
+ return file_cache.cache
48
+ except Exception:
49
+ LOGGER.info(
50
+ "file_cache is only supported with oauth2client<4.0.0", exc_info=False
51
+ )
52
+ return None
53
+
54
+
55
+ def get_static_doc(serviceName, version):
56
+ """Retrieves the discovery document from the directory defined in
57
+ DISCOVERY_DOC_DIR corresponding to the serviceName and version provided.
58
+
59
+ Args:
60
+ serviceName: string, name of the service.
61
+ version: string, the version of the service.
62
+
63
+ Returns:
64
+ A string containing the contents of the JSON discovery document,
65
+ otherwise None if the JSON discovery document was not found.
66
+ """
67
+
68
+ content = None
69
+ doc_name = "{}.{}.json".format(serviceName, version)
70
+
71
+ try:
72
+ with open(os.path.join(DISCOVERY_DOC_DIR, doc_name), "r") as f:
73
+ content = f.read()
74
+ except FileNotFoundError:
75
+ # File does not exist. Nothing to do here.
76
+ pass
77
+
78
+ return content
.venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (2.87 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/appengine_memcache.cpython-311.pyc ADDED
Binary file (2.24 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/base.cpython-311.pyc ADDED
Binary file (1.49 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/__pycache__/file_cache.cpython-311.pyc ADDED
Binary file (7.44 kB). View file
 
.venv/lib/python3.11/site-packages/googleapiclient/discovery_cache/appengine_memcache.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2014 Google Inc. All Rights Reserved.
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
+ """App Engine memcache based cache for the discovery document."""
16
+
17
+ import logging
18
+
19
+ # This is only an optional dependency because we only import this
20
+ # module when google.appengine.api.memcache is available.
21
+ from google.appengine.api import memcache
22
+
23
+ from . import base
24
+ from ..discovery_cache import DISCOVERY_DOC_MAX_AGE
25
+
26
+ LOGGER = logging.getLogger(__name__)
27
+
28
+ NAMESPACE = "google-api-client"
29
+
30
+
31
+ class Cache(base.Cache):
32
+ """A cache with app engine memcache API."""
33
+
34
+ def __init__(self, max_age):
35
+ """Constructor.
36
+
37
+ Args:
38
+ max_age: Cache expiration in seconds.
39
+ """
40
+ self._max_age = max_age
41
+
42
+ def get(self, url):
43
+ try:
44
+ return memcache.get(url, namespace=NAMESPACE)
45
+ except Exception as e:
46
+ LOGGER.warning(e, exc_info=True)
47
+
48
+ def set(self, url, content):
49
+ try:
50
+ memcache.set(url, content, time=int(self._max_age), namespace=NAMESPACE)
51
+ except Exception as e:
52
+ LOGGER.warning(e, exc_info=True)
53
+
54
+
55
+ cache = Cache(max_age=DISCOVERY_DOC_MAX_AGE)