koichi12 commited on
Commit
c450656
·
verified ·
1 Parent(s): 8ffa840

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/httptools/__init__.py +6 -0
  2. .venv/lib/python3.11/site-packages/httptools/__pycache__/__init__.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/httptools/__pycache__/_version.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/httptools/_version.py +13 -0
  5. .venv/lib/python3.11/site-packages/httptools/parser/__init__.py +5 -0
  6. .venv/lib/python3.11/site-packages/httptools/parser/__pycache__/__init__.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/httptools/parser/__pycache__/errors.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/httptools/parser/cparser.pxd +167 -0
  9. .venv/lib/python3.11/site-packages/httptools/parser/errors.py +30 -0
  10. .venv/lib/python3.11/site-packages/httptools/parser/parser.pyx +436 -0
  11. .venv/lib/python3.11/site-packages/httptools/parser/python.pxd +6 -0
  12. .venv/lib/python3.11/site-packages/httptools/parser/url_cparser.pxd +31 -0
  13. .venv/lib/python3.11/site-packages/httptools/parser/url_parser.pyx +108 -0
  14. .venv/lib/python3.11/site-packages/networkx/utils/tests/__init__.py +0 -0
  15. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  16. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test__init.cpython-311.pyc +0 -0
  17. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
  18. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_config.cpython-311.pyc +0 -0
  19. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_decorators.cpython-311.pyc +0 -0
  20. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_heaps.cpython-311.pyc +0 -0
  21. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_mapped_queue.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_misc.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_random_sequence.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_rcm.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_unionfind.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/networkx/utils/tests/test_config.py +231 -0
  27. .venv/lib/python3.11/site-packages/networkx/utils/tests/test_mapped_queue.py +268 -0
  28. .venv/lib/python3.11/site-packages/networkx/utils/tests/test_misc.py +268 -0
  29. .venv/lib/python3.11/site-packages/networkx/utils/tests/test_random_sequence.py +38 -0
  30. .venv/lib/python3.11/site-packages/oauth2client/__init__.py +24 -0
  31. .venv/lib/python3.11/site-packages/oauth2client/_helpers.py +341 -0
  32. .venv/lib/python3.11/site-packages/oauth2client/_openssl_crypt.py +136 -0
  33. .venv/lib/python3.11/site-packages/oauth2client/_pkce.py +67 -0
  34. .venv/lib/python3.11/site-packages/oauth2client/_pure_python_crypt.py +184 -0
  35. .venv/lib/python3.11/site-packages/oauth2client/_pycrypto_crypt.py +124 -0
  36. .venv/lib/python3.11/site-packages/oauth2client/client.py +2170 -0
  37. .venv/lib/python3.11/site-packages/oauth2client/clientsecrets.py +173 -0
  38. .venv/lib/python3.11/site-packages/oauth2client/contrib/__init__.py +6 -0
  39. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/__init__.cpython-311.pyc +0 -0
  40. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/_appengine_ndb.cpython-311.pyc +0 -0
  41. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/_metadata.cpython-311.pyc +0 -0
  42. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/appengine.cpython-311.pyc +0 -0
  43. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/devshell.cpython-311.pyc +0 -0
  44. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/dictionary_storage.cpython-311.pyc +0 -0
  45. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/flask_util.cpython-311.pyc +0 -0
  46. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/gce.cpython-311.pyc +0 -0
  47. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/keyring_storage.cpython-311.pyc +0 -0
  48. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/multiprocess_file_storage.cpython-311.pyc +0 -0
  49. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/sqlalchemy.cpython-311.pyc +0 -0
  50. .venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/xsrfutil.cpython-311.pyc +0 -0
.venv/lib/python3.11/site-packages/httptools/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from . import parser
2
+ from .parser import * # NOQA
3
+
4
+ from ._version import __version__ # NOQA
5
+
6
+ __all__ = parser.__all__ + ('__version__',) # NOQA
.venv/lib/python3.11/site-packages/httptools/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (352 Bytes). View file
 
.venv/lib/python3.11/site-packages/httptools/__pycache__/_version.cpython-311.pyc ADDED
Binary file (204 Bytes). View file
 
.venv/lib/python3.11/site-packages/httptools/_version.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file MUST NOT contain anything but the __version__ assignment.
2
+ #
3
+ # When making a release, change the value of __version__
4
+ # to an appropriate value, and open a pull request against
5
+ # the correct branch (master if making a new feature release).
6
+ # The commit message MUST contain a properly formatted release
7
+ # log, and the commit must be signed.
8
+ #
9
+ # The release automation will: build and test the packages for the
10
+ # supported platforms, publish the packages on PyPI, merge the PR
11
+ # to the target branch, create a Git tag pointing to the commit.
12
+
13
+ __version__ = '0.6.4'
.venv/lib/python3.11/site-packages/httptools/parser/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from .parser import * # NoQA
2
+ from .errors import * # NoQA
3
+ from .url_parser import * # NoQA
4
+
5
+ __all__ = parser.__all__ + errors.__all__ + url_parser.__all__ # NoQA
.venv/lib/python3.11/site-packages/httptools/parser/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (355 Bytes). View file
 
.venv/lib/python3.11/site-packages/httptools/parser/__pycache__/errors.cpython-311.pyc ADDED
Binary file (1.45 kB). View file
 
.venv/lib/python3.11/site-packages/httptools/parser/cparser.pxd ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t
2
+
3
+
4
+ cdef extern from "llhttp.h":
5
+ struct llhttp__internal_s:
6
+ int32_t _index
7
+ void *_span_pos0
8
+ void *_span_cb0
9
+ int32_t error
10
+ const char *reason
11
+ const char *error_pos
12
+ void *data
13
+ void *_current
14
+ uint64_t content_length
15
+ uint8_t type
16
+ uint8_t method
17
+ uint8_t http_major
18
+ uint8_t http_minor
19
+ uint8_t header_state
20
+ uint16_t flags
21
+ uint8_t upgrade
22
+ uint16_t status_code
23
+ uint8_t finish
24
+ void *settings
25
+ ctypedef llhttp__internal_s llhttp__internal_t
26
+ ctypedef llhttp__internal_t llhttp_t
27
+
28
+ ctypedef int (*llhttp_data_cb) (llhttp_t*,
29
+ const char *at,
30
+ size_t length) except -1
31
+
32
+ ctypedef int (*llhttp_cb) (llhttp_t*) except -1
33
+
34
+ struct llhttp_settings_s:
35
+ llhttp_cb on_message_begin
36
+ llhttp_data_cb on_url
37
+ llhttp_data_cb on_status
38
+ llhttp_data_cb on_header_field
39
+ llhttp_data_cb on_header_value
40
+ llhttp_cb on_headers_complete
41
+ llhttp_data_cb on_body
42
+ llhttp_cb on_message_complete
43
+ llhttp_cb on_chunk_header
44
+ llhttp_cb on_chunk_complete
45
+ ctypedef llhttp_settings_s llhttp_settings_t
46
+
47
+ enum llhttp_type:
48
+ HTTP_BOTH,
49
+ HTTP_REQUEST,
50
+ HTTP_RESPONSE
51
+ ctypedef llhttp_type llhttp_type_t
52
+
53
+ enum llhttp_errno:
54
+ HPE_OK,
55
+ HPE_INTERNAL,
56
+ HPE_STRICT,
57
+ HPE_LF_EXPECTED,
58
+ HPE_UNEXPECTED_CONTENT_LENGTH,
59
+ HPE_CLOSED_CONNECTION,
60
+ HPE_INVALID_METHOD,
61
+ HPE_INVALID_URL,
62
+ HPE_INVALID_CONSTANT,
63
+ HPE_INVALID_VERSION,
64
+ HPE_INVALID_HEADER_TOKEN,
65
+ HPE_INVALID_CONTENT_LENGTH,
66
+ HPE_INVALID_CHUNK_SIZE,
67
+ HPE_INVALID_STATUS,
68
+ HPE_INVALID_EOF_STATE,
69
+ HPE_INVALID_TRANSFER_ENCODING,
70
+ HPE_CB_MESSAGE_BEGIN,
71
+ HPE_CB_HEADERS_COMPLETE,
72
+ HPE_CB_MESSAGE_COMPLETE,
73
+ HPE_CB_CHUNK_HEADER,
74
+ HPE_CB_CHUNK_COMPLETE,
75
+ HPE_PAUSED,
76
+ HPE_PAUSED_UPGRADE,
77
+ HPE_USER
78
+ ctypedef llhttp_errno llhttp_errno_t
79
+
80
+ enum llhttp_flags:
81
+ F_CONNECTION_KEEP_ALIVE,
82
+ F_CONNECTION_CLOSE,
83
+ F_CONNECTION_UPGRADE,
84
+ F_CHUNKED,
85
+ F_UPGRADE,
86
+ F_CONTENT_LENGTH,
87
+ F_SKIPBODY,
88
+ F_TRAILING,
89
+ F_LENIENT,
90
+ F_TRANSFER_ENCODING
91
+ ctypedef llhttp_flags llhttp_flags_t
92
+
93
+ enum llhttp_method:
94
+ HTTP_DELETE,
95
+ HTTP_GET,
96
+ HTTP_HEAD,
97
+ HTTP_POST,
98
+ HTTP_PUT,
99
+ HTTP_CONNECT,
100
+ HTTP_OPTIONS,
101
+ HTTP_TRACE,
102
+ HTTP_COPY,
103
+ HTTP_LOCK,
104
+ HTTP_MKCOL,
105
+ HTTP_MOVE,
106
+ HTTP_PROPFIND,
107
+ HTTP_PROPPATCH,
108
+ HTTP_SEARCH,
109
+ HTTP_UNLOCK,
110
+ HTTP_BIND,
111
+ HTTP_REBIND,
112
+ HTTP_UNBIND,
113
+ HTTP_ACL,
114
+ HTTP_REPORT,
115
+ HTTP_MKACTIVITY,
116
+ HTTP_CHECKOUT,
117
+ HTTP_MERGE,
118
+ HTTP_MSEARCH,
119
+ HTTP_NOTIFY,
120
+ HTTP_SUBSCRIBE,
121
+ HTTP_UNSUBSCRIBE,
122
+ HTTP_PATCH,
123
+ HTTP_PURGE,
124
+ HTTP_MKCALENDAR,
125
+ HTTP_LINK,
126
+ HTTP_UNLINK,
127
+ HTTP_SOURCE,
128
+ HTTP_PRI,
129
+ HTTP_DESCRIBE,
130
+ HTTP_ANNOUNCE,
131
+ HTTP_SETUP,
132
+ HTTP_PLAY,
133
+ HTTP_PAUSE,
134
+ HTTP_TEARDOWN,
135
+ HTTP_GET_PARAMETER,
136
+ HTTP_SET_PARAMETER,
137
+ HTTP_REDIRECT,
138
+ HTTP_RECORD,
139
+ HTTP_FLUSH
140
+ ctypedef llhttp_method llhttp_method_t
141
+
142
+ void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings)
143
+
144
+ void llhttp_settings_init(llhttp_settings_t* settings)
145
+
146
+ llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len)
147
+
148
+ void llhttp_resume_after_upgrade(llhttp_t* parser)
149
+
150
+ int llhttp_should_keep_alive(const llhttp_t* parser)
151
+
152
+ const char* llhttp_get_error_pos(const llhttp_t* parser)
153
+ const char* llhttp_get_error_reason(const llhttp_t* parser)
154
+ const char* llhttp_method_name(llhttp_method_t method)
155
+
156
+ void llhttp_set_error_reason(llhttp_t* parser, const char* reason);
157
+
158
+ void llhttp_set_lenient_headers(llhttp_t* parser, bint enabled);
159
+ void llhttp_set_lenient_chunked_length(llhttp_t* parser, bint enabled);
160
+ void llhttp_set_lenient_keep_alive(llhttp_t* parser, bint enabled);
161
+ void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, bint enabled);
162
+ void llhttp_set_lenient_version(llhttp_t* parser, bint enabled);
163
+ void llhttp_set_lenient_data_after_close(llhttp_t* parser, bint enabled);
164
+ void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, bint enabled);
165
+ void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, bint enabled);
166
+ void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, bint enabled);
167
+ void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, bint enabled);
.venv/lib/python3.11/site-packages/httptools/parser/errors.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __all__ = ('HttpParserError',
2
+ 'HttpParserCallbackError',
3
+ 'HttpParserInvalidStatusError',
4
+ 'HttpParserInvalidMethodError',
5
+ 'HttpParserInvalidURLError',
6
+ 'HttpParserUpgrade')
7
+
8
+
9
+ class HttpParserError(Exception):
10
+ pass
11
+
12
+
13
+ class HttpParserCallbackError(HttpParserError):
14
+ pass
15
+
16
+
17
+ class HttpParserInvalidStatusError(HttpParserError):
18
+ pass
19
+
20
+
21
+ class HttpParserInvalidMethodError(HttpParserError):
22
+ pass
23
+
24
+
25
+ class HttpParserInvalidURLError(HttpParserError):
26
+ pass
27
+
28
+
29
+ class HttpParserUpgrade(Exception):
30
+ pass
.venv/lib/python3.11/site-packages/httptools/parser/parser.pyx ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #cython: language_level=3
2
+
3
+ from __future__ import print_function
4
+ from typing import Optional
5
+
6
+ from cpython.mem cimport PyMem_Malloc, PyMem_Free
7
+ from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
8
+ Py_buffer, PyBytes_AsString
9
+
10
+ from .python cimport PyMemoryView_Check, PyMemoryView_GET_BUFFER
11
+
12
+
13
+ from .errors import (HttpParserError,
14
+ HttpParserCallbackError,
15
+ HttpParserInvalidStatusError,
16
+ HttpParserInvalidMethodError,
17
+ HttpParserInvalidURLError,
18
+ HttpParserUpgrade)
19
+
20
+ cimport cython
21
+ from . cimport cparser
22
+
23
+
24
+ __all__ = ('HttpRequestParser', 'HttpResponseParser')
25
+
26
+
27
+ @cython.internal
28
+ cdef class HttpParser:
29
+
30
+ cdef:
31
+ cparser.llhttp_t* _cparser
32
+ cparser.llhttp_settings_t* _csettings
33
+
34
+ bytes _current_header_name
35
+ bytes _current_header_value
36
+
37
+ _proto_on_url, _proto_on_status, _proto_on_body, \
38
+ _proto_on_header, _proto_on_headers_complete, \
39
+ _proto_on_message_complete, _proto_on_chunk_header, \
40
+ _proto_on_chunk_complete, _proto_on_message_begin
41
+
42
+ object _last_error
43
+
44
+ Py_buffer py_buf
45
+
46
+ def __cinit__(self):
47
+ self._cparser = <cparser.llhttp_t*> \
48
+ PyMem_Malloc(sizeof(cparser.llhttp_t))
49
+ if self._cparser is NULL:
50
+ raise MemoryError()
51
+
52
+ self._csettings = <cparser.llhttp_settings_t*> \
53
+ PyMem_Malloc(sizeof(cparser.llhttp_settings_t))
54
+ if self._csettings is NULL:
55
+ raise MemoryError()
56
+
57
+ def __dealloc__(self):
58
+ PyMem_Free(self._cparser)
59
+ PyMem_Free(self._csettings)
60
+
61
+ cdef _init(self, protocol, cparser.llhttp_type_t mode):
62
+ cparser.llhttp_settings_init(self._csettings)
63
+
64
+ cparser.llhttp_init(self._cparser, mode, self._csettings)
65
+ self._cparser.data = <void*>self
66
+
67
+ self._current_header_name = None
68
+ self._current_header_value = None
69
+
70
+ self._proto_on_header = getattr(protocol, 'on_header', None)
71
+ if self._proto_on_header is not None:
72
+ self._csettings.on_header_field = cb_on_header_field
73
+ self._csettings.on_header_value = cb_on_header_value
74
+ self._proto_on_headers_complete = getattr(
75
+ protocol, 'on_headers_complete', None)
76
+ self._csettings.on_headers_complete = cb_on_headers_complete
77
+
78
+ self._proto_on_body = getattr(protocol, 'on_body', None)
79
+ if self._proto_on_body is not None:
80
+ self._csettings.on_body = cb_on_body
81
+
82
+ self._proto_on_message_begin = getattr(
83
+ protocol, 'on_message_begin', None)
84
+ if self._proto_on_message_begin is not None:
85
+ self._csettings.on_message_begin = cb_on_message_begin
86
+
87
+ self._proto_on_message_complete = getattr(
88
+ protocol, 'on_message_complete', None)
89
+ if self._proto_on_message_complete is not None:
90
+ self._csettings.on_message_complete = cb_on_message_complete
91
+
92
+ self._proto_on_chunk_header = getattr(
93
+ protocol, 'on_chunk_header', None)
94
+ self._csettings.on_chunk_header = cb_on_chunk_header
95
+
96
+ self._proto_on_chunk_complete = getattr(
97
+ protocol, 'on_chunk_complete', None)
98
+ self._csettings.on_chunk_complete = cb_on_chunk_complete
99
+
100
+ self._last_error = None
101
+
102
+ cdef _maybe_call_on_header(self):
103
+ if self._current_header_value is not None:
104
+ current_header_name = self._current_header_name
105
+ current_header_value = self._current_header_value
106
+
107
+ self._current_header_name = self._current_header_value = None
108
+
109
+ if self._proto_on_header is not None:
110
+ self._proto_on_header(current_header_name,
111
+ current_header_value)
112
+
113
+ cdef _on_header_field(self, bytes field):
114
+ self._maybe_call_on_header()
115
+ if self._current_header_name is None:
116
+ self._current_header_name = field
117
+ else:
118
+ self._current_header_name += field
119
+
120
+ cdef _on_header_value(self, bytes val):
121
+ if self._current_header_value is None:
122
+ self._current_header_value = val
123
+ else:
124
+ # This is unlikely, as mostly HTTP headers are one-line
125
+ self._current_header_value += val
126
+
127
+ cdef _on_headers_complete(self):
128
+ self._maybe_call_on_header()
129
+
130
+ if self._proto_on_headers_complete is not None:
131
+ self._proto_on_headers_complete()
132
+
133
+ cdef _on_chunk_header(self):
134
+ if (self._current_header_value is not None or
135
+ self._current_header_name is not None):
136
+ raise HttpParserError('invalid headers state')
137
+
138
+ if self._proto_on_chunk_header is not None:
139
+ self._proto_on_chunk_header()
140
+
141
+ cdef _on_chunk_complete(self):
142
+ self._maybe_call_on_header()
143
+
144
+ if self._proto_on_chunk_complete is not None:
145
+ self._proto_on_chunk_complete()
146
+
147
+ ### Public API ###
148
+
149
+ def set_dangerous_leniencies(
150
+ self,
151
+ lenient_headers: Optional[bool] = None,
152
+ lenient_chunked_length: Optional[bool] = None,
153
+ lenient_keep_alive: Optional[bool] = None,
154
+ lenient_transfer_encoding: Optional[bool] = None,
155
+ lenient_version: Optional[bool] = None,
156
+ lenient_data_after_close: Optional[bool] = None,
157
+ lenient_optional_lf_after_cr: Optional[bool] = None,
158
+ lenient_optional_cr_before_lf: Optional[bool] = None,
159
+ lenient_optional_crlf_after_chunk: Optional[bool] = None,
160
+ lenient_spaces_after_chunk_size: Optional[bool] = None,
161
+ ):
162
+ cdef cparser.llhttp_t* parser = self._cparser
163
+ if lenient_headers is not None:
164
+ cparser.llhttp_set_lenient_headers(
165
+ parser, lenient_headers)
166
+ if lenient_chunked_length is not None:
167
+ cparser.llhttp_set_lenient_chunked_length(
168
+ parser, lenient_chunked_length)
169
+ if lenient_keep_alive is not None:
170
+ cparser.llhttp_set_lenient_keep_alive(
171
+ parser, lenient_keep_alive)
172
+ if lenient_transfer_encoding is not None:
173
+ cparser.llhttp_set_lenient_transfer_encoding(
174
+ parser, lenient_transfer_encoding)
175
+ if lenient_version is not None:
176
+ cparser.llhttp_set_lenient_version(
177
+ parser, lenient_version)
178
+ if lenient_data_after_close is not None:
179
+ cparser.llhttp_set_lenient_data_after_close(
180
+ parser, lenient_data_after_close)
181
+ if lenient_optional_lf_after_cr is not None:
182
+ cparser.llhttp_set_lenient_optional_lf_after_cr(
183
+ parser, lenient_optional_lf_after_cr)
184
+ if lenient_optional_cr_before_lf is not None:
185
+ cparser.llhttp_set_lenient_optional_cr_before_lf(
186
+ parser, lenient_optional_cr_before_lf)
187
+ if lenient_optional_crlf_after_chunk is not None:
188
+ cparser.llhttp_set_lenient_optional_crlf_after_chunk(
189
+ parser, lenient_optional_crlf_after_chunk)
190
+ if lenient_spaces_after_chunk_size is not None:
191
+ cparser.llhttp_set_lenient_spaces_after_chunk_size(
192
+ parser, lenient_spaces_after_chunk_size)
193
+
194
+ def get_http_version(self):
195
+ cdef cparser.llhttp_t* parser = self._cparser
196
+ return '{}.{}'.format(parser.http_major, parser.http_minor)
197
+
198
+ def should_keep_alive(self):
199
+ return bool(cparser.llhttp_should_keep_alive(self._cparser))
200
+
201
+ def should_upgrade(self):
202
+ cdef cparser.llhttp_t* parser = self._cparser
203
+ return bool(parser.upgrade)
204
+
205
+ def feed_data(self, data):
206
+ cdef:
207
+ size_t data_len
208
+ cparser.llhttp_errno_t err
209
+ Py_buffer *buf
210
+ bint owning_buf = False
211
+ const char* err_pos
212
+
213
+ if PyMemoryView_Check(data):
214
+ buf = PyMemoryView_GET_BUFFER(data)
215
+ data_len = <size_t>buf.len
216
+ err = cparser.llhttp_execute(
217
+ self._cparser,
218
+ <char*>buf.buf,
219
+ data_len)
220
+
221
+ else:
222
+ buf = &self.py_buf
223
+ PyObject_GetBuffer(data, buf, PyBUF_SIMPLE)
224
+ owning_buf = True
225
+ data_len = <size_t>buf.len
226
+
227
+ err = cparser.llhttp_execute(
228
+ self._cparser,
229
+ <char*>buf.buf,
230
+ data_len)
231
+
232
+ try:
233
+ if self._cparser.upgrade == 1 and err == cparser.HPE_PAUSED_UPGRADE:
234
+ err_pos = cparser.llhttp_get_error_pos(self._cparser)
235
+
236
+ # Immediately free the parser from "error" state, simulating
237
+ # http-parser behavior here because 1) we never had the API to
238
+ # allow users manually "resume after upgrade", and 2) the use
239
+ # case for resuming parsing is very rare.
240
+ cparser.llhttp_resume_after_upgrade(self._cparser)
241
+
242
+ # The err_pos here is specific for the input buf. So if we ever
243
+ # switch to the llhttp behavior (re-raise HttpParserUpgrade for
244
+ # successive calls to feed_data() until resume_after_upgrade is
245
+ # called), we have to store the result and keep our own state.
246
+ raise HttpParserUpgrade(err_pos - <char*>buf.buf)
247
+ finally:
248
+ if owning_buf:
249
+ PyBuffer_Release(buf)
250
+
251
+ if err != cparser.HPE_OK:
252
+ ex = parser_error_from_errno(
253
+ self._cparser,
254
+ <cparser.llhttp_errno_t> self._cparser.error)
255
+ if isinstance(ex, HttpParserCallbackError):
256
+ if self._last_error is not None:
257
+ ex.__context__ = self._last_error
258
+ self._last_error = None
259
+ raise ex
260
+
261
+
262
+ cdef class HttpRequestParser(HttpParser):
263
+
264
+ def __init__(self, protocol):
265
+ self._init(protocol, cparser.HTTP_REQUEST)
266
+
267
+ self._proto_on_url = getattr(protocol, 'on_url', None)
268
+ if self._proto_on_url is not None:
269
+ self._csettings.on_url = cb_on_url
270
+
271
+ def get_method(self):
272
+ cdef cparser.llhttp_t* parser = self._cparser
273
+ return cparser.llhttp_method_name(<cparser.llhttp_method_t> parser.method)
274
+
275
+
276
+ cdef class HttpResponseParser(HttpParser):
277
+
278
+ def __init__(self, protocol):
279
+ self._init(protocol, cparser.HTTP_RESPONSE)
280
+
281
+ self._proto_on_status = getattr(protocol, 'on_status', None)
282
+ if self._proto_on_status is not None:
283
+ self._csettings.on_status = cb_on_status
284
+
285
+ def get_status_code(self):
286
+ cdef cparser.llhttp_t* parser = self._cparser
287
+ return parser.status_code
288
+
289
+
290
+ cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1:
291
+ cdef HttpParser pyparser = <HttpParser>parser.data
292
+ try:
293
+ pyparser._proto_on_message_begin()
294
+ except BaseException as ex:
295
+ pyparser._last_error = ex
296
+ return -1
297
+ else:
298
+ return 0
299
+
300
+
301
+ cdef int cb_on_url(cparser.llhttp_t* parser,
302
+ const char *at, size_t length) except -1:
303
+ cdef HttpParser pyparser = <HttpParser>parser.data
304
+ try:
305
+ pyparser._proto_on_url(at[:length])
306
+ except BaseException as ex:
307
+ cparser.llhttp_set_error_reason(parser, "`on_url` callback error")
308
+ pyparser._last_error = ex
309
+ return cparser.HPE_USER
310
+ else:
311
+ return 0
312
+
313
+
314
+ cdef int cb_on_status(cparser.llhttp_t* parser,
315
+ const char *at, size_t length) except -1:
316
+ cdef HttpParser pyparser = <HttpParser>parser.data
317
+ try:
318
+ pyparser._proto_on_status(at[:length])
319
+ except BaseException as ex:
320
+ cparser.llhttp_set_error_reason(parser, "`on_status` callback error")
321
+ pyparser._last_error = ex
322
+ return cparser.HPE_USER
323
+ else:
324
+ return 0
325
+
326
+
327
+ cdef int cb_on_header_field(cparser.llhttp_t* parser,
328
+ const char *at, size_t length) except -1:
329
+ cdef HttpParser pyparser = <HttpParser>parser.data
330
+ try:
331
+ pyparser._on_header_field(at[:length])
332
+ except BaseException as ex:
333
+ cparser.llhttp_set_error_reason(parser, "`on_header_field` callback error")
334
+ pyparser._last_error = ex
335
+ return cparser.HPE_USER
336
+ else:
337
+ return 0
338
+
339
+
340
+ cdef int cb_on_header_value(cparser.llhttp_t* parser,
341
+ const char *at, size_t length) except -1:
342
+ cdef HttpParser pyparser = <HttpParser>parser.data
343
+ try:
344
+ pyparser._on_header_value(at[:length])
345
+ except BaseException as ex:
346
+ cparser.llhttp_set_error_reason(parser, "`on_header_value` callback error")
347
+ pyparser._last_error = ex
348
+ return cparser.HPE_USER
349
+ else:
350
+ return 0
351
+
352
+
353
+ cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
354
+ cdef HttpParser pyparser = <HttpParser>parser.data
355
+ try:
356
+ pyparser._on_headers_complete()
357
+ except BaseException as ex:
358
+ pyparser._last_error = ex
359
+ return -1
360
+ else:
361
+ if pyparser._cparser.upgrade:
362
+ return 1
363
+ else:
364
+ return 0
365
+
366
+
367
+ cdef int cb_on_body(cparser.llhttp_t* parser,
368
+ const char *at, size_t length) except -1:
369
+ cdef HttpParser pyparser = <HttpParser>parser.data
370
+ try:
371
+ pyparser._proto_on_body(at[:length])
372
+ except BaseException as ex:
373
+ cparser.llhttp_set_error_reason(parser, "`on_body` callback error")
374
+ pyparser._last_error = ex
375
+ return cparser.HPE_USER
376
+ else:
377
+ return 0
378
+
379
+
380
+ cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:
381
+ cdef HttpParser pyparser = <HttpParser>parser.data
382
+ try:
383
+ pyparser._proto_on_message_complete()
384
+ except BaseException as ex:
385
+ pyparser._last_error = ex
386
+ return -1
387
+ else:
388
+ return 0
389
+
390
+
391
+ cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1:
392
+ cdef HttpParser pyparser = <HttpParser>parser.data
393
+ try:
394
+ pyparser._on_chunk_header()
395
+ except BaseException as ex:
396
+ pyparser._last_error = ex
397
+ return -1
398
+ else:
399
+ return 0
400
+
401
+
402
+ cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1:
403
+ cdef HttpParser pyparser = <HttpParser>parser.data
404
+ try:
405
+ pyparser._on_chunk_complete()
406
+ except BaseException as ex:
407
+ pyparser._last_error = ex
408
+ return -1
409
+ else:
410
+ return 0
411
+
412
+
413
+ cdef parser_error_from_errno(cparser.llhttp_t* parser, cparser.llhttp_errno_t errno):
414
+ cdef bytes reason = cparser.llhttp_get_error_reason(parser)
415
+
416
+ if errno in (cparser.HPE_CB_MESSAGE_BEGIN,
417
+ cparser.HPE_CB_HEADERS_COMPLETE,
418
+ cparser.HPE_CB_MESSAGE_COMPLETE,
419
+ cparser.HPE_CB_CHUNK_HEADER,
420
+ cparser.HPE_CB_CHUNK_COMPLETE,
421
+ cparser.HPE_USER):
422
+ cls = HttpParserCallbackError
423
+
424
+ elif errno == cparser.HPE_INVALID_STATUS:
425
+ cls = HttpParserInvalidStatusError
426
+
427
+ elif errno == cparser.HPE_INVALID_METHOD:
428
+ cls = HttpParserInvalidMethodError
429
+
430
+ elif errno == cparser.HPE_INVALID_URL:
431
+ cls = HttpParserInvalidURLError
432
+
433
+ else:
434
+ cls = HttpParserError
435
+
436
+ return cls(reason.decode('latin-1'))
.venv/lib/python3.11/site-packages/httptools/parser/python.pxd ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ cimport cpython
2
+
3
+
4
+ cdef extern from "Python.h":
5
+ cpython.Py_buffer* PyMemoryView_GET_BUFFER(object)
6
+ bint PyMemoryView_Check(object)
.venv/lib/python3.11/site-packages/httptools/parser/url_cparser.pxd ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from libc.stdint cimport uint16_t
2
+
3
+
4
+ cdef extern from "http_parser.h":
5
+ # URL Parser
6
+
7
+ enum http_parser_url_fields:
8
+ UF_SCHEMA = 0,
9
+ UF_HOST = 1,
10
+ UF_PORT = 2,
11
+ UF_PATH = 3,
12
+ UF_QUERY = 4,
13
+ UF_FRAGMENT = 5,
14
+ UF_USERINFO = 6,
15
+ UF_MAX = 7
16
+
17
+ struct http_parser_url_field_data:
18
+ uint16_t off
19
+ uint16_t len
20
+
21
+ struct http_parser_url:
22
+ uint16_t field_set
23
+ uint16_t port
24
+ http_parser_url_field_data[<int>UF_MAX] field_data
25
+
26
+ void http_parser_url_init(http_parser_url *u)
27
+
28
+ int http_parser_parse_url(const char *buf,
29
+ size_t buflen,
30
+ int is_connect,
31
+ http_parser_url *u)
.venv/lib/python3.11/site-packages/httptools/parser/url_parser.pyx ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #cython: language_level=3
2
+
3
+ from __future__ import print_function
4
+ from cpython.mem cimport PyMem_Malloc, PyMem_Free
5
+ from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
6
+ Py_buffer
7
+
8
+ from .errors import HttpParserInvalidURLError
9
+
10
+ cimport cython
11
+ from . cimport url_cparser as uparser
12
+
13
+ __all__ = ('parse_url',)
14
+
15
+ @cython.freelist(250)
16
+ cdef class URL:
17
+ cdef readonly bytes schema
18
+ cdef readonly bytes host
19
+ cdef readonly object port
20
+ cdef readonly bytes path
21
+ cdef readonly bytes query
22
+ cdef readonly bytes fragment
23
+ cdef readonly bytes userinfo
24
+
25
+ def __cinit__(self, bytes schema, bytes host, object port, bytes path,
26
+ bytes query, bytes fragment, bytes userinfo):
27
+
28
+ self.schema = schema
29
+ self.host = host
30
+ self.port = port
31
+ self.path = path
32
+ self.query = query
33
+ self.fragment = fragment
34
+ self.userinfo = userinfo
35
+
36
+ def __repr__(self):
37
+ return ('<URL schema: {!r}, host: {!r}, port: {!r}, path: {!r}, '
38
+ 'query: {!r}, fragment: {!r}, userinfo: {!r}>'
39
+ .format(self.schema, self.host, self.port, self.path,
40
+ self.query, self.fragment, self.userinfo))
41
+
42
+
43
+ def parse_url(url):
44
+ cdef:
45
+ Py_buffer py_buf
46
+ char* buf_data
47
+ uparser.http_parser_url* parsed
48
+ int res
49
+ bytes schema = None
50
+ bytes host = None
51
+ object port = None
52
+ bytes path = None
53
+ bytes query = None
54
+ bytes fragment = None
55
+ bytes userinfo = None
56
+ object result = None
57
+ int off
58
+ int ln
59
+
60
+ parsed = <uparser.http_parser_url*> \
61
+ PyMem_Malloc(sizeof(uparser.http_parser_url))
62
+ uparser.http_parser_url_init(parsed)
63
+
64
+ PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
65
+ try:
66
+ buf_data = <char*>py_buf.buf
67
+ res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
68
+
69
+ if res == 0:
70
+ if parsed.field_set & (1 << uparser.UF_SCHEMA):
71
+ off = parsed.field_data[<int>uparser.UF_SCHEMA].off
72
+ ln = parsed.field_data[<int>uparser.UF_SCHEMA].len
73
+ schema = buf_data[off:off+ln]
74
+
75
+ if parsed.field_set & (1 << uparser.UF_HOST):
76
+ off = parsed.field_data[<int>uparser.UF_HOST].off
77
+ ln = parsed.field_data[<int>uparser.UF_HOST].len
78
+ host = buf_data[off:off+ln]
79
+
80
+ if parsed.field_set & (1 << uparser.UF_PORT):
81
+ port = parsed.port
82
+
83
+ if parsed.field_set & (1 << uparser.UF_PATH):
84
+ off = parsed.field_data[<int>uparser.UF_PATH].off
85
+ ln = parsed.field_data[<int>uparser.UF_PATH].len
86
+ path = buf_data[off:off+ln]
87
+
88
+ if parsed.field_set & (1 << uparser.UF_QUERY):
89
+ off = parsed.field_data[<int>uparser.UF_QUERY].off
90
+ ln = parsed.field_data[<int>uparser.UF_QUERY].len
91
+ query = buf_data[off:off+ln]
92
+
93
+ if parsed.field_set & (1 << uparser.UF_FRAGMENT):
94
+ off = parsed.field_data[<int>uparser.UF_FRAGMENT].off
95
+ ln = parsed.field_data[<int>uparser.UF_FRAGMENT].len
96
+ fragment = buf_data[off:off+ln]
97
+
98
+ if parsed.field_set & (1 << uparser.UF_USERINFO):
99
+ off = parsed.field_data[<int>uparser.UF_USERINFO].off
100
+ ln = parsed.field_data[<int>uparser.UF_USERINFO].len
101
+ userinfo = buf_data[off:off+ln]
102
+
103
+ return URL(schema, host, port, path, query, fragment, userinfo)
104
+ else:
105
+ raise HttpParserInvalidURLError("invalid url {!r}".format(url))
106
+ finally:
107
+ PyBuffer_Release(&py_buf)
108
+ PyMem_Free(parsed)
.venv/lib/python3.11/site-packages/networkx/utils/tests/__init__.py ADDED
File without changes
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (193 Bytes). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test__init.cpython-311.pyc ADDED
Binary file (1.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_backends.cpython-311.pyc ADDED
Binary file (10.4 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_config.cpython-311.pyc ADDED
Binary file (19.7 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_decorators.cpython-311.pyc ADDED
Binary file (36.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_heaps.cpython-311.pyc ADDED
Binary file (6.07 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_mapped_queue.cpython-311.pyc ADDED
Binary file (17.1 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_misc.cpython-311.pyc ADDED
Binary file (19.3 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_random_sequence.cpython-311.pyc ADDED
Binary file (2.26 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_rcm.cpython-311.pyc ADDED
Binary file (2.24 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/__pycache__/test_unionfind.cpython-311.pyc ADDED
Binary file (3.01 kB). View file
 
.venv/lib/python3.11/site-packages/networkx/utils/tests/test_config.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections
2
+ import pickle
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.utils.configs import BackendPriorities, Config
8
+
9
+
10
+ # Define this at module level so we can test pickling
11
+ class ExampleConfig(Config):
12
+ """Example configuration."""
13
+
14
+ x: int
15
+ y: str
16
+
17
+ def _on_setattr(self, key, value):
18
+ if key == "x" and value <= 0:
19
+ raise ValueError("x must be positive")
20
+ if key == "y" and not isinstance(value, str):
21
+ raise TypeError("y must be a str")
22
+ return value
23
+
24
+
25
+ class EmptyConfig(Config):
26
+ pass
27
+
28
+
29
+ @pytest.mark.parametrize("cfg", [EmptyConfig(), Config()])
30
+ def test_config_empty(cfg):
31
+ assert dir(cfg) == []
32
+ with pytest.raises(AttributeError):
33
+ cfg.x = 1
34
+ with pytest.raises(KeyError):
35
+ cfg["x"] = 1
36
+ with pytest.raises(AttributeError):
37
+ cfg.x
38
+ with pytest.raises(KeyError):
39
+ cfg["x"]
40
+ assert len(cfg) == 0
41
+ assert "x" not in cfg
42
+ assert cfg == cfg
43
+ assert cfg.get("x", 2) == 2
44
+ assert set(cfg.keys()) == set()
45
+ assert set(cfg.values()) == set()
46
+ assert set(cfg.items()) == set()
47
+ cfg2 = pickle.loads(pickle.dumps(cfg))
48
+ assert cfg == cfg2
49
+ assert isinstance(cfg, collections.abc.Collection)
50
+ assert isinstance(cfg, collections.abc.Mapping)
51
+
52
+
53
+ def test_config_subclass():
54
+ with pytest.raises(TypeError, match="missing 2 required keyword-only"):
55
+ ExampleConfig()
56
+ with pytest.raises(ValueError, match="x must be positive"):
57
+ ExampleConfig(x=0, y="foo")
58
+ with pytest.raises(TypeError, match="unexpected keyword"):
59
+ ExampleConfig(x=1, y="foo", z="bad config")
60
+ with pytest.raises(TypeError, match="unexpected keyword"):
61
+ EmptyConfig(z="bad config")
62
+ cfg = ExampleConfig(x=1, y="foo")
63
+ assert cfg.x == 1
64
+ assert cfg["x"] == 1
65
+ assert cfg["y"] == "foo"
66
+ assert cfg.y == "foo"
67
+ assert "x" in cfg
68
+ assert "y" in cfg
69
+ assert "z" not in cfg
70
+ assert len(cfg) == 2
71
+ assert set(iter(cfg)) == {"x", "y"}
72
+ assert set(cfg.keys()) == {"x", "y"}
73
+ assert set(cfg.values()) == {1, "foo"}
74
+ assert set(cfg.items()) == {("x", 1), ("y", "foo")}
75
+ assert dir(cfg) == ["x", "y"]
76
+ cfg.x = 2
77
+ cfg["y"] = "bar"
78
+ assert cfg["x"] == 2
79
+ assert cfg.y == "bar"
80
+ with pytest.raises(TypeError, match="can't be deleted"):
81
+ del cfg.x
82
+ with pytest.raises(TypeError, match="can't be deleted"):
83
+ del cfg["y"]
84
+ assert cfg.x == 2
85
+ assert cfg == cfg
86
+ assert cfg == ExampleConfig(x=2, y="bar")
87
+ assert cfg != ExampleConfig(x=3, y="baz")
88
+ assert cfg != Config(x=2, y="bar")
89
+ with pytest.raises(TypeError, match="y must be a str"):
90
+ cfg["y"] = 5
91
+ with pytest.raises(ValueError, match="x must be positive"):
92
+ cfg.x = -5
93
+ assert cfg.get("x", 10) == 2
94
+ with pytest.raises(AttributeError):
95
+ cfg.z = 5
96
+ with pytest.raises(KeyError):
97
+ cfg["z"] = 5
98
+ with pytest.raises(AttributeError):
99
+ cfg.z
100
+ with pytest.raises(KeyError):
101
+ cfg["z"]
102
+ cfg2 = pickle.loads(pickle.dumps(cfg))
103
+ assert cfg == cfg2
104
+ assert cfg.__doc__ == "Example configuration."
105
+ assert cfg2.__doc__ == "Example configuration."
106
+
107
+
108
+ def test_config_defaults():
109
+ class DefaultConfig(Config):
110
+ x: int = 0
111
+ y: int
112
+
113
+ cfg = DefaultConfig(y=1)
114
+ assert cfg.x == 0
115
+ cfg = DefaultConfig(x=2, y=1)
116
+ assert cfg.x == 2
117
+
118
+
119
+ def test_nxconfig():
120
+ assert isinstance(nx.config.backend_priority, BackendPriorities)
121
+ assert isinstance(nx.config.backend_priority.algos, list)
122
+ assert isinstance(nx.config.backends, Config)
123
+ with pytest.raises(TypeError, match="must be a list of backend names"):
124
+ nx.config.backend_priority.algos = "nx_loopback"
125
+ with pytest.raises(ValueError, match="Unknown backend when setting"):
126
+ nx.config.backend_priority.algos = ["this_almost_certainly_is_not_a_backend"]
127
+ with pytest.raises(TypeError, match="must be a Config of backend configs"):
128
+ nx.config.backends = {}
129
+ with pytest.raises(TypeError, match="must be a Config of backend configs"):
130
+ nx.config.backends = Config(plausible_backend_name={})
131
+ with pytest.raises(ValueError, match="Unknown backend when setting"):
132
+ nx.config.backends = Config(this_almost_certainly_is_not_a_backend=Config())
133
+ with pytest.raises(TypeError, match="must be True or False"):
134
+ nx.config.cache_converted_graphs = "bad value"
135
+ with pytest.raises(TypeError, match="must be a set of "):
136
+ nx.config.warnings_to_ignore = 7
137
+ with pytest.raises(ValueError, match="Unknown warning "):
138
+ nx.config.warnings_to_ignore = {"bad value"}
139
+
140
+
141
+ def test_not_strict():
142
+ class FlexibleConfig(Config, strict=False):
143
+ x: int
144
+
145
+ cfg = FlexibleConfig(x=1)
146
+ assert "_strict" not in cfg
147
+ assert len(cfg) == 1
148
+ assert list(cfg) == ["x"]
149
+ assert list(cfg.keys()) == ["x"]
150
+ assert list(cfg.values()) == [1]
151
+ assert list(cfg.items()) == [("x", 1)]
152
+ assert cfg.x == 1
153
+ assert cfg["x"] == 1
154
+ assert "x" in cfg
155
+ assert hasattr(cfg, "x")
156
+ assert "FlexibleConfig(x=1)" in repr(cfg)
157
+ assert cfg == FlexibleConfig(x=1)
158
+ del cfg.x
159
+ assert "FlexibleConfig()" in repr(cfg)
160
+ assert len(cfg) == 0
161
+ assert not hasattr(cfg, "x")
162
+ assert "x" not in cfg
163
+ assert not hasattr(cfg, "y")
164
+ assert "y" not in cfg
165
+ cfg.y = 2
166
+ assert len(cfg) == 1
167
+ assert list(cfg) == ["y"]
168
+ assert list(cfg.keys()) == ["y"]
169
+ assert list(cfg.values()) == [2]
170
+ assert list(cfg.items()) == [("y", 2)]
171
+ assert cfg.y == 2
172
+ assert cfg["y"] == 2
173
+ assert hasattr(cfg, "y")
174
+ assert "y" in cfg
175
+ del cfg["y"]
176
+ assert len(cfg) == 0
177
+ assert list(cfg) == []
178
+ with pytest.raises(AttributeError, match="y"):
179
+ del cfg.y
180
+ with pytest.raises(KeyError, match="y"):
181
+ del cfg["y"]
182
+ with pytest.raises(TypeError, match="missing 1 required keyword-only"):
183
+ FlexibleConfig()
184
+ # Be strict when first creating the config object
185
+ with pytest.raises(TypeError, match="unexpected keyword argument 'y'"):
186
+ FlexibleConfig(x=1, y=2)
187
+
188
+ class FlexibleConfigWithDefault(Config, strict=False):
189
+ x: int = 0
190
+
191
+ assert FlexibleConfigWithDefault().x == 0
192
+ assert FlexibleConfigWithDefault(x=1)["x"] == 1
193
+
194
+
195
+ def test_context():
196
+ cfg = Config(x=1)
197
+ with cfg(x=2) as c:
198
+ assert c.x == 2
199
+ c.x = 3
200
+ assert cfg.x == 3
201
+ assert cfg.x == 1
202
+
203
+ with cfg(x=2) as c:
204
+ assert c == cfg
205
+ assert cfg.x == 2
206
+ with cfg(x=3) as c2:
207
+ assert c2 == cfg
208
+ assert cfg.x == 3
209
+ with pytest.raises(RuntimeError, match="context manager without"):
210
+ with cfg as c3: # Forgot to call `cfg(...)`
211
+ pass
212
+ assert cfg.x == 3
213
+ assert cfg.x == 2
214
+ assert cfg.x == 1
215
+
216
+ c = cfg(x=4) # Not yet as context (not recommended, but possible)
217
+ assert c == cfg
218
+ assert cfg.x == 4
219
+ # Cheat by looking at internal data; context stack should only grow with __enter__
220
+ assert cfg._prev is not None
221
+ assert cfg._context_stack == []
222
+ with c:
223
+ assert c == cfg
224
+ assert cfg.x == 4
225
+ assert cfg.x == 1
226
+ # Cheat again; there was no preceding `cfg(...)` call this time
227
+ assert cfg._prev is None
228
+ with pytest.raises(RuntimeError, match="context manager without"):
229
+ with cfg:
230
+ pass
231
+ assert cfg.x == 1
.venv/lib/python3.11/site-packages/networkx/utils/tests/test_mapped_queue.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from networkx.utils.mapped_queue import MappedQueue, _HeapElement
4
+
5
+
6
+ def test_HeapElement_gtlt():
7
+ bar = _HeapElement(1.1, "a")
8
+ foo = _HeapElement(1, "b")
9
+ assert foo < bar
10
+ assert bar > foo
11
+ assert foo < 1.1
12
+ assert 1 < bar
13
+
14
+
15
+ def test_HeapElement_gtlt_tied_priority():
16
+ bar = _HeapElement(1, "a")
17
+ foo = _HeapElement(1, "b")
18
+ assert foo > bar
19
+ assert bar < foo
20
+
21
+
22
+ def test_HeapElement_eq():
23
+ bar = _HeapElement(1.1, "a")
24
+ foo = _HeapElement(1, "a")
25
+ assert foo == bar
26
+ assert bar == foo
27
+ assert foo == "a"
28
+
29
+
30
+ def test_HeapElement_iter():
31
+ foo = _HeapElement(1, "a")
32
+ bar = _HeapElement(1.1, (3, 2, 1))
33
+ assert list(foo) == [1, "a"]
34
+ assert list(bar) == [1.1, 3, 2, 1]
35
+
36
+
37
+ def test_HeapElement_getitem():
38
+ foo = _HeapElement(1, "a")
39
+ bar = _HeapElement(1.1, (3, 2, 1))
40
+ assert foo[1] == "a"
41
+ assert foo[0] == 1
42
+ assert bar[0] == 1.1
43
+ assert bar[2] == 2
44
+ assert bar[3] == 1
45
+ pytest.raises(IndexError, bar.__getitem__, 4)
46
+ pytest.raises(IndexError, foo.__getitem__, 2)
47
+
48
+
49
+ class TestMappedQueue:
50
+ def setup_method(self):
51
+ pass
52
+
53
+ def _check_map(self, q):
54
+ assert q.position == {elt: pos for pos, elt in enumerate(q.heap)}
55
+
56
+ def _make_mapped_queue(self, h):
57
+ q = MappedQueue()
58
+ q.heap = h
59
+ q.position = {elt: pos for pos, elt in enumerate(h)}
60
+ return q
61
+
62
+ def test_heapify(self):
63
+ h = [5, 4, 3, 2, 1, 0]
64
+ q = self._make_mapped_queue(h)
65
+ q._heapify()
66
+ self._check_map(q)
67
+
68
+ def test_init(self):
69
+ h = [5, 4, 3, 2, 1, 0]
70
+ q = MappedQueue(h)
71
+ self._check_map(q)
72
+
73
+ def test_incomparable(self):
74
+ h = [5, 4, "a", 2, 1, 0]
75
+ pytest.raises(TypeError, MappedQueue, h)
76
+
77
+ def test_len(self):
78
+ h = [5, 4, 3, 2, 1, 0]
79
+ q = MappedQueue(h)
80
+ self._check_map(q)
81
+ assert len(q) == 6
82
+
83
+ def test_siftup_leaf(self):
84
+ h = [2]
85
+ h_sifted = [2]
86
+ q = self._make_mapped_queue(h)
87
+ q._siftup(0)
88
+ assert q.heap == h_sifted
89
+ self._check_map(q)
90
+
91
+ def test_siftup_one_child(self):
92
+ h = [2, 0]
93
+ h_sifted = [0, 2]
94
+ q = self._make_mapped_queue(h)
95
+ q._siftup(0)
96
+ assert q.heap == h_sifted
97
+ self._check_map(q)
98
+
99
+ def test_siftup_left_child(self):
100
+ h = [2, 0, 1]
101
+ h_sifted = [0, 2, 1]
102
+ q = self._make_mapped_queue(h)
103
+ q._siftup(0)
104
+ assert q.heap == h_sifted
105
+ self._check_map(q)
106
+
107
+ def test_siftup_right_child(self):
108
+ h = [2, 1, 0]
109
+ h_sifted = [0, 1, 2]
110
+ q = self._make_mapped_queue(h)
111
+ q._siftup(0)
112
+ assert q.heap == h_sifted
113
+ self._check_map(q)
114
+
115
+ def test_siftup_multiple(self):
116
+ h = [0, 1, 2, 4, 3, 5, 6]
117
+ h_sifted = [0, 1, 2, 4, 3, 5, 6]
118
+ q = self._make_mapped_queue(h)
119
+ q._siftup(0)
120
+ assert q.heap == h_sifted
121
+ self._check_map(q)
122
+
123
+ def test_siftdown_leaf(self):
124
+ h = [2]
125
+ h_sifted = [2]
126
+ q = self._make_mapped_queue(h)
127
+ q._siftdown(0, 0)
128
+ assert q.heap == h_sifted
129
+ self._check_map(q)
130
+
131
+ def test_siftdown_single(self):
132
+ h = [1, 0]
133
+ h_sifted = [0, 1]
134
+ q = self._make_mapped_queue(h)
135
+ q._siftdown(0, len(h) - 1)
136
+ assert q.heap == h_sifted
137
+ self._check_map(q)
138
+
139
+ def test_siftdown_multiple(self):
140
+ h = [1, 2, 3, 4, 5, 6, 7, 0]
141
+ h_sifted = [0, 1, 3, 2, 5, 6, 7, 4]
142
+ q = self._make_mapped_queue(h)
143
+ q._siftdown(0, len(h) - 1)
144
+ assert q.heap == h_sifted
145
+ self._check_map(q)
146
+
147
+ def test_push(self):
148
+ to_push = [6, 1, 4, 3, 2, 5, 0]
149
+ h_sifted = [0, 2, 1, 6, 3, 5, 4]
150
+ q = MappedQueue()
151
+ for elt in to_push:
152
+ q.push(elt)
153
+ assert q.heap == h_sifted
154
+ self._check_map(q)
155
+
156
+ def test_push_duplicate(self):
157
+ to_push = [2, 1, 0]
158
+ h_sifted = [0, 2, 1]
159
+ q = MappedQueue()
160
+ for elt in to_push:
161
+ inserted = q.push(elt)
162
+ assert inserted
163
+ assert q.heap == h_sifted
164
+ self._check_map(q)
165
+ inserted = q.push(1)
166
+ assert not inserted
167
+
168
+ def test_pop(self):
169
+ h = [3, 4, 6, 0, 1, 2, 5]
170
+ h_sorted = sorted(h)
171
+ q = self._make_mapped_queue(h)
172
+ q._heapify()
173
+ popped = [q.pop() for _ in range(len(h))]
174
+ assert popped == h_sorted
175
+ self._check_map(q)
176
+
177
+ def test_remove_leaf(self):
178
+ h = [0, 2, 1, 6, 3, 5, 4]
179
+ h_removed = [0, 2, 1, 6, 4, 5]
180
+ q = self._make_mapped_queue(h)
181
+ removed = q.remove(3)
182
+ assert q.heap == h_removed
183
+
184
+ def test_remove_root(self):
185
+ h = [0, 2, 1, 6, 3, 5, 4]
186
+ h_removed = [1, 2, 4, 6, 3, 5]
187
+ q = self._make_mapped_queue(h)
188
+ removed = q.remove(0)
189
+ assert q.heap == h_removed
190
+
191
+ def test_update_leaf(self):
192
+ h = [0, 20, 10, 60, 30, 50, 40]
193
+ h_updated = [0, 15, 10, 60, 20, 50, 40]
194
+ q = self._make_mapped_queue(h)
195
+ removed = q.update(30, 15)
196
+ assert q.heap == h_updated
197
+
198
+ def test_update_root(self):
199
+ h = [0, 20, 10, 60, 30, 50, 40]
200
+ h_updated = [10, 20, 35, 60, 30, 50, 40]
201
+ q = self._make_mapped_queue(h)
202
+ removed = q.update(0, 35)
203
+ assert q.heap == h_updated
204
+
205
+
206
+ class TestMappedDict(TestMappedQueue):
207
+ def _make_mapped_queue(self, h):
208
+ priority_dict = {elt: elt for elt in h}
209
+ return MappedQueue(priority_dict)
210
+
211
+ def test_init(self):
212
+ d = {5: 0, 4: 1, "a": 2, 2: 3, 1: 4}
213
+ q = MappedQueue(d)
214
+ assert q.position == d
215
+
216
+ def test_ties(self):
217
+ d = {5: 0, 4: 1, 3: 2, 2: 3, 1: 4}
218
+ q = MappedQueue(d)
219
+ assert q.position == {elt: pos for pos, elt in enumerate(q.heap)}
220
+
221
+ def test_pop(self):
222
+ d = {5: 0, 4: 1, 3: 2, 2: 3, 1: 4}
223
+ q = MappedQueue(d)
224
+ assert q.pop() == _HeapElement(0, 5)
225
+ assert q.position == {elt: pos for pos, elt in enumerate(q.heap)}
226
+
227
+ def test_empty_pop(self):
228
+ q = MappedQueue()
229
+ pytest.raises(IndexError, q.pop)
230
+
231
+ def test_incomparable_ties(self):
232
+ d = {5: 0, 4: 0, "a": 0, 2: 0, 1: 0}
233
+ pytest.raises(TypeError, MappedQueue, d)
234
+
235
+ def test_push(self):
236
+ to_push = [6, 1, 4, 3, 2, 5, 0]
237
+ h_sifted = [0, 2, 1, 6, 3, 5, 4]
238
+ q = MappedQueue()
239
+ for elt in to_push:
240
+ q.push(elt, priority=elt)
241
+ assert q.heap == h_sifted
242
+ self._check_map(q)
243
+
244
+ def test_push_duplicate(self):
245
+ to_push = [2, 1, 0]
246
+ h_sifted = [0, 2, 1]
247
+ q = MappedQueue()
248
+ for elt in to_push:
249
+ inserted = q.push(elt, priority=elt)
250
+ assert inserted
251
+ assert q.heap == h_sifted
252
+ self._check_map(q)
253
+ inserted = q.push(1, priority=1)
254
+ assert not inserted
255
+
256
+ def test_update_leaf(self):
257
+ h = [0, 20, 10, 60, 30, 50, 40]
258
+ h_updated = [0, 15, 10, 60, 20, 50, 40]
259
+ q = self._make_mapped_queue(h)
260
+ removed = q.update(30, 15, priority=15)
261
+ assert q.heap == h_updated
262
+
263
+ def test_update_root(self):
264
+ h = [0, 20, 10, 60, 30, 50, 40]
265
+ h_updated = [10, 20, 35, 60, 30, 50, 40]
266
+ q = self._make_mapped_queue(h)
267
+ removed = q.update(0, 35, priority=35)
268
+ assert q.heap == h_updated
.venv/lib/python3.11/site-packages/networkx/utils/tests/test_misc.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from copy import copy
3
+
4
+ import pytest
5
+
6
+ import networkx as nx
7
+ from networkx.utils import (
8
+ PythonRandomInterface,
9
+ PythonRandomViaNumpyBits,
10
+ arbitrary_element,
11
+ create_py_random_state,
12
+ create_random_state,
13
+ dict_to_numpy_array,
14
+ discrete_sequence,
15
+ flatten,
16
+ groups,
17
+ make_list_of_ints,
18
+ pairwise,
19
+ powerlaw_sequence,
20
+ )
21
+ from networkx.utils.misc import _dict_to_numpy_array1, _dict_to_numpy_array2
22
+
23
+ nested_depth = (
24
+ 1,
25
+ 2,
26
+ (3, 4, ((5, 6, (7,), (8, (9, 10), 11), (12, 13, (14, 15)), 16), 17), 18, 19),
27
+ 20,
28
+ )
29
+
30
+ nested_set = {
31
+ (1, 2, 3, 4),
32
+ (5, 6, 7, 8, 9),
33
+ (10, 11, (12, 13, 14), (15, 16, 17, 18)),
34
+ 19,
35
+ 20,
36
+ }
37
+
38
+ nested_mixed = [
39
+ 1,
40
+ (2, 3, {4, (5, 6), 7}, [8, 9]),
41
+ {10: "foo", 11: "bar", (12, 13): "baz"},
42
+ {(14, 15): "qwe", 16: "asd"},
43
+ (17, (18, "19"), 20),
44
+ ]
45
+
46
+
47
+ @pytest.mark.parametrize("result", [None, [], ["existing"], ["existing1", "existing2"]])
48
+ @pytest.mark.parametrize("nested", [nested_depth, nested_mixed, nested_set])
49
+ def test_flatten(nested, result):
50
+ if result is None:
51
+ val = flatten(nested, result)
52
+ assert len(val) == 20
53
+ else:
54
+ _result = copy(result) # because pytest passes parameters as is
55
+ nexisting = len(_result)
56
+ val = flatten(nested, _result)
57
+ assert len(val) == len(_result) == 20 + nexisting
58
+
59
+ assert issubclass(type(val), tuple)
60
+
61
+
62
+ def test_make_list_of_ints():
63
+ mylist = [1, 2, 3.0, 42, -2]
64
+ assert make_list_of_ints(mylist) is mylist
65
+ assert make_list_of_ints(mylist) == mylist
66
+ assert type(make_list_of_ints(mylist)[2]) is int
67
+ pytest.raises(nx.NetworkXError, make_list_of_ints, [1, 2, 3, "kermit"])
68
+ pytest.raises(nx.NetworkXError, make_list_of_ints, [1, 2, 3.1])
69
+
70
+
71
+ def test_random_number_distribution():
72
+ # smoke test only
73
+ z = powerlaw_sequence(20, exponent=2.5)
74
+ z = discrete_sequence(20, distribution=[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3])
75
+
76
+
77
+ class TestNumpyArray:
78
+ @classmethod
79
+ def setup_class(cls):
80
+ global np
81
+ np = pytest.importorskip("numpy")
82
+
83
+ def test_numpy_to_list_of_ints(self):
84
+ a = np.array([1, 2, 3], dtype=np.int64)
85
+ b = np.array([1.0, 2, 3])
86
+ c = np.array([1.1, 2, 3])
87
+ assert type(make_list_of_ints(a)) == list
88
+ assert make_list_of_ints(b) == list(b)
89
+ B = make_list_of_ints(b)
90
+ assert type(B[0]) == int
91
+ pytest.raises(nx.NetworkXError, make_list_of_ints, c)
92
+
93
+ def test__dict_to_numpy_array1(self):
94
+ d = {"a": 1, "b": 2}
95
+ a = _dict_to_numpy_array1(d, mapping={"a": 0, "b": 1})
96
+ np.testing.assert_allclose(a, np.array([1, 2]))
97
+ a = _dict_to_numpy_array1(d, mapping={"b": 0, "a": 1})
98
+ np.testing.assert_allclose(a, np.array([2, 1]))
99
+
100
+ a = _dict_to_numpy_array1(d)
101
+ np.testing.assert_allclose(a.sum(), 3)
102
+
103
+ def test__dict_to_numpy_array2(self):
104
+ d = {"a": {"a": 1, "b": 2}, "b": {"a": 10, "b": 20}}
105
+
106
+ mapping = {"a": 1, "b": 0}
107
+ a = _dict_to_numpy_array2(d, mapping=mapping)
108
+ np.testing.assert_allclose(a, np.array([[20, 10], [2, 1]]))
109
+
110
+ a = _dict_to_numpy_array2(d)
111
+ np.testing.assert_allclose(a.sum(), 33)
112
+
113
+ def test_dict_to_numpy_array_a(self):
114
+ d = {"a": {"a": 1, "b": 2}, "b": {"a": 10, "b": 20}}
115
+
116
+ mapping = {"a": 0, "b": 1}
117
+ a = dict_to_numpy_array(d, mapping=mapping)
118
+ np.testing.assert_allclose(a, np.array([[1, 2], [10, 20]]))
119
+
120
+ mapping = {"a": 1, "b": 0}
121
+ a = dict_to_numpy_array(d, mapping=mapping)
122
+ np.testing.assert_allclose(a, np.array([[20, 10], [2, 1]]))
123
+
124
+ a = _dict_to_numpy_array2(d)
125
+ np.testing.assert_allclose(a.sum(), 33)
126
+
127
+ def test_dict_to_numpy_array_b(self):
128
+ d = {"a": 1, "b": 2}
129
+
130
+ mapping = {"a": 0, "b": 1}
131
+ a = dict_to_numpy_array(d, mapping=mapping)
132
+ np.testing.assert_allclose(a, np.array([1, 2]))
133
+
134
+ a = _dict_to_numpy_array1(d)
135
+ np.testing.assert_allclose(a.sum(), 3)
136
+
137
+
138
+ def test_pairwise():
139
+ nodes = range(4)
140
+ node_pairs = [(0, 1), (1, 2), (2, 3)]
141
+ node_pairs_cycle = node_pairs + [(3, 0)]
142
+ assert list(pairwise(nodes)) == node_pairs
143
+ assert list(pairwise(iter(nodes))) == node_pairs
144
+ assert list(pairwise(nodes, cyclic=True)) == node_pairs_cycle
145
+ empty_iter = iter(())
146
+ assert list(pairwise(empty_iter)) == []
147
+ empty_iter = iter(())
148
+ assert list(pairwise(empty_iter, cyclic=True)) == []
149
+
150
+
151
+ def test_groups():
152
+ many_to_one = dict(zip("abcde", [0, 0, 1, 1, 2]))
153
+ actual = groups(many_to_one)
154
+ expected = {0: {"a", "b"}, 1: {"c", "d"}, 2: {"e"}}
155
+ assert actual == expected
156
+ assert {} == groups({})
157
+
158
+
159
+ def test_create_random_state():
160
+ np = pytest.importorskip("numpy")
161
+ rs = np.random.RandomState
162
+
163
+ assert isinstance(create_random_state(1), rs)
164
+ assert isinstance(create_random_state(None), rs)
165
+ assert isinstance(create_random_state(np.random), rs)
166
+ assert isinstance(create_random_state(rs(1)), rs)
167
+ # Support for numpy.random.Generator
168
+ rng = np.random.default_rng()
169
+ assert isinstance(create_random_state(rng), np.random.Generator)
170
+ pytest.raises(ValueError, create_random_state, "a")
171
+
172
+ assert np.all(rs(1).rand(10) == create_random_state(1).rand(10))
173
+
174
+
175
+ def test_create_py_random_state():
176
+ pyrs = random.Random
177
+
178
+ assert isinstance(create_py_random_state(1), pyrs)
179
+ assert isinstance(create_py_random_state(None), pyrs)
180
+ assert isinstance(create_py_random_state(pyrs(1)), pyrs)
181
+ pytest.raises(ValueError, create_py_random_state, "a")
182
+
183
+ np = pytest.importorskip("numpy")
184
+
185
+ rs = np.random.RandomState
186
+ rng = np.random.default_rng(1000)
187
+ rng_explicit = np.random.Generator(np.random.SFC64())
188
+ old_nprs = PythonRandomInterface
189
+ nprs = PythonRandomViaNumpyBits
190
+ assert isinstance(create_py_random_state(np.random), nprs)
191
+ assert isinstance(create_py_random_state(rs(1)), old_nprs)
192
+ assert isinstance(create_py_random_state(rng), nprs)
193
+ assert isinstance(create_py_random_state(rng_explicit), nprs)
194
+ # test default rng input
195
+ assert isinstance(PythonRandomInterface(), old_nprs)
196
+ assert isinstance(PythonRandomViaNumpyBits(), nprs)
197
+
198
+ # VeryLargeIntegers Smoke test (they raise error for np.random)
199
+ int64max = 9223372036854775807 # from np.iinfo(np.int64).max
200
+ for r in (rng, rs(1)):
201
+ prs = create_py_random_state(r)
202
+ prs.randrange(3, int64max + 5)
203
+ prs.randint(3, int64max + 5)
204
+
205
+
206
+ def test_PythonRandomInterface_RandomState():
207
+ np = pytest.importorskip("numpy")
208
+
209
+ seed = 42
210
+ rs = np.random.RandomState
211
+ rng = PythonRandomInterface(rs(seed))
212
+ rs42 = rs(seed)
213
+
214
+ # make sure these functions are same as expected outcome
215
+ assert rng.randrange(3, 5) == rs42.randint(3, 5)
216
+ assert rng.choice([1, 2, 3]) == rs42.choice([1, 2, 3])
217
+ assert rng.gauss(0, 1) == rs42.normal(0, 1)
218
+ assert rng.expovariate(1.5) == rs42.exponential(1 / 1.5)
219
+ assert np.all(rng.shuffle([1, 2, 3]) == rs42.shuffle([1, 2, 3]))
220
+ assert np.all(
221
+ rng.sample([1, 2, 3], 2) == rs42.choice([1, 2, 3], (2,), replace=False)
222
+ )
223
+ assert np.all(
224
+ [rng.randint(3, 5) for _ in range(100)]
225
+ == [rs42.randint(3, 6) for _ in range(100)]
226
+ )
227
+ assert rng.random() == rs42.random_sample()
228
+
229
+
230
+ def test_PythonRandomInterface_Generator():
231
+ np = pytest.importorskip("numpy")
232
+
233
+ seed = 42
234
+ rng = np.random.default_rng(seed)
235
+ pri = PythonRandomInterface(np.random.default_rng(seed))
236
+
237
+ # make sure these functions are same as expected outcome
238
+ assert pri.randrange(3, 5) == rng.integers(3, 5)
239
+ assert pri.choice([1, 2, 3]) == rng.choice([1, 2, 3])
240
+ assert pri.gauss(0, 1) == rng.normal(0, 1)
241
+ assert pri.expovariate(1.5) == rng.exponential(1 / 1.5)
242
+ assert np.all(pri.shuffle([1, 2, 3]) == rng.shuffle([1, 2, 3]))
243
+ assert np.all(
244
+ pri.sample([1, 2, 3], 2) == rng.choice([1, 2, 3], (2,), replace=False)
245
+ )
246
+ assert np.all(
247
+ [pri.randint(3, 5) for _ in range(100)]
248
+ == [rng.integers(3, 6) for _ in range(100)]
249
+ )
250
+ assert pri.random() == rng.random()
251
+
252
+
253
+ @pytest.mark.parametrize(
254
+ ("iterable_type", "expected"), ((list, 1), (tuple, 1), (str, "["), (set, 1))
255
+ )
256
+ def test_arbitrary_element(iterable_type, expected):
257
+ iterable = iterable_type([1, 2, 3])
258
+ assert arbitrary_element(iterable) == expected
259
+
260
+
261
+ @pytest.mark.parametrize(
262
+ "iterator",
263
+ ((i for i in range(3)), iter([1, 2, 3])), # generator
264
+ )
265
+ def test_arbitrary_element_raises(iterator):
266
+ """Value error is raised when input is an iterator."""
267
+ with pytest.raises(ValueError, match="from an iterator"):
268
+ arbitrary_element(iterator)
.venv/lib/python3.11/site-packages/networkx/utils/tests/test_random_sequence.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from networkx.utils import (
4
+ powerlaw_sequence,
5
+ random_weighted_sample,
6
+ weighted_choice,
7
+ zipf_rv,
8
+ )
9
+
10
+
11
+ def test_degree_sequences():
12
+ seq = powerlaw_sequence(10, seed=1)
13
+ seq = powerlaw_sequence(10)
14
+ assert len(seq) == 10
15
+
16
+
17
+ def test_zipf_rv():
18
+ r = zipf_rv(2.3, xmin=2, seed=1)
19
+ r = zipf_rv(2.3, 2, 1)
20
+ r = zipf_rv(2.3)
21
+ assert type(r), int
22
+ pytest.raises(ValueError, zipf_rv, 0.5)
23
+ pytest.raises(ValueError, zipf_rv, 2, xmin=0)
24
+
25
+
26
+ def test_random_weighted_sample():
27
+ mapping = {"a": 10, "b": 20}
28
+ s = random_weighted_sample(mapping, 2, seed=1)
29
+ s = random_weighted_sample(mapping, 2)
30
+ assert sorted(s) == sorted(mapping.keys())
31
+ pytest.raises(ValueError, random_weighted_sample, mapping, 3)
32
+
33
+
34
+ def test_random_weighted_choice():
35
+ mapping = {"a": 10, "b": 0}
36
+ c = weighted_choice(mapping, seed=1)
37
+ c = weighted_choice(mapping)
38
+ assert c == "a"
.venv/lib/python3.11/site-packages/oauth2client/__init__.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Client library for using OAuth2, especially with Google APIs."""
16
+
17
+ __version__ = '4.1.3'
18
+
19
+ GOOGLE_AUTH_URI = 'https://accounts.google.com/o/oauth2/v2/auth'
20
+ GOOGLE_DEVICE_URI = 'https://oauth2.googleapis.com/device/code'
21
+ GOOGLE_REVOKE_URI = 'https://oauth2.googleapis.com/revoke'
22
+ GOOGLE_TOKEN_URI = 'https://oauth2.googleapis.com/token'
23
+ GOOGLE_TOKEN_INFO_URI = 'https://oauth2.googleapis.com/tokeninfo'
24
+
.venv/lib/python3.11/site-packages/oauth2client/_helpers.py ADDED
@@ -0,0 +1,341 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 base64
18
+ import functools
19
+ import inspect
20
+ import json
21
+ import logging
22
+ import os
23
+ import warnings
24
+
25
+ import six
26
+ from six.moves import urllib
27
+
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ POSITIONAL_WARNING = 'WARNING'
32
+ POSITIONAL_EXCEPTION = 'EXCEPTION'
33
+ POSITIONAL_IGNORE = 'IGNORE'
34
+ POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION,
35
+ POSITIONAL_IGNORE])
36
+
37
+ positional_parameters_enforcement = POSITIONAL_WARNING
38
+
39
+ _SYM_LINK_MESSAGE = 'File: {0}: Is a symbolic link.'
40
+ _IS_DIR_MESSAGE = '{0}: Is a directory'
41
+ _MISSING_FILE_MESSAGE = 'Cannot access {0}: No such file or directory'
42
+
43
+
44
+ def positional(max_positional_args):
45
+ """A decorator to declare that only the first N arguments my be positional.
46
+
47
+ This decorator makes it easy to support Python 3 style keyword-only
48
+ parameters. For example, in Python 3 it is possible to write::
49
+
50
+ def fn(pos1, *, kwonly1=None, kwonly1=None):
51
+ ...
52
+
53
+ All named parameters after ``*`` must be a keyword::
54
+
55
+ fn(10, 'kw1', 'kw2') # Raises exception.
56
+ fn(10, kwonly1='kw1') # Ok.
57
+
58
+ Example
59
+ ^^^^^^^
60
+
61
+ To define a function like above, do::
62
+
63
+ @positional(1)
64
+ def fn(pos1, kwonly1=None, kwonly2=None):
65
+ ...
66
+
67
+ If no default value is provided to a keyword argument, it becomes a
68
+ required keyword argument::
69
+
70
+ @positional(0)
71
+ def fn(required_kw):
72
+ ...
73
+
74
+ This must be called with the keyword parameter::
75
+
76
+ fn() # Raises exception.
77
+ fn(10) # Raises exception.
78
+ fn(required_kw=10) # Ok.
79
+
80
+ When defining instance or class methods always remember to account for
81
+ ``self`` and ``cls``::
82
+
83
+ class MyClass(object):
84
+
85
+ @positional(2)
86
+ def my_method(self, pos1, kwonly1=None):
87
+ ...
88
+
89
+ @classmethod
90
+ @positional(2)
91
+ def my_method(cls, pos1, kwonly1=None):
92
+ ...
93
+
94
+ The positional decorator behavior is controlled by
95
+ ``_helpers.positional_parameters_enforcement``, which may be set to
96
+ ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
97
+ ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
98
+ nothing, respectively, if a declaration is violated.
99
+
100
+ Args:
101
+ max_positional_arguments: Maximum number of positional arguments. All
102
+ parameters after the this index must be
103
+ keyword only.
104
+
105
+ Returns:
106
+ A decorator that prevents using arguments after max_positional_args
107
+ from being used as positional parameters.
108
+
109
+ Raises:
110
+ TypeError: if a key-word only argument is provided as a positional
111
+ parameter, but only if
112
+ _helpers.positional_parameters_enforcement is set to
113
+ POSITIONAL_EXCEPTION.
114
+ """
115
+
116
+ def positional_decorator(wrapped):
117
+ @functools.wraps(wrapped)
118
+ def positional_wrapper(*args, **kwargs):
119
+ if len(args) > max_positional_args:
120
+ plural_s = ''
121
+ if max_positional_args != 1:
122
+ plural_s = 's'
123
+ message = ('{function}() takes at most {args_max} positional '
124
+ 'argument{plural} ({args_given} given)'.format(
125
+ function=wrapped.__name__,
126
+ args_max=max_positional_args,
127
+ args_given=len(args),
128
+ plural=plural_s))
129
+ if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
130
+ raise TypeError(message)
131
+ elif positional_parameters_enforcement == POSITIONAL_WARNING:
132
+ logger.warning(message)
133
+ return wrapped(*args, **kwargs)
134
+ return positional_wrapper
135
+
136
+ if isinstance(max_positional_args, six.integer_types):
137
+ return positional_decorator
138
+ else:
139
+ args, _, _, defaults = inspect.getargspec(max_positional_args)
140
+ return positional(len(args) - len(defaults))(max_positional_args)
141
+
142
+
143
+ def scopes_to_string(scopes):
144
+ """Converts scope value to a string.
145
+
146
+ If scopes is a string then it is simply passed through. If scopes is an
147
+ iterable then a string is returned that is all the individual scopes
148
+ concatenated with spaces.
149
+
150
+ Args:
151
+ scopes: string or iterable of strings, the scopes.
152
+
153
+ Returns:
154
+ The scopes formatted as a single string.
155
+ """
156
+ if isinstance(scopes, six.string_types):
157
+ return scopes
158
+ else:
159
+ return ' '.join(scopes)
160
+
161
+
162
+ def string_to_scopes(scopes):
163
+ """Converts stringifed scope value to a list.
164
+
165
+ If scopes is a list then it is simply passed through. If scopes is an
166
+ string then a list of each individual scope is returned.
167
+
168
+ Args:
169
+ scopes: a string or iterable of strings, the scopes.
170
+
171
+ Returns:
172
+ The scopes in a list.
173
+ """
174
+ if not scopes:
175
+ return []
176
+ elif isinstance(scopes, six.string_types):
177
+ return scopes.split(' ')
178
+ else:
179
+ return scopes
180
+
181
+
182
+ def parse_unique_urlencoded(content):
183
+ """Parses unique key-value parameters from urlencoded content.
184
+
185
+ Args:
186
+ content: string, URL-encoded key-value pairs.
187
+
188
+ Returns:
189
+ dict, The key-value pairs from ``content``.
190
+
191
+ Raises:
192
+ ValueError: if one of the keys is repeated.
193
+ """
194
+ urlencoded_params = urllib.parse.parse_qs(content)
195
+ params = {}
196
+ for key, value in six.iteritems(urlencoded_params):
197
+ if len(value) != 1:
198
+ msg = ('URL-encoded content contains a repeated value:'
199
+ '%s -> %s' % (key, ', '.join(value)))
200
+ raise ValueError(msg)
201
+ params[key] = value[0]
202
+ return params
203
+
204
+
205
+ def update_query_params(uri, params):
206
+ """Updates a URI with new query parameters.
207
+
208
+ If a given key from ``params`` is repeated in the ``uri``, then
209
+ the URI will be considered invalid and an error will occur.
210
+
211
+ If the URI is valid, then each value from ``params`` will
212
+ replace the corresponding value in the query parameters (if
213
+ it exists).
214
+
215
+ Args:
216
+ uri: string, A valid URI, with potential existing query parameters.
217
+ params: dict, A dictionary of query parameters.
218
+
219
+ Returns:
220
+ The same URI but with the new query parameters added.
221
+ """
222
+ parts = urllib.parse.urlparse(uri)
223
+ query_params = parse_unique_urlencoded(parts.query)
224
+ query_params.update(params)
225
+ new_query = urllib.parse.urlencode(query_params)
226
+ new_parts = parts._replace(query=new_query)
227
+ return urllib.parse.urlunparse(new_parts)
228
+
229
+
230
+ def _add_query_parameter(url, name, value):
231
+ """Adds a query parameter to a url.
232
+
233
+ Replaces the current value if it already exists in the URL.
234
+
235
+ Args:
236
+ url: string, url to add the query parameter to.
237
+ name: string, query parameter name.
238
+ value: string, query parameter value.
239
+
240
+ Returns:
241
+ Updated query parameter. Does not update the url if value is None.
242
+ """
243
+ if value is None:
244
+ return url
245
+ else:
246
+ return update_query_params(url, {name: value})
247
+
248
+
249
+ def validate_file(filename):
250
+ if os.path.islink(filename):
251
+ raise IOError(_SYM_LINK_MESSAGE.format(filename))
252
+ elif os.path.isdir(filename):
253
+ raise IOError(_IS_DIR_MESSAGE.format(filename))
254
+ elif not os.path.isfile(filename):
255
+ warnings.warn(_MISSING_FILE_MESSAGE.format(filename))
256
+
257
+
258
+ def _parse_pem_key(raw_key_input):
259
+ """Identify and extract PEM keys.
260
+
261
+ Determines whether the given key is in the format of PEM key, and extracts
262
+ the relevant part of the key if it is.
263
+
264
+ Args:
265
+ raw_key_input: The contents of a private key file (either PEM or
266
+ PKCS12).
267
+
268
+ Returns:
269
+ string, The actual key if the contents are from a PEM file, or
270
+ else None.
271
+ """
272
+ offset = raw_key_input.find(b'-----BEGIN ')
273
+ if offset != -1:
274
+ return raw_key_input[offset:]
275
+
276
+
277
+ def _json_encode(data):
278
+ return json.dumps(data, separators=(',', ':'))
279
+
280
+
281
+ def _to_bytes(value, encoding='ascii'):
282
+ """Converts a string value to bytes, if necessary.
283
+
284
+ Unfortunately, ``six.b`` is insufficient for this task since in
285
+ Python2 it does not modify ``unicode`` objects.
286
+
287
+ Args:
288
+ value: The string/bytes value to be converted.
289
+ encoding: The encoding to use to convert unicode to bytes. Defaults
290
+ to "ascii", which will not allow any characters from ordinals
291
+ larger than 127. Other useful values are "latin-1", which
292
+ which will only allows byte ordinals (up to 255) and "utf-8",
293
+ which will encode any unicode that needs to be.
294
+
295
+ Returns:
296
+ The original value converted to bytes (if unicode) or as passed in
297
+ if it started out as bytes.
298
+
299
+ Raises:
300
+ ValueError if the value could not be converted to bytes.
301
+ """
302
+ result = (value.encode(encoding)
303
+ if isinstance(value, six.text_type) else value)
304
+ if isinstance(result, six.binary_type):
305
+ return result
306
+ else:
307
+ raise ValueError('{0!r} could not be converted to bytes'.format(value))
308
+
309
+
310
+ def _from_bytes(value):
311
+ """Converts bytes to a string value, if necessary.
312
+
313
+ Args:
314
+ value: The string/bytes value to be converted.
315
+
316
+ Returns:
317
+ The original value converted to unicode (if bytes) or as passed in
318
+ if it started out as unicode.
319
+
320
+ Raises:
321
+ ValueError if the value could not be converted to unicode.
322
+ """
323
+ result = (value.decode('utf-8')
324
+ if isinstance(value, six.binary_type) else value)
325
+ if isinstance(result, six.text_type):
326
+ return result
327
+ else:
328
+ raise ValueError(
329
+ '{0!r} could not be converted to unicode'.format(value))
330
+
331
+
332
+ def _urlsafe_b64encode(raw_bytes):
333
+ raw_bytes = _to_bytes(raw_bytes, encoding='utf-8')
334
+ return base64.urlsafe_b64encode(raw_bytes).rstrip(b'=')
335
+
336
+
337
+ def _urlsafe_b64decode(b64string):
338
+ # Guard against unicode strings, which base64 can't handle.
339
+ b64string = _to_bytes(b64string)
340
+ padded = b64string + b'=' * (4 - len(b64string) % 4)
341
+ return base64.urlsafe_b64decode(padded)
.venv/lib/python3.11/site-packages/oauth2client/_openssl_crypt.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """OpenSSL Crypto-related routines for oauth2client."""
15
+
16
+ from OpenSSL import crypto
17
+
18
+ from oauth2client import _helpers
19
+
20
+
21
+ class OpenSSLVerifier(object):
22
+ """Verifies the signature on a message."""
23
+
24
+ def __init__(self, pubkey):
25
+ """Constructor.
26
+
27
+ Args:
28
+ pubkey: OpenSSL.crypto.PKey, The public key to verify with.
29
+ """
30
+ self._pubkey = pubkey
31
+
32
+ def verify(self, message, signature):
33
+ """Verifies a message against a signature.
34
+
35
+ Args:
36
+ message: string or bytes, The message to verify. If string, will be
37
+ encoded to bytes as utf-8.
38
+ signature: string or bytes, The signature on the message. If string,
39
+ will be encoded to bytes as utf-8.
40
+
41
+ Returns:
42
+ True if message was signed by the private key associated with the
43
+ public key that this object was constructed with.
44
+ """
45
+ message = _helpers._to_bytes(message, encoding='utf-8')
46
+ signature = _helpers._to_bytes(signature, encoding='utf-8')
47
+ try:
48
+ crypto.verify(self._pubkey, signature, message, 'sha256')
49
+ return True
50
+ except crypto.Error:
51
+ return False
52
+
53
+ @staticmethod
54
+ def from_string(key_pem, is_x509_cert):
55
+ """Construct a Verified instance from a string.
56
+
57
+ Args:
58
+ key_pem: string, public key in PEM format.
59
+ is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it
60
+ is expected to be an RSA key in PEM format.
61
+
62
+ Returns:
63
+ Verifier instance.
64
+
65
+ Raises:
66
+ OpenSSL.crypto.Error: if the key_pem can't be parsed.
67
+ """
68
+ key_pem = _helpers._to_bytes(key_pem)
69
+ if is_x509_cert:
70
+ pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
71
+ else:
72
+ pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
73
+ return OpenSSLVerifier(pubkey)
74
+
75
+
76
+ class OpenSSLSigner(object):
77
+ """Signs messages with a private key."""
78
+
79
+ def __init__(self, pkey):
80
+ """Constructor.
81
+
82
+ Args:
83
+ pkey: OpenSSL.crypto.PKey (or equiv), The private key to sign with.
84
+ """
85
+ self._key = pkey
86
+
87
+ def sign(self, message):
88
+ """Signs a message.
89
+
90
+ Args:
91
+ message: bytes, Message to be signed.
92
+
93
+ Returns:
94
+ string, The signature of the message for the given key.
95
+ """
96
+ message = _helpers._to_bytes(message, encoding='utf-8')
97
+ return crypto.sign(self._key, message, 'sha256')
98
+
99
+ @staticmethod
100
+ def from_string(key, password=b'notasecret'):
101
+ """Construct a Signer instance from a string.
102
+
103
+ Args:
104
+ key: string, private key in PKCS12 or PEM format.
105
+ password: string, password for the private key file.
106
+
107
+ Returns:
108
+ Signer instance.
109
+
110
+ Raises:
111
+ OpenSSL.crypto.Error if the key can't be parsed.
112
+ """
113
+ key = _helpers._to_bytes(key)
114
+ parsed_pem_key = _helpers._parse_pem_key(key)
115
+ if parsed_pem_key:
116
+ pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, parsed_pem_key)
117
+ else:
118
+ password = _helpers._to_bytes(password, encoding='utf-8')
119
+ pkey = crypto.load_pkcs12(key, password).get_privatekey()
120
+ return OpenSSLSigner(pkey)
121
+
122
+
123
+ def pkcs12_key_as_pem(private_key_bytes, private_key_password):
124
+ """Convert the contents of a PKCS#12 key to PEM using pyOpenSSL.
125
+
126
+ Args:
127
+ private_key_bytes: Bytes. PKCS#12 key in DER format.
128
+ private_key_password: String. Password for PKCS#12 key.
129
+
130
+ Returns:
131
+ String. PEM contents of ``private_key_bytes``.
132
+ """
133
+ private_key_password = _helpers._to_bytes(private_key_password)
134
+ pkcs12 = crypto.load_pkcs12(private_key_bytes, private_key_password)
135
+ return crypto.dump_privatekey(crypto.FILETYPE_PEM,
136
+ pkcs12.get_privatekey())
.venv/lib/python3.11/site-packages/oauth2client/_pkce.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """
16
+ Utility functions for implementing Proof Key for Code Exchange (PKCE) by OAuth
17
+ Public Clients
18
+
19
+ See RFC7636.
20
+ """
21
+
22
+ import base64
23
+ import hashlib
24
+ import os
25
+
26
+
27
+ def code_verifier(n_bytes=64):
28
+ """
29
+ Generates a 'code_verifier' as described in section 4.1 of RFC 7636.
30
+
31
+ This is a 'high-entropy cryptographic random string' that will be
32
+ impractical for an attacker to guess.
33
+
34
+ Args:
35
+ n_bytes: integer between 31 and 96, inclusive. default: 64
36
+ number of bytes of entropy to include in verifier.
37
+
38
+ Returns:
39
+ Bytestring, representing urlsafe base64-encoded random data.
40
+ """
41
+ verifier = base64.urlsafe_b64encode(os.urandom(n_bytes)).rstrip(b'=')
42
+ # https://tools.ietf.org/html/rfc7636#section-4.1
43
+ # minimum length of 43 characters and a maximum length of 128 characters.
44
+ if len(verifier) < 43:
45
+ raise ValueError("Verifier too short. n_bytes must be > 30.")
46
+ elif len(verifier) > 128:
47
+ raise ValueError("Verifier too long. n_bytes must be < 97.")
48
+ else:
49
+ return verifier
50
+
51
+
52
+ def code_challenge(verifier):
53
+ """
54
+ Creates a 'code_challenge' as described in section 4.2 of RFC 7636
55
+ by taking the sha256 hash of the verifier and then urlsafe
56
+ base64-encoding it.
57
+
58
+ Args:
59
+ verifier: bytestring, representing a code_verifier as generated by
60
+ code_verifier().
61
+
62
+ Returns:
63
+ Bytestring, representing a urlsafe base64-encoded sha256 hash digest,
64
+ without '=' padding.
65
+ """
66
+ digest = hashlib.sha256(verifier).digest()
67
+ return base64.urlsafe_b64encode(digest).rstrip(b'=')
.venv/lib/python3.11/site-packages/oauth2client/_pure_python_crypt.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Pure Python crypto-related routines for oauth2client.
16
+
17
+ Uses the ``rsa``, ``pyasn1`` and ``pyasn1_modules`` packages
18
+ to parse PEM files storing PKCS#1 or PKCS#8 keys as well as
19
+ certificates.
20
+ """
21
+
22
+ from pyasn1.codec.der import decoder
23
+ from pyasn1_modules import pem
24
+ from pyasn1_modules.rfc2459 import Certificate
25
+ from pyasn1_modules.rfc5208 import PrivateKeyInfo
26
+ import rsa
27
+ import six
28
+
29
+ from oauth2client import _helpers
30
+
31
+
32
+ _PKCS12_ERROR = r"""\
33
+ PKCS12 format is not supported by the RSA library.
34
+ Either install PyOpenSSL, or please convert .p12 format
35
+ to .pem format:
36
+ $ cat key.p12 | \
37
+ > openssl pkcs12 -nodes -nocerts -passin pass:notasecret | \
38
+ > openssl rsa > key.pem
39
+ """
40
+
41
+ _POW2 = (128, 64, 32, 16, 8, 4, 2, 1)
42
+ _PKCS1_MARKER = ('-----BEGIN RSA PRIVATE KEY-----',
43
+ '-----END RSA PRIVATE KEY-----')
44
+ _PKCS8_MARKER = ('-----BEGIN PRIVATE KEY-----',
45
+ '-----END PRIVATE KEY-----')
46
+ _PKCS8_SPEC = PrivateKeyInfo()
47
+
48
+
49
+ def _bit_list_to_bytes(bit_list):
50
+ """Converts an iterable of 1's and 0's to bytes.
51
+
52
+ Combines the list 8 at a time, treating each group of 8 bits
53
+ as a single byte.
54
+ """
55
+ num_bits = len(bit_list)
56
+ byte_vals = bytearray()
57
+ for start in six.moves.xrange(0, num_bits, 8):
58
+ curr_bits = bit_list[start:start + 8]
59
+ char_val = sum(val * digit
60
+ for val, digit in zip(_POW2, curr_bits))
61
+ byte_vals.append(char_val)
62
+ return bytes(byte_vals)
63
+
64
+
65
+ class RsaVerifier(object):
66
+ """Verifies the signature on a message.
67
+
68
+ Args:
69
+ pubkey: rsa.key.PublicKey (or equiv), The public key to verify with.
70
+ """
71
+
72
+ def __init__(self, pubkey):
73
+ self._pubkey = pubkey
74
+
75
+ def verify(self, message, signature):
76
+ """Verifies a message against a signature.
77
+
78
+ Args:
79
+ message: string or bytes, The message to verify. If string, will be
80
+ encoded to bytes as utf-8.
81
+ signature: string or bytes, The signature on the message. If
82
+ string, will be encoded to bytes as utf-8.
83
+
84
+ Returns:
85
+ True if message was signed by the private key associated with the
86
+ public key that this object was constructed with.
87
+ """
88
+ message = _helpers._to_bytes(message, encoding='utf-8')
89
+ try:
90
+ return rsa.pkcs1.verify(message, signature, self._pubkey)
91
+ except (ValueError, rsa.pkcs1.VerificationError):
92
+ return False
93
+
94
+ @classmethod
95
+ def from_string(cls, key_pem, is_x509_cert):
96
+ """Construct an RsaVerifier instance from a string.
97
+
98
+ Args:
99
+ key_pem: string, public key in PEM format.
100
+ is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it
101
+ is expected to be an RSA key in PEM format.
102
+
103
+ Returns:
104
+ RsaVerifier instance.
105
+
106
+ Raises:
107
+ ValueError: if the key_pem can't be parsed. In either case, error
108
+ will begin with 'No PEM start marker'. If
109
+ ``is_x509_cert`` is True, will fail to find the
110
+ "-----BEGIN CERTIFICATE-----" error, otherwise fails
111
+ to find "-----BEGIN RSA PUBLIC KEY-----".
112
+ """
113
+ key_pem = _helpers._to_bytes(key_pem)
114
+ if is_x509_cert:
115
+ der = rsa.pem.load_pem(key_pem, 'CERTIFICATE')
116
+ asn1_cert, remaining = decoder.decode(der, asn1Spec=Certificate())
117
+ if remaining != b'':
118
+ raise ValueError('Unused bytes', remaining)
119
+
120
+ cert_info = asn1_cert['tbsCertificate']['subjectPublicKeyInfo']
121
+ key_bytes = _bit_list_to_bytes(cert_info['subjectPublicKey'])
122
+ pubkey = rsa.PublicKey.load_pkcs1(key_bytes, 'DER')
123
+ else:
124
+ pubkey = rsa.PublicKey.load_pkcs1(key_pem, 'PEM')
125
+ return cls(pubkey)
126
+
127
+
128
+ class RsaSigner(object):
129
+ """Signs messages with a private key.
130
+
131
+ Args:
132
+ pkey: rsa.key.PrivateKey (or equiv), The private key to sign with.
133
+ """
134
+
135
+ def __init__(self, pkey):
136
+ self._key = pkey
137
+
138
+ def sign(self, message):
139
+ """Signs a message.
140
+
141
+ Args:
142
+ message: bytes, Message to be signed.
143
+
144
+ Returns:
145
+ string, The signature of the message for the given key.
146
+ """
147
+ message = _helpers._to_bytes(message, encoding='utf-8')
148
+ return rsa.pkcs1.sign(message, self._key, 'SHA-256')
149
+
150
+ @classmethod
151
+ def from_string(cls, key, password='notasecret'):
152
+ """Construct an RsaSigner instance from a string.
153
+
154
+ Args:
155
+ key: string, private key in PEM format.
156
+ password: string, password for private key file. Unused for PEM
157
+ files.
158
+
159
+ Returns:
160
+ RsaSigner instance.
161
+
162
+ Raises:
163
+ ValueError if the key cannot be parsed as PKCS#1 or PKCS#8 in
164
+ PEM format.
165
+ """
166
+ key = _helpers._from_bytes(key) # pem expects str in Py3
167
+ marker_id, key_bytes = pem.readPemBlocksFromFile(
168
+ six.StringIO(key), _PKCS1_MARKER, _PKCS8_MARKER)
169
+
170
+ if marker_id == 0:
171
+ pkey = rsa.key.PrivateKey.load_pkcs1(key_bytes,
172
+ format='DER')
173
+ elif marker_id == 1:
174
+ key_info, remaining = decoder.decode(
175
+ key_bytes, asn1Spec=_PKCS8_SPEC)
176
+ if remaining != b'':
177
+ raise ValueError('Unused bytes', remaining)
178
+ pkey_info = key_info.getComponentByName('privateKey')
179
+ pkey = rsa.key.PrivateKey.load_pkcs1(pkey_info.asOctets(),
180
+ format='DER')
181
+ else:
182
+ raise ValueError('No key could be detected.')
183
+
184
+ return cls(pkey)
.venv/lib/python3.11/site-packages/oauth2client/_pycrypto_crypt.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """pyCrypto Crypto-related routines for oauth2client."""
15
+
16
+ from Crypto.Hash import SHA256
17
+ from Crypto.PublicKey import RSA
18
+ from Crypto.Signature import PKCS1_v1_5
19
+ from Crypto.Util.asn1 import DerSequence
20
+
21
+ from oauth2client import _helpers
22
+
23
+
24
+ class PyCryptoVerifier(object):
25
+ """Verifies the signature on a message."""
26
+
27
+ def __init__(self, pubkey):
28
+ """Constructor.
29
+
30
+ Args:
31
+ pubkey: OpenSSL.crypto.PKey (or equiv), The public key to verify
32
+ with.
33
+ """
34
+ self._pubkey = pubkey
35
+
36
+ def verify(self, message, signature):
37
+ """Verifies a message against a signature.
38
+
39
+ Args:
40
+ message: string or bytes, The message to verify. If string, will be
41
+ encoded to bytes as utf-8.
42
+ signature: string or bytes, The signature on the message.
43
+
44
+ Returns:
45
+ True if message was signed by the private key associated with the
46
+ public key that this object was constructed with.
47
+ """
48
+ message = _helpers._to_bytes(message, encoding='utf-8')
49
+ return PKCS1_v1_5.new(self._pubkey).verify(
50
+ SHA256.new(message), signature)
51
+
52
+ @staticmethod
53
+ def from_string(key_pem, is_x509_cert):
54
+ """Construct a Verified instance from a string.
55
+
56
+ Args:
57
+ key_pem: string, public key in PEM format.
58
+ is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it
59
+ is expected to be an RSA key in PEM format.
60
+
61
+ Returns:
62
+ Verifier instance.
63
+ """
64
+ if is_x509_cert:
65
+ key_pem = _helpers._to_bytes(key_pem)
66
+ pemLines = key_pem.replace(b' ', b'').split()
67
+ certDer = _helpers._urlsafe_b64decode(b''.join(pemLines[1:-1]))
68
+ certSeq = DerSequence()
69
+ certSeq.decode(certDer)
70
+ tbsSeq = DerSequence()
71
+ tbsSeq.decode(certSeq[0])
72
+ pubkey = RSA.importKey(tbsSeq[6])
73
+ else:
74
+ pubkey = RSA.importKey(key_pem)
75
+ return PyCryptoVerifier(pubkey)
76
+
77
+
78
+ class PyCryptoSigner(object):
79
+ """Signs messages with a private key."""
80
+
81
+ def __init__(self, pkey):
82
+ """Constructor.
83
+
84
+ Args:
85
+ pkey, OpenSSL.crypto.PKey (or equiv), The private key to sign with.
86
+ """
87
+ self._key = pkey
88
+
89
+ def sign(self, message):
90
+ """Signs a message.
91
+
92
+ Args:
93
+ message: string, Message to be signed.
94
+
95
+ Returns:
96
+ string, The signature of the message for the given key.
97
+ """
98
+ message = _helpers._to_bytes(message, encoding='utf-8')
99
+ return PKCS1_v1_5.new(self._key).sign(SHA256.new(message))
100
+
101
+ @staticmethod
102
+ def from_string(key, password='notasecret'):
103
+ """Construct a Signer instance from a string.
104
+
105
+ Args:
106
+ key: string, private key in PEM format.
107
+ password: string, password for private key file. Unused for PEM
108
+ files.
109
+
110
+ Returns:
111
+ Signer instance.
112
+
113
+ Raises:
114
+ NotImplementedError if the key isn't in PEM format.
115
+ """
116
+ parsed_pem_key = _helpers._parse_pem_key(_helpers._to_bytes(key))
117
+ if parsed_pem_key:
118
+ pkey = RSA.importKey(parsed_pem_key)
119
+ else:
120
+ raise NotImplementedError(
121
+ 'No key in PEM format was detected. This implementation '
122
+ 'can only use the PyCrypto library for keys in PEM '
123
+ 'format.')
124
+ return PyCryptoSigner(pkey)
.venv/lib/python3.11/site-packages/oauth2client/client.py ADDED
@@ -0,0 +1,2170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """An OAuth 2.0 client.
16
+
17
+ Tools for interacting with OAuth 2.0 protected resources.
18
+ """
19
+
20
+ import collections
21
+ import copy
22
+ import datetime
23
+ import json
24
+ import logging
25
+ import os
26
+ import shutil
27
+ import socket
28
+ import sys
29
+ import tempfile
30
+
31
+ import six
32
+ from six.moves import http_client
33
+ from six.moves import urllib
34
+
35
+ import oauth2client
36
+ from oauth2client import _helpers
37
+ from oauth2client import _pkce
38
+ from oauth2client import clientsecrets
39
+ from oauth2client import transport
40
+
41
+
42
+ HAS_OPENSSL = False
43
+ HAS_CRYPTO = False
44
+ try:
45
+ from oauth2client import crypt
46
+ HAS_CRYPTO = True
47
+ HAS_OPENSSL = crypt.OpenSSLVerifier is not None
48
+ except ImportError: # pragma: NO COVER
49
+ pass
50
+
51
+
52
+ logger = logging.getLogger(__name__)
53
+
54
+ # Expiry is stored in RFC3339 UTC format
55
+ EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
56
+
57
+ # Which certs to use to validate id_tokens received.
58
+ ID_TOKEN_VERIFICATION_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
59
+ # This symbol previously had a typo in the name; we keep the old name
60
+ # around for now, but will remove it in the future.
61
+ ID_TOKEN_VERIFICATON_CERTS = ID_TOKEN_VERIFICATION_CERTS
62
+
63
+ # Constant to use for the out of band OAuth 2.0 flow.
64
+ OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
65
+
66
+ # The value representing user credentials.
67
+ AUTHORIZED_USER = 'authorized_user'
68
+
69
+ # The value representing service account credentials.
70
+ SERVICE_ACCOUNT = 'service_account'
71
+
72
+ # The environment variable pointing the file with local
73
+ # Application Default Credentials.
74
+ GOOGLE_APPLICATION_CREDENTIALS = 'GOOGLE_APPLICATION_CREDENTIALS'
75
+ # The ~/.config subdirectory containing gcloud credentials. Intended
76
+ # to be swapped out in tests.
77
+ _CLOUDSDK_CONFIG_DIRECTORY = 'gcloud'
78
+ # The environment variable name which can replace ~/.config if set.
79
+ _CLOUDSDK_CONFIG_ENV_VAR = 'CLOUDSDK_CONFIG'
80
+
81
+ # The error message we show users when we can't find the Application
82
+ # Default Credentials.
83
+ ADC_HELP_MSG = (
84
+ 'The Application Default Credentials are not available. They are '
85
+ 'available if running in Google Compute Engine. Otherwise, the '
86
+ 'environment variable ' +
87
+ GOOGLE_APPLICATION_CREDENTIALS +
88
+ ' must be defined pointing to a file defining the credentials. See '
89
+ 'https://developers.google.com/accounts/docs/'
90
+ 'application-default-credentials for more information.')
91
+
92
+ _WELL_KNOWN_CREDENTIALS_FILE = 'application_default_credentials.json'
93
+
94
+ # The access token along with the seconds in which it expires.
95
+ AccessTokenInfo = collections.namedtuple(
96
+ 'AccessTokenInfo', ['access_token', 'expires_in'])
97
+
98
+ DEFAULT_ENV_NAME = 'UNKNOWN'
99
+
100
+ # If set to True _get_environment avoid GCE check (_detect_gce_environment)
101
+ NO_GCE_CHECK = os.getenv('NO_GCE_CHECK', 'False')
102
+
103
+ # Timeout in seconds to wait for the GCE metadata server when detecting the
104
+ # GCE environment.
105
+ try:
106
+ GCE_METADATA_TIMEOUT = int(os.getenv('GCE_METADATA_TIMEOUT', 3))
107
+ except ValueError: # pragma: NO COVER
108
+ GCE_METADATA_TIMEOUT = 3
109
+
110
+ _SERVER_SOFTWARE = 'SERVER_SOFTWARE'
111
+ _GCE_METADATA_URI = 'http://' + os.getenv('GCE_METADATA_IP', '169.254.169.254')
112
+ _METADATA_FLAVOR_HEADER = 'metadata-flavor' # lowercase header
113
+ _DESIRED_METADATA_FLAVOR = 'Google'
114
+ _GCE_HEADERS = {_METADATA_FLAVOR_HEADER: _DESIRED_METADATA_FLAVOR}
115
+
116
+ # Expose utcnow() at module level to allow for
117
+ # easier testing (by replacing with a stub).
118
+ _UTCNOW = datetime.datetime.utcnow
119
+
120
+ # NOTE: These names were previously defined in this module but have been
121
+ # moved into `oauth2client.transport`,
122
+ clean_headers = transport.clean_headers
123
+ MemoryCache = transport.MemoryCache
124
+ REFRESH_STATUS_CODES = transport.REFRESH_STATUS_CODES
125
+
126
+
127
+ class SETTINGS(object):
128
+ """Settings namespace for globally defined values."""
129
+ env_name = None
130
+
131
+
132
+ class Error(Exception):
133
+ """Base error for this module."""
134
+
135
+
136
+ class FlowExchangeError(Error):
137
+ """Error trying to exchange an authorization grant for an access token."""
138
+
139
+
140
+ class AccessTokenRefreshError(Error):
141
+ """Error trying to refresh an expired access token."""
142
+
143
+
144
+ class HttpAccessTokenRefreshError(AccessTokenRefreshError):
145
+ """Error (with HTTP status) trying to refresh an expired access token."""
146
+ def __init__(self, *args, **kwargs):
147
+ super(HttpAccessTokenRefreshError, self).__init__(*args)
148
+ self.status = kwargs.get('status')
149
+
150
+
151
+ class TokenRevokeError(Error):
152
+ """Error trying to revoke a token."""
153
+
154
+
155
+ class UnknownClientSecretsFlowError(Error):
156
+ """The client secrets file called for an unknown type of OAuth 2.0 flow."""
157
+
158
+
159
+ class AccessTokenCredentialsError(Error):
160
+ """Having only the access_token means no refresh is possible."""
161
+
162
+
163
+ class VerifyJwtTokenError(Error):
164
+ """Could not retrieve certificates for validation."""
165
+
166
+
167
+ class NonAsciiHeaderError(Error):
168
+ """Header names and values must be ASCII strings."""
169
+
170
+
171
+ class ApplicationDefaultCredentialsError(Error):
172
+ """Error retrieving the Application Default Credentials."""
173
+
174
+
175
+ class OAuth2DeviceCodeError(Error):
176
+ """Error trying to retrieve a device code."""
177
+
178
+
179
+ class CryptoUnavailableError(Error, NotImplementedError):
180
+ """Raised when a crypto library is required, but none is available."""
181
+
182
+
183
+ def _parse_expiry(expiry):
184
+ if expiry and isinstance(expiry, datetime.datetime):
185
+ return expiry.strftime(EXPIRY_FORMAT)
186
+ else:
187
+ return None
188
+
189
+
190
+ class Credentials(object):
191
+ """Base class for all Credentials objects.
192
+
193
+ Subclasses must define an authorize() method that applies the credentials
194
+ to an HTTP transport.
195
+
196
+ Subclasses must also specify a classmethod named 'from_json' that takes a
197
+ JSON string as input and returns an instantiated Credentials object.
198
+ """
199
+
200
+ NON_SERIALIZED_MEMBERS = frozenset(['store'])
201
+
202
+ def authorize(self, http):
203
+ """Take an httplib2.Http instance (or equivalent) and authorizes it.
204
+
205
+ Authorizes it for the set of credentials, usually by replacing
206
+ http.request() with a method that adds in the appropriate headers and
207
+ then delegates to the original Http.request() method.
208
+
209
+ Args:
210
+ http: httplib2.Http, an http object to be used to make the refresh
211
+ request.
212
+ """
213
+ raise NotImplementedError
214
+
215
+ def refresh(self, http):
216
+ """Forces a refresh of the access_token.
217
+
218
+ Args:
219
+ http: httplib2.Http, an http object to be used to make the refresh
220
+ request.
221
+ """
222
+ raise NotImplementedError
223
+
224
+ def revoke(self, http):
225
+ """Revokes a refresh_token and makes the credentials void.
226
+
227
+ Args:
228
+ http: httplib2.Http, an http object to be used to make the revoke
229
+ request.
230
+ """
231
+ raise NotImplementedError
232
+
233
+ def apply(self, headers):
234
+ """Add the authorization to the headers.
235
+
236
+ Args:
237
+ headers: dict, the headers to add the Authorization header to.
238
+ """
239
+ raise NotImplementedError
240
+
241
+ def _to_json(self, strip, to_serialize=None):
242
+ """Utility function that creates JSON repr. of a Credentials object.
243
+
244
+ Args:
245
+ strip: array, An array of names of members to exclude from the
246
+ JSON.
247
+ to_serialize: dict, (Optional) The properties for this object
248
+ that will be serialized. This allows callers to
249
+ modify before serializing.
250
+
251
+ Returns:
252
+ string, a JSON representation of this instance, suitable to pass to
253
+ from_json().
254
+ """
255
+ curr_type = self.__class__
256
+ if to_serialize is None:
257
+ to_serialize = copy.copy(self.__dict__)
258
+ else:
259
+ # Assumes it is a str->str dictionary, so we don't deep copy.
260
+ to_serialize = copy.copy(to_serialize)
261
+ for member in strip:
262
+ if member in to_serialize:
263
+ del to_serialize[member]
264
+ to_serialize['token_expiry'] = _parse_expiry(
265
+ to_serialize.get('token_expiry'))
266
+ # Add in information we will need later to reconstitute this instance.
267
+ to_serialize['_class'] = curr_type.__name__
268
+ to_serialize['_module'] = curr_type.__module__
269
+ for key, val in to_serialize.items():
270
+ if isinstance(val, bytes):
271
+ to_serialize[key] = val.decode('utf-8')
272
+ if isinstance(val, set):
273
+ to_serialize[key] = list(val)
274
+ return json.dumps(to_serialize)
275
+
276
+ def to_json(self):
277
+ """Creating a JSON representation of an instance of Credentials.
278
+
279
+ Returns:
280
+ string, a JSON representation of this instance, suitable to pass to
281
+ from_json().
282
+ """
283
+ return self._to_json(self.NON_SERIALIZED_MEMBERS)
284
+
285
+ @classmethod
286
+ def new_from_json(cls, json_data):
287
+ """Utility class method to instantiate a Credentials subclass from JSON.
288
+
289
+ Expects the JSON string to have been produced by to_json().
290
+
291
+ Args:
292
+ json_data: string or bytes, JSON from to_json().
293
+
294
+ Returns:
295
+ An instance of the subclass of Credentials that was serialized with
296
+ to_json().
297
+ """
298
+ json_data_as_unicode = _helpers._from_bytes(json_data)
299
+ data = json.loads(json_data_as_unicode)
300
+ # Find and call the right classmethod from_json() to restore
301
+ # the object.
302
+ module_name = data['_module']
303
+ try:
304
+ module_obj = __import__(module_name)
305
+ except ImportError:
306
+ # In case there's an object from the old package structure,
307
+ # update it
308
+ module_name = module_name.replace('.googleapiclient', '')
309
+ module_obj = __import__(module_name)
310
+
311
+ module_obj = __import__(module_name,
312
+ fromlist=module_name.split('.')[:-1])
313
+ kls = getattr(module_obj, data['_class'])
314
+ return kls.from_json(json_data_as_unicode)
315
+
316
+ @classmethod
317
+ def from_json(cls, unused_data):
318
+ """Instantiate a Credentials object from a JSON description of it.
319
+
320
+ The JSON should have been produced by calling .to_json() on the object.
321
+
322
+ Args:
323
+ unused_data: dict, A deserialized JSON object.
324
+
325
+ Returns:
326
+ An instance of a Credentials subclass.
327
+ """
328
+ return Credentials()
329
+
330
+
331
+ class Flow(object):
332
+ """Base class for all Flow objects."""
333
+ pass
334
+
335
+
336
+ class Storage(object):
337
+ """Base class for all Storage objects.
338
+
339
+ Store and retrieve a single credential. This class supports locking
340
+ such that multiple processes and threads can operate on a single
341
+ store.
342
+ """
343
+ def __init__(self, lock=None):
344
+ """Create a Storage instance.
345
+
346
+ Args:
347
+ lock: An optional threading.Lock-like object. Must implement at
348
+ least acquire() and release(). Does not need to be
349
+ re-entrant.
350
+ """
351
+ self._lock = lock
352
+
353
+ def acquire_lock(self):
354
+ """Acquires any lock necessary to access this Storage.
355
+
356
+ This lock is not reentrant.
357
+ """
358
+ if self._lock is not None:
359
+ self._lock.acquire()
360
+
361
+ def release_lock(self):
362
+ """Release the Storage lock.
363
+
364
+ Trying to release a lock that isn't held will result in a
365
+ RuntimeError in the case of a threading.Lock or multiprocessing.Lock.
366
+ """
367
+ if self._lock is not None:
368
+ self._lock.release()
369
+
370
+ def locked_get(self):
371
+ """Retrieve credential.
372
+
373
+ The Storage lock must be held when this is called.
374
+
375
+ Returns:
376
+ oauth2client.client.Credentials
377
+ """
378
+ raise NotImplementedError
379
+
380
+ def locked_put(self, credentials):
381
+ """Write a credential.
382
+
383
+ The Storage lock must be held when this is called.
384
+
385
+ Args:
386
+ credentials: Credentials, the credentials to store.
387
+ """
388
+ raise NotImplementedError
389
+
390
+ def locked_delete(self):
391
+ """Delete a credential.
392
+
393
+ The Storage lock must be held when this is called.
394
+ """
395
+ raise NotImplementedError
396
+
397
+ def get(self):
398
+ """Retrieve credential.
399
+
400
+ The Storage lock must *not* be held when this is called.
401
+
402
+ Returns:
403
+ oauth2client.client.Credentials
404
+ """
405
+ self.acquire_lock()
406
+ try:
407
+ return self.locked_get()
408
+ finally:
409
+ self.release_lock()
410
+
411
+ def put(self, credentials):
412
+ """Write a credential.
413
+
414
+ The Storage lock must be held when this is called.
415
+
416
+ Args:
417
+ credentials: Credentials, the credentials to store.
418
+ """
419
+ self.acquire_lock()
420
+ try:
421
+ self.locked_put(credentials)
422
+ finally:
423
+ self.release_lock()
424
+
425
+ def delete(self):
426
+ """Delete credential.
427
+
428
+ Frees any resources associated with storing the credential.
429
+ The Storage lock must *not* be held when this is called.
430
+
431
+ Returns:
432
+ None
433
+ """
434
+ self.acquire_lock()
435
+ try:
436
+ return self.locked_delete()
437
+ finally:
438
+ self.release_lock()
439
+
440
+
441
+ class OAuth2Credentials(Credentials):
442
+ """Credentials object for OAuth 2.0.
443
+
444
+ Credentials can be applied to an httplib2.Http object using the authorize()
445
+ method, which then adds the OAuth 2.0 access token to each request.
446
+
447
+ OAuth2Credentials objects may be safely pickled and unpickled.
448
+ """
449
+
450
+ @_helpers.positional(8)
451
+ def __init__(self, access_token, client_id, client_secret, refresh_token,
452
+ token_expiry, token_uri, user_agent, revoke_uri=None,
453
+ id_token=None, token_response=None, scopes=None,
454
+ token_info_uri=None, id_token_jwt=None):
455
+ """Create an instance of OAuth2Credentials.
456
+
457
+ This constructor is not usually called by the user, instead
458
+ OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow.
459
+
460
+ Args:
461
+ access_token: string, access token.
462
+ client_id: string, client identifier.
463
+ client_secret: string, client secret.
464
+ refresh_token: string, refresh token.
465
+ token_expiry: datetime, when the access_token expires.
466
+ token_uri: string, URI of token endpoint.
467
+ user_agent: string, The HTTP User-Agent to provide for this
468
+ application.
469
+ revoke_uri: string, URI for revoke endpoint. Defaults to None; a
470
+ token can't be revoked if this is None.
471
+ id_token: object, The identity of the resource owner.
472
+ token_response: dict, the decoded response to the token request.
473
+ None if a token hasn't been requested yet. Stored
474
+ because some providers (e.g. wordpress.com) include
475
+ extra fields that clients may want.
476
+ scopes: list, authorized scopes for these credentials.
477
+ token_info_uri: string, the URI for the token info endpoint.
478
+ Defaults to None; scopes can not be refreshed if
479
+ this is None.
480
+ id_token_jwt: string, the encoded and signed identity JWT. The
481
+ decoded version of this is stored in id_token.
482
+
483
+ Notes:
484
+ store: callable, A callable that when passed a Credential
485
+ will store the credential back to where it came from.
486
+ This is needed to store the latest access_token if it
487
+ has expired and been refreshed.
488
+ """
489
+ self.access_token = access_token
490
+ self.client_id = client_id
491
+ self.client_secret = client_secret
492
+ self.refresh_token = refresh_token
493
+ self.store = None
494
+ self.token_expiry = token_expiry
495
+ self.token_uri = token_uri
496
+ self.user_agent = user_agent
497
+ self.revoke_uri = revoke_uri
498
+ self.id_token = id_token
499
+ self.id_token_jwt = id_token_jwt
500
+ self.token_response = token_response
501
+ self.scopes = set(_helpers.string_to_scopes(scopes or []))
502
+ self.token_info_uri = token_info_uri
503
+
504
+ # True if the credentials have been revoked or expired and can't be
505
+ # refreshed.
506
+ self.invalid = False
507
+
508
+ def authorize(self, http):
509
+ """Authorize an httplib2.Http instance with these credentials.
510
+
511
+ The modified http.request method will add authentication headers to
512
+ each request and will refresh access_tokens when a 401 is received on a
513
+ request. In addition the http.request method has a credentials
514
+ property, http.request.credentials, which is the Credentials object
515
+ that authorized it.
516
+
517
+ Args:
518
+ http: An instance of ``httplib2.Http`` or something that acts
519
+ like it.
520
+
521
+ Returns:
522
+ A modified instance of http that was passed in.
523
+
524
+ Example::
525
+
526
+ h = httplib2.Http()
527
+ h = credentials.authorize(h)
528
+
529
+ You can't create a new OAuth subclass of httplib2.Authentication
530
+ because it never gets passed the absolute URI, which is needed for
531
+ signing. So instead we have to overload 'request' with a closure
532
+ that adds in the Authorization header and then calls the original
533
+ version of 'request()'.
534
+ """
535
+ transport.wrap_http_for_auth(self, http)
536
+ return http
537
+
538
+ def refresh(self, http):
539
+ """Forces a refresh of the access_token.
540
+
541
+ Args:
542
+ http: httplib2.Http, an http object to be used to make the refresh
543
+ request.
544
+ """
545
+ self._refresh(http)
546
+
547
+ def revoke(self, http):
548
+ """Revokes a refresh_token and makes the credentials void.
549
+
550
+ Args:
551
+ http: httplib2.Http, an http object to be used to make the revoke
552
+ request.
553
+ """
554
+ self._revoke(http)
555
+
556
+ def apply(self, headers):
557
+ """Add the authorization to the headers.
558
+
559
+ Args:
560
+ headers: dict, the headers to add the Authorization header to.
561
+ """
562
+ headers['Authorization'] = 'Bearer ' + self.access_token
563
+
564
+ def has_scopes(self, scopes):
565
+ """Verify that the credentials are authorized for the given scopes.
566
+
567
+ Returns True if the credentials authorized scopes contain all of the
568
+ scopes given.
569
+
570
+ Args:
571
+ scopes: list or string, the scopes to check.
572
+
573
+ Notes:
574
+ There are cases where the credentials are unaware of which scopes
575
+ are authorized. Notably, credentials obtained and stored before
576
+ this code was added will not have scopes, AccessTokenCredentials do
577
+ not have scopes. In both cases, you can use refresh_scopes() to
578
+ obtain the canonical set of scopes.
579
+ """
580
+ scopes = _helpers.string_to_scopes(scopes)
581
+ return set(scopes).issubset(self.scopes)
582
+
583
+ def retrieve_scopes(self, http):
584
+ """Retrieves the canonical list of scopes for this access token.
585
+
586
+ Gets the scopes from the OAuth2 provider.
587
+
588
+ Args:
589
+ http: httplib2.Http, an http object to be used to make the refresh
590
+ request.
591
+
592
+ Returns:
593
+ A set of strings containing the canonical list of scopes.
594
+ """
595
+ self._retrieve_scopes(http)
596
+ return self.scopes
597
+
598
+ @classmethod
599
+ def from_json(cls, json_data):
600
+ """Instantiate a Credentials object from a JSON description of it.
601
+
602
+ The JSON should have been produced by calling .to_json() on the object.
603
+
604
+ Args:
605
+ json_data: string or bytes, JSON to deserialize.
606
+
607
+ Returns:
608
+ An instance of a Credentials subclass.
609
+ """
610
+ data = json.loads(_helpers._from_bytes(json_data))
611
+ if (data.get('token_expiry') and
612
+ not isinstance(data['token_expiry'], datetime.datetime)):
613
+ try:
614
+ data['token_expiry'] = datetime.datetime.strptime(
615
+ data['token_expiry'], EXPIRY_FORMAT)
616
+ except ValueError:
617
+ data['token_expiry'] = None
618
+ retval = cls(
619
+ data['access_token'],
620
+ data['client_id'],
621
+ data['client_secret'],
622
+ data['refresh_token'],
623
+ data['token_expiry'],
624
+ data['token_uri'],
625
+ data['user_agent'],
626
+ revoke_uri=data.get('revoke_uri', None),
627
+ id_token=data.get('id_token', None),
628
+ id_token_jwt=data.get('id_token_jwt', None),
629
+ token_response=data.get('token_response', None),
630
+ scopes=data.get('scopes', None),
631
+ token_info_uri=data.get('token_info_uri', None))
632
+ retval.invalid = data['invalid']
633
+ return retval
634
+
635
+ @property
636
+ def access_token_expired(self):
637
+ """True if the credential is expired or invalid.
638
+
639
+ If the token_expiry isn't set, we assume the token doesn't expire.
640
+ """
641
+ if self.invalid:
642
+ return True
643
+
644
+ if not self.token_expiry:
645
+ return False
646
+
647
+ now = _UTCNOW()
648
+ if now >= self.token_expiry:
649
+ logger.info('access_token is expired. Now: %s, token_expiry: %s',
650
+ now, self.token_expiry)
651
+ return True
652
+ return False
653
+
654
+ def get_access_token(self, http=None):
655
+ """Return the access token and its expiration information.
656
+
657
+ If the token does not exist, get one.
658
+ If the token expired, refresh it.
659
+ """
660
+ if not self.access_token or self.access_token_expired:
661
+ if not http:
662
+ http = transport.get_http_object()
663
+ self.refresh(http)
664
+ return AccessTokenInfo(access_token=self.access_token,
665
+ expires_in=self._expires_in())
666
+
667
+ def set_store(self, store):
668
+ """Set the Storage for the credential.
669
+
670
+ Args:
671
+ store: Storage, an implementation of Storage object.
672
+ This is needed to store the latest access_token if it
673
+ has expired and been refreshed. This implementation uses
674
+ locking to check for updates before updating the
675
+ access_token.
676
+ """
677
+ self.store = store
678
+
679
+ def _expires_in(self):
680
+ """Return the number of seconds until this token expires.
681
+
682
+ If token_expiry is in the past, this method will return 0, meaning the
683
+ token has already expired.
684
+
685
+ If token_expiry is None, this method will return None. Note that
686
+ returning 0 in such a case would not be fair: the token may still be
687
+ valid; we just don't know anything about it.
688
+ """
689
+ if self.token_expiry:
690
+ now = _UTCNOW()
691
+ if self.token_expiry > now:
692
+ time_delta = self.token_expiry - now
693
+ # TODO(orestica): return time_delta.total_seconds()
694
+ # once dropping support for Python 2.6
695
+ return time_delta.days * 86400 + time_delta.seconds
696
+ else:
697
+ return 0
698
+
699
+ def _updateFromCredential(self, other):
700
+ """Update this Credential from another instance."""
701
+ self.__dict__.update(other.__getstate__())
702
+
703
+ def __getstate__(self):
704
+ """Trim the state down to something that can be pickled."""
705
+ d = copy.copy(self.__dict__)
706
+ del d['store']
707
+ return d
708
+
709
+ def __setstate__(self, state):
710
+ """Reconstitute the state of the object from being pickled."""
711
+ self.__dict__.update(state)
712
+ self.store = None
713
+
714
+ def _generate_refresh_request_body(self):
715
+ """Generate the body that will be used in the refresh request."""
716
+ body = urllib.parse.urlencode({
717
+ 'grant_type': 'refresh_token',
718
+ 'client_id': self.client_id,
719
+ 'client_secret': self.client_secret,
720
+ 'refresh_token': self.refresh_token,
721
+ })
722
+ return body
723
+
724
+ def _generate_refresh_request_headers(self):
725
+ """Generate the headers that will be used in the refresh request."""
726
+ headers = {
727
+ 'content-type': 'application/x-www-form-urlencoded',
728
+ }
729
+
730
+ if self.user_agent is not None:
731
+ headers['user-agent'] = self.user_agent
732
+
733
+ return headers
734
+
735
+ def _refresh(self, http):
736
+ """Refreshes the access_token.
737
+
738
+ This method first checks by reading the Storage object if available.
739
+ If a refresh is still needed, it holds the Storage lock until the
740
+ refresh is completed.
741
+
742
+ Args:
743
+ http: an object to be used to make HTTP requests.
744
+
745
+ Raises:
746
+ HttpAccessTokenRefreshError: When the refresh fails.
747
+ """
748
+ if not self.store:
749
+ self._do_refresh_request(http)
750
+ else:
751
+ self.store.acquire_lock()
752
+ try:
753
+ new_cred = self.store.locked_get()
754
+
755
+ if (new_cred and not new_cred.invalid and
756
+ new_cred.access_token != self.access_token and
757
+ not new_cred.access_token_expired):
758
+ logger.info('Updated access_token read from Storage')
759
+ self._updateFromCredential(new_cred)
760
+ else:
761
+ self._do_refresh_request(http)
762
+ finally:
763
+ self.store.release_lock()
764
+
765
+ def _do_refresh_request(self, http):
766
+ """Refresh the access_token using the refresh_token.
767
+
768
+ Args:
769
+ http: an object to be used to make HTTP requests.
770
+
771
+ Raises:
772
+ HttpAccessTokenRefreshError: When the refresh fails.
773
+ """
774
+ body = self._generate_refresh_request_body()
775
+ headers = self._generate_refresh_request_headers()
776
+
777
+ logger.info('Refreshing access_token')
778
+ resp, content = transport.request(
779
+ http, self.token_uri, method='POST',
780
+ body=body, headers=headers)
781
+ content = _helpers._from_bytes(content)
782
+ if resp.status == http_client.OK:
783
+ d = json.loads(content)
784
+ self.token_response = d
785
+ self.access_token = d['access_token']
786
+ self.refresh_token = d.get('refresh_token', self.refresh_token)
787
+ if 'expires_in' in d:
788
+ delta = datetime.timedelta(seconds=int(d['expires_in']))
789
+ self.token_expiry = delta + _UTCNOW()
790
+ else:
791
+ self.token_expiry = None
792
+ if 'id_token' in d:
793
+ self.id_token = _extract_id_token(d['id_token'])
794
+ self.id_token_jwt = d['id_token']
795
+ else:
796
+ self.id_token = None
797
+ self.id_token_jwt = None
798
+ # On temporary refresh errors, the user does not actually have to
799
+ # re-authorize, so we unflag here.
800
+ self.invalid = False
801
+ if self.store:
802
+ self.store.locked_put(self)
803
+ else:
804
+ # An {'error':...} response body means the token is expired or
805
+ # revoked, so we flag the credentials as such.
806
+ logger.info('Failed to retrieve access token: %s', content)
807
+ error_msg = 'Invalid response {0}.'.format(resp.status)
808
+ try:
809
+ d = json.loads(content)
810
+ if 'error' in d:
811
+ error_msg = d['error']
812
+ if 'error_description' in d:
813
+ error_msg += ': ' + d['error_description']
814
+ self.invalid = True
815
+ if self.store is not None:
816
+ self.store.locked_put(self)
817
+ except (TypeError, ValueError):
818
+ pass
819
+ raise HttpAccessTokenRefreshError(error_msg, status=resp.status)
820
+
821
+ def _revoke(self, http):
822
+ """Revokes this credential and deletes the stored copy (if it exists).
823
+
824
+ Args:
825
+ http: an object to be used to make HTTP requests.
826
+ """
827
+ self._do_revoke(http, self.refresh_token or self.access_token)
828
+
829
+ def _do_revoke(self, http, token):
830
+ """Revokes this credential and deletes the stored copy (if it exists).
831
+
832
+ Args:
833
+ http: an object to be used to make HTTP requests.
834
+ token: A string used as the token to be revoked. Can be either an
835
+ access_token or refresh_token.
836
+
837
+ Raises:
838
+ TokenRevokeError: If the revoke request does not return with a
839
+ 200 OK.
840
+ """
841
+ logger.info('Revoking token')
842
+ query_params = {'token': token}
843
+ token_revoke_uri = _helpers.update_query_params(
844
+ self.revoke_uri, query_params)
845
+ resp, content = transport.request(http, token_revoke_uri)
846
+ if resp.status == http_client.METHOD_NOT_ALLOWED:
847
+ body = urllib.parse.urlencode(query_params)
848
+ resp, content = transport.request(http, token_revoke_uri,
849
+ method='POST', body=body)
850
+ if resp.status == http_client.OK:
851
+ self.invalid = True
852
+ else:
853
+ error_msg = 'Invalid response {0}.'.format(resp.status)
854
+ try:
855
+ d = json.loads(_helpers._from_bytes(content))
856
+ if 'error' in d:
857
+ error_msg = d['error']
858
+ except (TypeError, ValueError):
859
+ pass
860
+ raise TokenRevokeError(error_msg)
861
+
862
+ if self.store:
863
+ self.store.delete()
864
+
865
+ def _retrieve_scopes(self, http):
866
+ """Retrieves the list of authorized scopes from the OAuth2 provider.
867
+
868
+ Args:
869
+ http: an object to be used to make HTTP requests.
870
+ """
871
+ self._do_retrieve_scopes(http, self.access_token)
872
+
873
+ def _do_retrieve_scopes(self, http, token):
874
+ """Retrieves the list of authorized scopes from the OAuth2 provider.
875
+
876
+ Args:
877
+ http: an object to be used to make HTTP requests.
878
+ token: A string used as the token to identify the credentials to
879
+ the provider.
880
+
881
+ Raises:
882
+ Error: When refresh fails, indicating the the access token is
883
+ invalid.
884
+ """
885
+ logger.info('Refreshing scopes')
886
+ query_params = {'access_token': token, 'fields': 'scope'}
887
+ token_info_uri = _helpers.update_query_params(
888
+ self.token_info_uri, query_params)
889
+ resp, content = transport.request(http, token_info_uri)
890
+ content = _helpers._from_bytes(content)
891
+ if resp.status == http_client.OK:
892
+ d = json.loads(content)
893
+ self.scopes = set(_helpers.string_to_scopes(d.get('scope', '')))
894
+ else:
895
+ error_msg = 'Invalid response {0}.'.format(resp.status)
896
+ try:
897
+ d = json.loads(content)
898
+ if 'error_description' in d:
899
+ error_msg = d['error_description']
900
+ except (TypeError, ValueError):
901
+ pass
902
+ raise Error(error_msg)
903
+
904
+
905
+ class AccessTokenCredentials(OAuth2Credentials):
906
+ """Credentials object for OAuth 2.0.
907
+
908
+ Credentials can be applied to an httplib2.Http object using the
909
+ authorize() method, which then signs each request from that object
910
+ with the OAuth 2.0 access token. This set of credentials is for the
911
+ use case where you have acquired an OAuth 2.0 access_token from
912
+ another place such as a JavaScript client or another web
913
+ application, and wish to use it from Python. Because only the
914
+ access_token is present it can not be refreshed and will in time
915
+ expire.
916
+
917
+ AccessTokenCredentials objects may be safely pickled and unpickled.
918
+
919
+ Usage::
920
+
921
+ credentials = AccessTokenCredentials('<an access token>',
922
+ 'my-user-agent/1.0')
923
+ http = httplib2.Http()
924
+ http = credentials.authorize(http)
925
+
926
+ Raises:
927
+ AccessTokenCredentialsExpired: raised when the access_token expires or
928
+ is revoked.
929
+ """
930
+
931
+ def __init__(self, access_token, user_agent, revoke_uri=None):
932
+ """Create an instance of OAuth2Credentials
933
+
934
+ This is one of the few types if Credentials that you should contrust,
935
+ Credentials objects are usually instantiated by a Flow.
936
+
937
+ Args:
938
+ access_token: string, access token.
939
+ user_agent: string, The HTTP User-Agent to provide for this
940
+ application.
941
+ revoke_uri: string, URI for revoke endpoint. Defaults to None; a
942
+ token can't be revoked if this is None.
943
+ """
944
+ super(AccessTokenCredentials, self).__init__(
945
+ access_token,
946
+ None,
947
+ None,
948
+ None,
949
+ None,
950
+ None,
951
+ user_agent,
952
+ revoke_uri=revoke_uri)
953
+
954
+ @classmethod
955
+ def from_json(cls, json_data):
956
+ data = json.loads(_helpers._from_bytes(json_data))
957
+ retval = AccessTokenCredentials(
958
+ data['access_token'],
959
+ data['user_agent'])
960
+ return retval
961
+
962
+ def _refresh(self, http):
963
+ """Refreshes the access token.
964
+
965
+ Args:
966
+ http: unused HTTP object.
967
+
968
+ Raises:
969
+ AccessTokenCredentialsError: always
970
+ """
971
+ raise AccessTokenCredentialsError(
972
+ 'The access_token is expired or invalid and can\'t be refreshed.')
973
+
974
+ def _revoke(self, http):
975
+ """Revokes the access_token and deletes the store if available.
976
+
977
+ Args:
978
+ http: an object to be used to make HTTP requests.
979
+ """
980
+ self._do_revoke(http, self.access_token)
981
+
982
+
983
+ def _detect_gce_environment():
984
+ """Determine if the current environment is Compute Engine.
985
+
986
+ Returns:
987
+ Boolean indicating whether or not the current environment is Google
988
+ Compute Engine.
989
+ """
990
+ # NOTE: The explicit ``timeout`` is a workaround. The underlying
991
+ # issue is that resolving an unknown host on some networks will take
992
+ # 20-30 seconds; making this timeout short fixes the issue, but
993
+ # could lead to false negatives in the event that we are on GCE, but
994
+ # the metadata resolution was particularly slow. The latter case is
995
+ # "unlikely".
996
+ http = transport.get_http_object(timeout=GCE_METADATA_TIMEOUT)
997
+ try:
998
+ response, _ = transport.request(
999
+ http, _GCE_METADATA_URI, headers=_GCE_HEADERS)
1000
+ return (
1001
+ response.status == http_client.OK and
1002
+ response.get(_METADATA_FLAVOR_HEADER) == _DESIRED_METADATA_FLAVOR)
1003
+ except socket.error: # socket.timeout or socket.error(64, 'Host is down')
1004
+ logger.info('Timeout attempting to reach GCE metadata service.')
1005
+ return False
1006
+
1007
+
1008
+ def _in_gae_environment():
1009
+ """Detects if the code is running in the App Engine environment.
1010
+
1011
+ Returns:
1012
+ True if running in the GAE environment, False otherwise.
1013
+ """
1014
+ if SETTINGS.env_name is not None:
1015
+ return SETTINGS.env_name in ('GAE_PRODUCTION', 'GAE_LOCAL')
1016
+
1017
+ try:
1018
+ import google.appengine # noqa: unused import
1019
+ except ImportError:
1020
+ pass
1021
+ else:
1022
+ server_software = os.environ.get(_SERVER_SOFTWARE, '')
1023
+ if server_software.startswith('Google App Engine/'):
1024
+ SETTINGS.env_name = 'GAE_PRODUCTION'
1025
+ return True
1026
+ elif server_software.startswith('Development/'):
1027
+ SETTINGS.env_name = 'GAE_LOCAL'
1028
+ return True
1029
+
1030
+ return False
1031
+
1032
+
1033
+ def _in_gce_environment():
1034
+ """Detect if the code is running in the Compute Engine environment.
1035
+
1036
+ Returns:
1037
+ True if running in the GCE environment, False otherwise.
1038
+ """
1039
+ if SETTINGS.env_name is not None:
1040
+ return SETTINGS.env_name == 'GCE_PRODUCTION'
1041
+
1042
+ if NO_GCE_CHECK != 'True' and _detect_gce_environment():
1043
+ SETTINGS.env_name = 'GCE_PRODUCTION'
1044
+ return True
1045
+ return False
1046
+
1047
+
1048
+ class GoogleCredentials(OAuth2Credentials):
1049
+ """Application Default Credentials for use in calling Google APIs.
1050
+
1051
+ The Application Default Credentials are being constructed as a function of
1052
+ the environment where the code is being run.
1053
+ More details can be found on this page:
1054
+ https://developers.google.com/accounts/docs/application-default-credentials
1055
+
1056
+ Here is an example of how to use the Application Default Credentials for a
1057
+ service that requires authentication::
1058
+
1059
+ from googleapiclient.discovery import build
1060
+ from oauth2client.client import GoogleCredentials
1061
+
1062
+ credentials = GoogleCredentials.get_application_default()
1063
+ service = build('compute', 'v1', credentials=credentials)
1064
+
1065
+ PROJECT = 'bamboo-machine-422'
1066
+ ZONE = 'us-central1-a'
1067
+ request = service.instances().list(project=PROJECT, zone=ZONE)
1068
+ response = request.execute()
1069
+
1070
+ print(response)
1071
+ """
1072
+
1073
+ NON_SERIALIZED_MEMBERS = (
1074
+ frozenset(['_private_key']) |
1075
+ OAuth2Credentials.NON_SERIALIZED_MEMBERS)
1076
+ """Members that aren't serialized when object is converted to JSON."""
1077
+
1078
+ def __init__(self, access_token, client_id, client_secret, refresh_token,
1079
+ token_expiry, token_uri, user_agent,
1080
+ revoke_uri=oauth2client.GOOGLE_REVOKE_URI):
1081
+ """Create an instance of GoogleCredentials.
1082
+
1083
+ This constructor is not usually called by the user, instead
1084
+ GoogleCredentials objects are instantiated by
1085
+ GoogleCredentials.from_stream() or
1086
+ GoogleCredentials.get_application_default().
1087
+
1088
+ Args:
1089
+ access_token: string, access token.
1090
+ client_id: string, client identifier.
1091
+ client_secret: string, client secret.
1092
+ refresh_token: string, refresh token.
1093
+ token_expiry: datetime, when the access_token expires.
1094
+ token_uri: string, URI of token endpoint.
1095
+ user_agent: string, The HTTP User-Agent to provide for this
1096
+ application.
1097
+ revoke_uri: string, URI for revoke endpoint. Defaults to
1098
+ oauth2client.GOOGLE_REVOKE_URI; a token can't be
1099
+ revoked if this is None.
1100
+ """
1101
+ super(GoogleCredentials, self).__init__(
1102
+ access_token, client_id, client_secret, refresh_token,
1103
+ token_expiry, token_uri, user_agent, revoke_uri=revoke_uri)
1104
+
1105
+ def create_scoped_required(self):
1106
+ """Whether this Credentials object is scopeless.
1107
+
1108
+ create_scoped(scopes) method needs to be called in order to create
1109
+ a Credentials object for API calls.
1110
+ """
1111
+ return False
1112
+
1113
+ def create_scoped(self, scopes):
1114
+ """Create a Credentials object for the given scopes.
1115
+
1116
+ The Credentials type is preserved.
1117
+ """
1118
+ return self
1119
+
1120
+ @classmethod
1121
+ def from_json(cls, json_data):
1122
+ # TODO(issue 388): eliminate the circularity that is the reason for
1123
+ # this non-top-level import.
1124
+ from oauth2client import service_account
1125
+ data = json.loads(_helpers._from_bytes(json_data))
1126
+
1127
+ # We handle service_account.ServiceAccountCredentials since it is a
1128
+ # possible return type of GoogleCredentials.get_application_default()
1129
+ if (data['_module'] == 'oauth2client.service_account' and
1130
+ data['_class'] == 'ServiceAccountCredentials'):
1131
+ return service_account.ServiceAccountCredentials.from_json(data)
1132
+ elif (data['_module'] == 'oauth2client.service_account' and
1133
+ data['_class'] == '_JWTAccessCredentials'):
1134
+ return service_account._JWTAccessCredentials.from_json(data)
1135
+
1136
+ token_expiry = _parse_expiry(data.get('token_expiry'))
1137
+ google_credentials = cls(
1138
+ data['access_token'],
1139
+ data['client_id'],
1140
+ data['client_secret'],
1141
+ data['refresh_token'],
1142
+ token_expiry,
1143
+ data['token_uri'],
1144
+ data['user_agent'],
1145
+ revoke_uri=data.get('revoke_uri', None))
1146
+ google_credentials.invalid = data['invalid']
1147
+ return google_credentials
1148
+
1149
+ @property
1150
+ def serialization_data(self):
1151
+ """Get the fields and values identifying the current credentials."""
1152
+ return {
1153
+ 'type': 'authorized_user',
1154
+ 'client_id': self.client_id,
1155
+ 'client_secret': self.client_secret,
1156
+ 'refresh_token': self.refresh_token
1157
+ }
1158
+
1159
+ @staticmethod
1160
+ def _implicit_credentials_from_gae():
1161
+ """Attempts to get implicit credentials in Google App Engine env.
1162
+
1163
+ If the current environment is not detected as App Engine, returns None,
1164
+ indicating no Google App Engine credentials can be detected from the
1165
+ current environment.
1166
+
1167
+ Returns:
1168
+ None, if not in GAE, else an appengine.AppAssertionCredentials
1169
+ object.
1170
+ """
1171
+ if not _in_gae_environment():
1172
+ return None
1173
+
1174
+ return _get_application_default_credential_GAE()
1175
+
1176
+ @staticmethod
1177
+ def _implicit_credentials_from_gce():
1178
+ """Attempts to get implicit credentials in Google Compute Engine env.
1179
+
1180
+ If the current environment is not detected as Compute Engine, returns
1181
+ None, indicating no Google Compute Engine credentials can be detected
1182
+ from the current environment.
1183
+
1184
+ Returns:
1185
+ None, if not in GCE, else a gce.AppAssertionCredentials object.
1186
+ """
1187
+ if not _in_gce_environment():
1188
+ return None
1189
+
1190
+ return _get_application_default_credential_GCE()
1191
+
1192
+ @staticmethod
1193
+ def _implicit_credentials_from_files():
1194
+ """Attempts to get implicit credentials from local credential files.
1195
+
1196
+ First checks if the environment variable GOOGLE_APPLICATION_CREDENTIALS
1197
+ is set with a filename and then falls back to a configuration file (the
1198
+ "well known" file) associated with the 'gcloud' command line tool.
1199
+
1200
+ Returns:
1201
+ Credentials object associated with the
1202
+ GOOGLE_APPLICATION_CREDENTIALS file or the "well known" file if
1203
+ either exist. If neither file is define, returns None, indicating
1204
+ no credentials from a file can detected from the current
1205
+ environment.
1206
+ """
1207
+ credentials_filename = _get_environment_variable_file()
1208
+ if not credentials_filename:
1209
+ credentials_filename = _get_well_known_file()
1210
+ if os.path.isfile(credentials_filename):
1211
+ extra_help = (' (produced automatically when running'
1212
+ ' "gcloud auth login" command)')
1213
+ else:
1214
+ credentials_filename = None
1215
+ else:
1216
+ extra_help = (' (pointed to by ' + GOOGLE_APPLICATION_CREDENTIALS +
1217
+ ' environment variable)')
1218
+
1219
+ if not credentials_filename:
1220
+ return
1221
+
1222
+ # If we can read the credentials from a file, we don't need to know
1223
+ # what environment we are in.
1224
+ SETTINGS.env_name = DEFAULT_ENV_NAME
1225
+
1226
+ try:
1227
+ return _get_application_default_credential_from_file(
1228
+ credentials_filename)
1229
+ except (ApplicationDefaultCredentialsError, ValueError) as error:
1230
+ _raise_exception_for_reading_json(credentials_filename,
1231
+ extra_help, error)
1232
+
1233
+ @classmethod
1234
+ def _get_implicit_credentials(cls):
1235
+ """Gets credentials implicitly from the environment.
1236
+
1237
+ Checks environment in order of precedence:
1238
+ - Environment variable GOOGLE_APPLICATION_CREDENTIALS pointing to
1239
+ a file with stored credentials information.
1240
+ - Stored "well known" file associated with `gcloud` command line tool.
1241
+ - Google App Engine (production and testing)
1242
+ - Google Compute Engine production environment.
1243
+
1244
+ Raises:
1245
+ ApplicationDefaultCredentialsError: raised when the credentials
1246
+ fail to be retrieved.
1247
+ """
1248
+ # Environ checks (in order).
1249
+ environ_checkers = [
1250
+ cls._implicit_credentials_from_files,
1251
+ cls._implicit_credentials_from_gae,
1252
+ cls._implicit_credentials_from_gce,
1253
+ ]
1254
+
1255
+ for checker in environ_checkers:
1256
+ credentials = checker()
1257
+ if credentials is not None:
1258
+ return credentials
1259
+
1260
+ # If no credentials, fail.
1261
+ raise ApplicationDefaultCredentialsError(ADC_HELP_MSG)
1262
+
1263
+ @staticmethod
1264
+ def get_application_default():
1265
+ """Get the Application Default Credentials for the current environment.
1266
+
1267
+ Raises:
1268
+ ApplicationDefaultCredentialsError: raised when the credentials
1269
+ fail to be retrieved.
1270
+ """
1271
+ return GoogleCredentials._get_implicit_credentials()
1272
+
1273
+ @staticmethod
1274
+ def from_stream(credential_filename):
1275
+ """Create a Credentials object by reading information from a file.
1276
+
1277
+ It returns an object of type GoogleCredentials.
1278
+
1279
+ Args:
1280
+ credential_filename: the path to the file from where the
1281
+ credentials are to be read
1282
+
1283
+ Raises:
1284
+ ApplicationDefaultCredentialsError: raised when the credentials
1285
+ fail to be retrieved.
1286
+ """
1287
+ if credential_filename and os.path.isfile(credential_filename):
1288
+ try:
1289
+ return _get_application_default_credential_from_file(
1290
+ credential_filename)
1291
+ except (ApplicationDefaultCredentialsError, ValueError) as error:
1292
+ extra_help = (' (provided as parameter to the '
1293
+ 'from_stream() method)')
1294
+ _raise_exception_for_reading_json(credential_filename,
1295
+ extra_help,
1296
+ error)
1297
+ else:
1298
+ raise ApplicationDefaultCredentialsError(
1299
+ 'The parameter passed to the from_stream() '
1300
+ 'method should point to a file.')
1301
+
1302
+
1303
+ def _save_private_file(filename, json_contents):
1304
+ """Saves a file with read-write permissions on for the owner.
1305
+
1306
+ Args:
1307
+ filename: String. Absolute path to file.
1308
+ json_contents: JSON serializable object to be saved.
1309
+ """
1310
+ temp_filename = tempfile.mktemp()
1311
+ file_desc = os.open(temp_filename, os.O_WRONLY | os.O_CREAT, 0o600)
1312
+ with os.fdopen(file_desc, 'w') as file_handle:
1313
+ json.dump(json_contents, file_handle, sort_keys=True,
1314
+ indent=2, separators=(',', ': '))
1315
+ shutil.move(temp_filename, filename)
1316
+
1317
+
1318
+ def save_to_well_known_file(credentials, well_known_file=None):
1319
+ """Save the provided GoogleCredentials to the well known file.
1320
+
1321
+ Args:
1322
+ credentials: the credentials to be saved to the well known file;
1323
+ it should be an instance of GoogleCredentials
1324
+ well_known_file: the name of the file where the credentials are to be
1325
+ saved; this parameter is supposed to be used for
1326
+ testing only
1327
+ """
1328
+ # TODO(orestica): move this method to tools.py
1329
+ # once the argparse import gets fixed (it is not present in Python 2.6)
1330
+
1331
+ if well_known_file is None:
1332
+ well_known_file = _get_well_known_file()
1333
+
1334
+ config_dir = os.path.dirname(well_known_file)
1335
+ if not os.path.isdir(config_dir):
1336
+ raise OSError(
1337
+ 'Config directory does not exist: {0}'.format(config_dir))
1338
+
1339
+ credentials_data = credentials.serialization_data
1340
+ _save_private_file(well_known_file, credentials_data)
1341
+
1342
+
1343
+ def _get_environment_variable_file():
1344
+ application_default_credential_filename = (
1345
+ os.environ.get(GOOGLE_APPLICATION_CREDENTIALS, None))
1346
+
1347
+ if application_default_credential_filename:
1348
+ if os.path.isfile(application_default_credential_filename):
1349
+ return application_default_credential_filename
1350
+ else:
1351
+ raise ApplicationDefaultCredentialsError(
1352
+ 'File ' + application_default_credential_filename +
1353
+ ' (pointed by ' +
1354
+ GOOGLE_APPLICATION_CREDENTIALS +
1355
+ ' environment variable) does not exist!')
1356
+
1357
+
1358
+ def _get_well_known_file():
1359
+ """Get the well known file produced by command 'gcloud auth login'."""
1360
+ # TODO(orestica): Revisit this method once gcloud provides a better way
1361
+ # of pinpointing the exact location of the file.
1362
+ default_config_dir = os.getenv(_CLOUDSDK_CONFIG_ENV_VAR)
1363
+ if default_config_dir is None:
1364
+ if os.name == 'nt':
1365
+ try:
1366
+ default_config_dir = os.path.join(os.environ['APPDATA'],
1367
+ _CLOUDSDK_CONFIG_DIRECTORY)
1368
+ except KeyError:
1369
+ # This should never happen unless someone is really
1370
+ # messing with things.
1371
+ drive = os.environ.get('SystemDrive', 'C:')
1372
+ default_config_dir = os.path.join(drive, '\\',
1373
+ _CLOUDSDK_CONFIG_DIRECTORY)
1374
+ else:
1375
+ default_config_dir = os.path.join(os.path.expanduser('~'),
1376
+ '.config',
1377
+ _CLOUDSDK_CONFIG_DIRECTORY)
1378
+
1379
+ return os.path.join(default_config_dir, _WELL_KNOWN_CREDENTIALS_FILE)
1380
+
1381
+
1382
+ def _get_application_default_credential_from_file(filename):
1383
+ """Build the Application Default Credentials from file."""
1384
+ # read the credentials from the file
1385
+ with open(filename) as file_obj:
1386
+ client_credentials = json.load(file_obj)
1387
+
1388
+ credentials_type = client_credentials.get('type')
1389
+ if credentials_type == AUTHORIZED_USER:
1390
+ required_fields = set(['client_id', 'client_secret', 'refresh_token'])
1391
+ elif credentials_type == SERVICE_ACCOUNT:
1392
+ required_fields = set(['client_id', 'client_email', 'private_key_id',
1393
+ 'private_key'])
1394
+ else:
1395
+ raise ApplicationDefaultCredentialsError(
1396
+ "'type' field should be defined (and have one of the '" +
1397
+ AUTHORIZED_USER + "' or '" + SERVICE_ACCOUNT + "' values)")
1398
+
1399
+ missing_fields = required_fields.difference(client_credentials.keys())
1400
+
1401
+ if missing_fields:
1402
+ _raise_exception_for_missing_fields(missing_fields)
1403
+
1404
+ if client_credentials['type'] == AUTHORIZED_USER:
1405
+ return GoogleCredentials(
1406
+ access_token=None,
1407
+ client_id=client_credentials['client_id'],
1408
+ client_secret=client_credentials['client_secret'],
1409
+ refresh_token=client_credentials['refresh_token'],
1410
+ token_expiry=None,
1411
+ token_uri=oauth2client.GOOGLE_TOKEN_URI,
1412
+ user_agent='Python client library')
1413
+ else: # client_credentials['type'] == SERVICE_ACCOUNT
1414
+ from oauth2client import service_account
1415
+ return service_account._JWTAccessCredentials.from_json_keyfile_dict(
1416
+ client_credentials)
1417
+
1418
+
1419
+ def _raise_exception_for_missing_fields(missing_fields):
1420
+ raise ApplicationDefaultCredentialsError(
1421
+ 'The following field(s) must be defined: ' + ', '.join(missing_fields))
1422
+
1423
+
1424
+ def _raise_exception_for_reading_json(credential_file,
1425
+ extra_help,
1426
+ error):
1427
+ raise ApplicationDefaultCredentialsError(
1428
+ 'An error was encountered while reading json file: ' +
1429
+ credential_file + extra_help + ': ' + str(error))
1430
+
1431
+
1432
+ def _get_application_default_credential_GAE():
1433
+ from oauth2client.contrib.appengine import AppAssertionCredentials
1434
+
1435
+ return AppAssertionCredentials([])
1436
+
1437
+
1438
+ def _get_application_default_credential_GCE():
1439
+ from oauth2client.contrib.gce import AppAssertionCredentials
1440
+
1441
+ return AppAssertionCredentials()
1442
+
1443
+
1444
+ class AssertionCredentials(GoogleCredentials):
1445
+ """Abstract Credentials object used for OAuth 2.0 assertion grants.
1446
+
1447
+ This credential does not require a flow to instantiate because it
1448
+ represents a two legged flow, and therefore has all of the required
1449
+ information to generate and refresh its own access tokens. It must
1450
+ be subclassed to generate the appropriate assertion string.
1451
+
1452
+ AssertionCredentials objects may be safely pickled and unpickled.
1453
+ """
1454
+
1455
+ @_helpers.positional(2)
1456
+ def __init__(self, assertion_type, user_agent=None,
1457
+ token_uri=oauth2client.GOOGLE_TOKEN_URI,
1458
+ revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
1459
+ **unused_kwargs):
1460
+ """Constructor for AssertionFlowCredentials.
1461
+
1462
+ Args:
1463
+ assertion_type: string, assertion type that will be declared to the
1464
+ auth server
1465
+ user_agent: string, The HTTP User-Agent to provide for this
1466
+ application.
1467
+ token_uri: string, URI for token endpoint. For convenience defaults
1468
+ to Google's endpoints but any OAuth 2.0 provider can be
1469
+ used.
1470
+ revoke_uri: string, URI for revoke endpoint.
1471
+ """
1472
+ super(AssertionCredentials, self).__init__(
1473
+ None,
1474
+ None,
1475
+ None,
1476
+ None,
1477
+ None,
1478
+ token_uri,
1479
+ user_agent,
1480
+ revoke_uri=revoke_uri)
1481
+ self.assertion_type = assertion_type
1482
+
1483
+ def _generate_refresh_request_body(self):
1484
+ assertion = self._generate_assertion()
1485
+
1486
+ body = urllib.parse.urlencode({
1487
+ 'assertion': assertion,
1488
+ 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
1489
+ })
1490
+
1491
+ return body
1492
+
1493
+ def _generate_assertion(self):
1494
+ """Generate assertion string to be used in the access token request."""
1495
+ raise NotImplementedError
1496
+
1497
+ def _revoke(self, http):
1498
+ """Revokes the access_token and deletes the store if available.
1499
+
1500
+ Args:
1501
+ http: an object to be used to make HTTP requests.
1502
+ """
1503
+ self._do_revoke(http, self.access_token)
1504
+
1505
+ def sign_blob(self, blob):
1506
+ """Cryptographically sign a blob (of bytes).
1507
+
1508
+ Args:
1509
+ blob: bytes, Message to be signed.
1510
+
1511
+ Returns:
1512
+ tuple, A pair of the private key ID used to sign the blob and
1513
+ the signed contents.
1514
+ """
1515
+ raise NotImplementedError('This method is abstract.')
1516
+
1517
+
1518
+ def _require_crypto_or_die():
1519
+ """Ensure we have a crypto library, or throw CryptoUnavailableError.
1520
+
1521
+ The oauth2client.crypt module requires either PyCrypto or PyOpenSSL
1522
+ to be available in order to function, but these are optional
1523
+ dependencies.
1524
+ """
1525
+ if not HAS_CRYPTO:
1526
+ raise CryptoUnavailableError('No crypto library available')
1527
+
1528
+
1529
+ @_helpers.positional(2)
1530
+ def verify_id_token(id_token, audience, http=None,
1531
+ cert_uri=ID_TOKEN_VERIFICATION_CERTS):
1532
+ """Verifies a signed JWT id_token.
1533
+
1534
+ This function requires PyOpenSSL and because of that it does not work on
1535
+ App Engine.
1536
+
1537
+ Args:
1538
+ id_token: string, A Signed JWT.
1539
+ audience: string, The audience 'aud' that the token should be for.
1540
+ http: httplib2.Http, instance to use to make the HTTP request. Callers
1541
+ should supply an instance that has caching enabled.
1542
+ cert_uri: string, URI of the certificates in JSON format to
1543
+ verify the JWT against.
1544
+
1545
+ Returns:
1546
+ The deserialized JSON in the JWT.
1547
+
1548
+ Raises:
1549
+ oauth2client.crypt.AppIdentityError: if the JWT fails to verify.
1550
+ CryptoUnavailableError: if no crypto library is available.
1551
+ """
1552
+ _require_crypto_or_die()
1553
+ if http is None:
1554
+ http = transport.get_cached_http()
1555
+
1556
+ resp, content = transport.request(http, cert_uri)
1557
+ if resp.status == http_client.OK:
1558
+ certs = json.loads(_helpers._from_bytes(content))
1559
+ return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
1560
+ else:
1561
+ raise VerifyJwtTokenError('Status code: {0}'.format(resp.status))
1562
+
1563
+
1564
+ def _extract_id_token(id_token):
1565
+ """Extract the JSON payload from a JWT.
1566
+
1567
+ Does the extraction w/o checking the signature.
1568
+
1569
+ Args:
1570
+ id_token: string or bytestring, OAuth 2.0 id_token.
1571
+
1572
+ Returns:
1573
+ object, The deserialized JSON payload.
1574
+ """
1575
+ if type(id_token) == bytes:
1576
+ segments = id_token.split(b'.')
1577
+ else:
1578
+ segments = id_token.split(u'.')
1579
+
1580
+ if len(segments) != 3:
1581
+ raise VerifyJwtTokenError(
1582
+ 'Wrong number of segments in token: {0}'.format(id_token))
1583
+
1584
+ return json.loads(
1585
+ _helpers._from_bytes(_helpers._urlsafe_b64decode(segments[1])))
1586
+
1587
+
1588
+ def _parse_exchange_token_response(content):
1589
+ """Parses response of an exchange token request.
1590
+
1591
+ Most providers return JSON but some (e.g. Facebook) return a
1592
+ url-encoded string.
1593
+
1594
+ Args:
1595
+ content: The body of a response
1596
+
1597
+ Returns:
1598
+ Content as a dictionary object. Note that the dict could be empty,
1599
+ i.e. {}. That basically indicates a failure.
1600
+ """
1601
+ resp = {}
1602
+ content = _helpers._from_bytes(content)
1603
+ try:
1604
+ resp = json.loads(content)
1605
+ except Exception:
1606
+ # different JSON libs raise different exceptions,
1607
+ # so we just do a catch-all here
1608
+ resp = _helpers.parse_unique_urlencoded(content)
1609
+
1610
+ # some providers respond with 'expires', others with 'expires_in'
1611
+ if resp and 'expires' in resp:
1612
+ resp['expires_in'] = resp.pop('expires')
1613
+
1614
+ return resp
1615
+
1616
+
1617
+ @_helpers.positional(4)
1618
+ def credentials_from_code(client_id, client_secret, scope, code,
1619
+ redirect_uri='postmessage', http=None,
1620
+ user_agent=None,
1621
+ token_uri=oauth2client.GOOGLE_TOKEN_URI,
1622
+ auth_uri=oauth2client.GOOGLE_AUTH_URI,
1623
+ revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
1624
+ device_uri=oauth2client.GOOGLE_DEVICE_URI,
1625
+ token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
1626
+ pkce=False,
1627
+ code_verifier=None):
1628
+ """Exchanges an authorization code for an OAuth2Credentials object.
1629
+
1630
+ Args:
1631
+ client_id: string, client identifier.
1632
+ client_secret: string, client secret.
1633
+ scope: string or iterable of strings, scope(s) to request.
1634
+ code: string, An authorization code, most likely passed down from
1635
+ the client
1636
+ redirect_uri: string, this is generally set to 'postmessage' to match
1637
+ the redirect_uri that the client specified
1638
+ http: httplib2.Http, optional http instance to use to do the fetch
1639
+ token_uri: string, URI for token endpoint. For convenience defaults
1640
+ to Google's endpoints but any OAuth 2.0 provider can be
1641
+ used.
1642
+ auth_uri: string, URI for authorization endpoint. For convenience
1643
+ defaults to Google's endpoints but any OAuth 2.0 provider
1644
+ can be used.
1645
+ revoke_uri: string, URI for revoke endpoint. For convenience
1646
+ defaults to Google's endpoints but any OAuth 2.0 provider
1647
+ can be used.
1648
+ device_uri: string, URI for device authorization endpoint. For
1649
+ convenience defaults to Google's endpoints but any OAuth
1650
+ 2.0 provider can be used.
1651
+ pkce: boolean, default: False, Generate and include a "Proof Key
1652
+ for Code Exchange" (PKCE) with your authorization and token
1653
+ requests. This adds security for installed applications that
1654
+ cannot protect a client_secret. See RFC 7636 for details.
1655
+ code_verifier: bytestring or None, default: None, parameter passed
1656
+ as part of the code exchange when pkce=True. If
1657
+ None, a code_verifier will automatically be
1658
+ generated as part of step1_get_authorize_url(). See
1659
+ RFC 7636 for details.
1660
+
1661
+ Returns:
1662
+ An OAuth2Credentials object.
1663
+
1664
+ Raises:
1665
+ FlowExchangeError if the authorization code cannot be exchanged for an
1666
+ access token
1667
+ """
1668
+ flow = OAuth2WebServerFlow(client_id, client_secret, scope,
1669
+ redirect_uri=redirect_uri,
1670
+ user_agent=user_agent,
1671
+ auth_uri=auth_uri,
1672
+ token_uri=token_uri,
1673
+ revoke_uri=revoke_uri,
1674
+ device_uri=device_uri,
1675
+ token_info_uri=token_info_uri,
1676
+ pkce=pkce,
1677
+ code_verifier=code_verifier)
1678
+
1679
+ credentials = flow.step2_exchange(code, http=http)
1680
+ return credentials
1681
+
1682
+
1683
+ @_helpers.positional(3)
1684
+ def credentials_from_clientsecrets_and_code(filename, scope, code,
1685
+ message=None,
1686
+ redirect_uri='postmessage',
1687
+ http=None,
1688
+ cache=None,
1689
+ device_uri=None):
1690
+ """Returns OAuth2Credentials from a clientsecrets file and an auth code.
1691
+
1692
+ Will create the right kind of Flow based on the contents of the
1693
+ clientsecrets file or will raise InvalidClientSecretsError for unknown
1694
+ types of Flows.
1695
+
1696
+ Args:
1697
+ filename: string, File name of clientsecrets.
1698
+ scope: string or iterable of strings, scope(s) to request.
1699
+ code: string, An authorization code, most likely passed down from
1700
+ the client
1701
+ message: string, A friendly string to display to the user if the
1702
+ clientsecrets file is missing or invalid. If message is
1703
+ provided then sys.exit will be called in the case of an error.
1704
+ If message in not provided then
1705
+ clientsecrets.InvalidClientSecretsError will be raised.
1706
+ redirect_uri: string, this is generally set to 'postmessage' to match
1707
+ the redirect_uri that the client specified
1708
+ http: httplib2.Http, optional http instance to use to do the fetch
1709
+ cache: An optional cache service client that implements get() and set()
1710
+ methods. See clientsecrets.loadfile() for details.
1711
+ device_uri: string, OAuth 2.0 device authorization endpoint
1712
+ pkce: boolean, default: False, Generate and include a "Proof Key
1713
+ for Code Exchange" (PKCE) with your authorization and token
1714
+ requests. This adds security for installed applications that
1715
+ cannot protect a client_secret. See RFC 7636 for details.
1716
+ code_verifier: bytestring or None, default: None, parameter passed
1717
+ as part of the code exchange when pkce=True. If
1718
+ None, a code_verifier will automatically be
1719
+ generated as part of step1_get_authorize_url(). See
1720
+ RFC 7636 for details.
1721
+
1722
+ Returns:
1723
+ An OAuth2Credentials object.
1724
+
1725
+ Raises:
1726
+ FlowExchangeError: if the authorization code cannot be exchanged for an
1727
+ access token
1728
+ UnknownClientSecretsFlowError: if the file describes an unknown kind
1729
+ of Flow.
1730
+ clientsecrets.InvalidClientSecretsError: if the clientsecrets file is
1731
+ invalid.
1732
+ """
1733
+ flow = flow_from_clientsecrets(filename, scope, message=message,
1734
+ cache=cache, redirect_uri=redirect_uri,
1735
+ device_uri=device_uri)
1736
+ credentials = flow.step2_exchange(code, http=http)
1737
+ return credentials
1738
+
1739
+
1740
+ class DeviceFlowInfo(collections.namedtuple('DeviceFlowInfo', (
1741
+ 'device_code', 'user_code', 'interval', 'verification_url',
1742
+ 'user_code_expiry'))):
1743
+ """Intermediate information the OAuth2 for devices flow."""
1744
+
1745
+ @classmethod
1746
+ def FromResponse(cls, response):
1747
+ """Create a DeviceFlowInfo from a server response.
1748
+
1749
+ The response should be a dict containing entries as described here:
1750
+
1751
+ http://tools.ietf.org/html/draft-ietf-oauth-v2-05#section-3.7.1
1752
+ """
1753
+ # device_code, user_code, and verification_url are required.
1754
+ kwargs = {
1755
+ 'device_code': response['device_code'],
1756
+ 'user_code': response['user_code'],
1757
+ }
1758
+ # The response may list the verification address as either
1759
+ # verification_url or verification_uri, so we check for both.
1760
+ verification_url = response.get(
1761
+ 'verification_url', response.get('verification_uri'))
1762
+ if verification_url is None:
1763
+ raise OAuth2DeviceCodeError(
1764
+ 'No verification_url provided in server response')
1765
+ kwargs['verification_url'] = verification_url
1766
+ # expires_in and interval are optional.
1767
+ kwargs.update({
1768
+ 'interval': response.get('interval'),
1769
+ 'user_code_expiry': None,
1770
+ })
1771
+ if 'expires_in' in response:
1772
+ kwargs['user_code_expiry'] = (
1773
+ _UTCNOW() +
1774
+ datetime.timedelta(seconds=int(response['expires_in'])))
1775
+ return cls(**kwargs)
1776
+
1777
+
1778
+ def _oauth2_web_server_flow_params(kwargs):
1779
+ """Configures redirect URI parameters for OAuth2WebServerFlow."""
1780
+ params = {
1781
+ 'access_type': 'offline',
1782
+ 'response_type': 'code',
1783
+ }
1784
+
1785
+ params.update(kwargs)
1786
+
1787
+ # Check for the presence of the deprecated approval_prompt param and
1788
+ # warn appropriately.
1789
+ approval_prompt = params.get('approval_prompt')
1790
+ if approval_prompt is not None:
1791
+ logger.warning(
1792
+ 'The approval_prompt parameter for OAuth2WebServerFlow is '
1793
+ 'deprecated. Please use the prompt parameter instead.')
1794
+
1795
+ if approval_prompt == 'force':
1796
+ logger.warning(
1797
+ 'approval_prompt="force" has been adjusted to '
1798
+ 'prompt="consent"')
1799
+ params['prompt'] = 'consent'
1800
+ del params['approval_prompt']
1801
+
1802
+ return params
1803
+
1804
+
1805
+ class OAuth2WebServerFlow(Flow):
1806
+ """Does the Web Server Flow for OAuth 2.0.
1807
+
1808
+ OAuth2WebServerFlow objects may be safely pickled and unpickled.
1809
+ """
1810
+
1811
+ @_helpers.positional(4)
1812
+ def __init__(self, client_id,
1813
+ client_secret=None,
1814
+ scope=None,
1815
+ redirect_uri=None,
1816
+ user_agent=None,
1817
+ auth_uri=oauth2client.GOOGLE_AUTH_URI,
1818
+ token_uri=oauth2client.GOOGLE_TOKEN_URI,
1819
+ revoke_uri=oauth2client.GOOGLE_REVOKE_URI,
1820
+ login_hint=None,
1821
+ device_uri=oauth2client.GOOGLE_DEVICE_URI,
1822
+ token_info_uri=oauth2client.GOOGLE_TOKEN_INFO_URI,
1823
+ authorization_header=None,
1824
+ pkce=False,
1825
+ code_verifier=None,
1826
+ **kwargs):
1827
+ """Constructor for OAuth2WebServerFlow.
1828
+
1829
+ The kwargs argument is used to set extra query parameters on the
1830
+ auth_uri. For example, the access_type and prompt
1831
+ query parameters can be set via kwargs.
1832
+
1833
+ Args:
1834
+ client_id: string, client identifier.
1835
+ client_secret: string client secret.
1836
+ scope: string or iterable of strings, scope(s) of the credentials
1837
+ being requested.
1838
+ redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob'
1839
+ for a non-web-based application, or a URI that
1840
+ handles the callback from the authorization server.
1841
+ user_agent: string, HTTP User-Agent to provide for this
1842
+ application.
1843
+ auth_uri: string, URI for authorization endpoint. For convenience
1844
+ defaults to Google's endpoints but any OAuth 2.0 provider
1845
+ can be used.
1846
+ token_uri: string, URI for token endpoint. For convenience
1847
+ defaults to Google's endpoints but any OAuth 2.0
1848
+ provider can be used.
1849
+ revoke_uri: string, URI for revoke endpoint. For convenience
1850
+ defaults to Google's endpoints but any OAuth 2.0
1851
+ provider can be used.
1852
+ login_hint: string, Either an email address or domain. Passing this
1853
+ hint will either pre-fill the email box on the sign-in
1854
+ form or select the proper multi-login session, thereby
1855
+ simplifying the login flow.
1856
+ device_uri: string, URI for device authorization endpoint. For
1857
+ convenience defaults to Google's endpoints but any
1858
+ OAuth 2.0 provider can be used.
1859
+ authorization_header: string, For use with OAuth 2.0 providers that
1860
+ require a client to authenticate using a
1861
+ header value instead of passing client_secret
1862
+ in the POST body.
1863
+ pkce: boolean, default: False, Generate and include a "Proof Key
1864
+ for Code Exchange" (PKCE) with your authorization and token
1865
+ requests. This adds security for installed applications that
1866
+ cannot protect a client_secret. See RFC 7636 for details.
1867
+ code_verifier: bytestring or None, default: None, parameter passed
1868
+ as part of the code exchange when pkce=True. If
1869
+ None, a code_verifier will automatically be
1870
+ generated as part of step1_get_authorize_url(). See
1871
+ RFC 7636 for details.
1872
+ **kwargs: dict, The keyword arguments are all optional and required
1873
+ parameters for the OAuth calls.
1874
+ """
1875
+ # scope is a required argument, but to preserve backwards-compatibility
1876
+ # we don't want to rearrange the positional arguments
1877
+ if scope is None:
1878
+ raise TypeError("The value of scope must not be None")
1879
+ self.client_id = client_id
1880
+ self.client_secret = client_secret
1881
+ self.scope = _helpers.scopes_to_string(scope)
1882
+ self.redirect_uri = redirect_uri
1883
+ self.login_hint = login_hint
1884
+ self.user_agent = user_agent
1885
+ self.auth_uri = auth_uri
1886
+ self.token_uri = token_uri
1887
+ self.revoke_uri = revoke_uri
1888
+ self.device_uri = device_uri
1889
+ self.token_info_uri = token_info_uri
1890
+ self.authorization_header = authorization_header
1891
+ self._pkce = pkce
1892
+ self.code_verifier = code_verifier
1893
+ self.params = _oauth2_web_server_flow_params(kwargs)
1894
+
1895
+ @_helpers.positional(1)
1896
+ def step1_get_authorize_url(self, redirect_uri=None, state=None):
1897
+ """Returns a URI to redirect to the provider.
1898
+
1899
+ Args:
1900
+ redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob'
1901
+ for a non-web-based application, or a URI that
1902
+ handles the callback from the authorization server.
1903
+ This parameter is deprecated, please move to passing
1904
+ the redirect_uri in via the constructor.
1905
+ state: string, Opaque state string which is passed through the
1906
+ OAuth2 flow and returned to the client as a query parameter
1907
+ in the callback.
1908
+
1909
+ Returns:
1910
+ A URI as a string to redirect the user to begin the authorization
1911
+ flow.
1912
+ """
1913
+ if redirect_uri is not None:
1914
+ logger.warning((
1915
+ 'The redirect_uri parameter for '
1916
+ 'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. '
1917
+ 'Please move to passing the redirect_uri in via the '
1918
+ 'constructor.'))
1919
+ self.redirect_uri = redirect_uri
1920
+
1921
+ if self.redirect_uri is None:
1922
+ raise ValueError('The value of redirect_uri must not be None.')
1923
+
1924
+ query_params = {
1925
+ 'client_id': self.client_id,
1926
+ 'redirect_uri': self.redirect_uri,
1927
+ 'scope': self.scope,
1928
+ }
1929
+ if state is not None:
1930
+ query_params['state'] = state
1931
+ if self.login_hint is not None:
1932
+ query_params['login_hint'] = self.login_hint
1933
+ if self._pkce:
1934
+ if not self.code_verifier:
1935
+ self.code_verifier = _pkce.code_verifier()
1936
+ challenge = _pkce.code_challenge(self.code_verifier)
1937
+ query_params['code_challenge'] = challenge
1938
+ query_params['code_challenge_method'] = 'S256'
1939
+
1940
+ query_params.update(self.params)
1941
+ return _helpers.update_query_params(self.auth_uri, query_params)
1942
+
1943
+ @_helpers.positional(1)
1944
+ def step1_get_device_and_user_codes(self, http=None):
1945
+ """Returns a user code and the verification URL where to enter it
1946
+
1947
+ Returns:
1948
+ A user code as a string for the user to authorize the application
1949
+ An URL as a string where the user has to enter the code
1950
+ """
1951
+ if self.device_uri is None:
1952
+ raise ValueError('The value of device_uri must not be None.')
1953
+
1954
+ body = urllib.parse.urlencode({
1955
+ 'client_id': self.client_id,
1956
+ 'scope': self.scope,
1957
+ })
1958
+ headers = {
1959
+ 'content-type': 'application/x-www-form-urlencoded',
1960
+ }
1961
+
1962
+ if self.user_agent is not None:
1963
+ headers['user-agent'] = self.user_agent
1964
+
1965
+ if http is None:
1966
+ http = transport.get_http_object()
1967
+
1968
+ resp, content = transport.request(
1969
+ http, self.device_uri, method='POST', body=body, headers=headers)
1970
+ content = _helpers._from_bytes(content)
1971
+ if resp.status == http_client.OK:
1972
+ try:
1973
+ flow_info = json.loads(content)
1974
+ except ValueError as exc:
1975
+ raise OAuth2DeviceCodeError(
1976
+ 'Could not parse server response as JSON: "{0}", '
1977
+ 'error: "{1}"'.format(content, exc))
1978
+ return DeviceFlowInfo.FromResponse(flow_info)
1979
+ else:
1980
+ error_msg = 'Invalid response {0}.'.format(resp.status)
1981
+ try:
1982
+ error_dict = json.loads(content)
1983
+ if 'error' in error_dict:
1984
+ error_msg += ' Error: {0}'.format(error_dict['error'])
1985
+ except ValueError:
1986
+ # Couldn't decode a JSON response, stick with the
1987
+ # default message.
1988
+ pass
1989
+ raise OAuth2DeviceCodeError(error_msg)
1990
+
1991
+ @_helpers.positional(2)
1992
+ def step2_exchange(self, code=None, http=None, device_flow_info=None):
1993
+ """Exchanges a code for OAuth2Credentials.
1994
+
1995
+ Args:
1996
+ code: string, a dict-like object, or None. For a non-device
1997
+ flow, this is either the response code as a string, or a
1998
+ dictionary of query parameters to the redirect_uri. For a
1999
+ device flow, this should be None.
2000
+ http: httplib2.Http, optional http instance to use when fetching
2001
+ credentials.
2002
+ device_flow_info: DeviceFlowInfo, return value from step1 in the
2003
+ case of a device flow.
2004
+
2005
+ Returns:
2006
+ An OAuth2Credentials object that can be used to authorize requests.
2007
+
2008
+ Raises:
2009
+ FlowExchangeError: if a problem occurred exchanging the code for a
2010
+ refresh_token.
2011
+ ValueError: if code and device_flow_info are both provided or both
2012
+ missing.
2013
+ """
2014
+ if code is None and device_flow_info is None:
2015
+ raise ValueError('No code or device_flow_info provided.')
2016
+ if code is not None and device_flow_info is not None:
2017
+ raise ValueError('Cannot provide both code and device_flow_info.')
2018
+
2019
+ if code is None:
2020
+ code = device_flow_info.device_code
2021
+ elif not isinstance(code, (six.string_types, six.binary_type)):
2022
+ if 'code' not in code:
2023
+ raise FlowExchangeError(code.get(
2024
+ 'error', 'No code was supplied in the query parameters.'))
2025
+ code = code['code']
2026
+
2027
+ post_data = {
2028
+ 'client_id': self.client_id,
2029
+ 'code': code,
2030
+ 'scope': self.scope,
2031
+ }
2032
+ if self.client_secret is not None:
2033
+ post_data['client_secret'] = self.client_secret
2034
+ if self._pkce:
2035
+ post_data['code_verifier'] = self.code_verifier
2036
+ if device_flow_info is not None:
2037
+ post_data['grant_type'] = 'http://oauth.net/grant_type/device/1.0'
2038
+ else:
2039
+ post_data['grant_type'] = 'authorization_code'
2040
+ post_data['redirect_uri'] = self.redirect_uri
2041
+ body = urllib.parse.urlencode(post_data)
2042
+ headers = {
2043
+ 'content-type': 'application/x-www-form-urlencoded',
2044
+ }
2045
+ if self.authorization_header is not None:
2046
+ headers['Authorization'] = self.authorization_header
2047
+ if self.user_agent is not None:
2048
+ headers['user-agent'] = self.user_agent
2049
+
2050
+ if http is None:
2051
+ http = transport.get_http_object()
2052
+
2053
+ resp, content = transport.request(
2054
+ http, self.token_uri, method='POST', body=body, headers=headers)
2055
+ d = _parse_exchange_token_response(content)
2056
+ if resp.status == http_client.OK and 'access_token' in d:
2057
+ access_token = d['access_token']
2058
+ refresh_token = d.get('refresh_token', None)
2059
+ if not refresh_token:
2060
+ logger.info(
2061
+ 'Received token response with no refresh_token. Consider '
2062
+ "reauthenticating with prompt='consent'.")
2063
+ token_expiry = None
2064
+ if 'expires_in' in d:
2065
+ delta = datetime.timedelta(seconds=int(d['expires_in']))
2066
+ token_expiry = delta + _UTCNOW()
2067
+
2068
+ extracted_id_token = None
2069
+ id_token_jwt = None
2070
+ if 'id_token' in d:
2071
+ extracted_id_token = _extract_id_token(d['id_token'])
2072
+ id_token_jwt = d['id_token']
2073
+
2074
+ logger.info('Successfully retrieved access token')
2075
+ return OAuth2Credentials(
2076
+ access_token, self.client_id, self.client_secret,
2077
+ refresh_token, token_expiry, self.token_uri, self.user_agent,
2078
+ revoke_uri=self.revoke_uri, id_token=extracted_id_token,
2079
+ id_token_jwt=id_token_jwt, token_response=d, scopes=self.scope,
2080
+ token_info_uri=self.token_info_uri)
2081
+ else:
2082
+ logger.info('Failed to retrieve access token: %s', content)
2083
+ if 'error' in d:
2084
+ # you never know what those providers got to say
2085
+ error_msg = (str(d['error']) +
2086
+ str(d.get('error_description', '')))
2087
+ else:
2088
+ error_msg = 'Invalid response: {0}.'.format(str(resp.status))
2089
+ raise FlowExchangeError(error_msg)
2090
+
2091
+
2092
+ @_helpers.positional(2)
2093
+ def flow_from_clientsecrets(filename, scope, redirect_uri=None,
2094
+ message=None, cache=None, login_hint=None,
2095
+ device_uri=None, pkce=None, code_verifier=None,
2096
+ prompt=None):
2097
+ """Create a Flow from a clientsecrets file.
2098
+
2099
+ Will create the right kind of Flow based on the contents of the
2100
+ clientsecrets file or will raise InvalidClientSecretsError for unknown
2101
+ types of Flows.
2102
+
2103
+ Args:
2104
+ filename: string, File name of client secrets.
2105
+ scope: string or iterable of strings, scope(s) to request.
2106
+ redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
2107
+ a non-web-based application, or a URI that handles the
2108
+ callback from the authorization server.
2109
+ message: string, A friendly string to display to the user if the
2110
+ clientsecrets file is missing or invalid. If message is
2111
+ provided then sys.exit will be called in the case of an error.
2112
+ If message in not provided then
2113
+ clientsecrets.InvalidClientSecretsError will be raised.
2114
+ cache: An optional cache service client that implements get() and set()
2115
+ methods. See clientsecrets.loadfile() for details.
2116
+ login_hint: string, Either an email address or domain. Passing this
2117
+ hint will either pre-fill the email box on the sign-in form
2118
+ or select the proper multi-login session, thereby
2119
+ simplifying the login flow.
2120
+ device_uri: string, URI for device authorization endpoint. For
2121
+ convenience defaults to Google's endpoints but any
2122
+ OAuth 2.0 provider can be used.
2123
+
2124
+ Returns:
2125
+ A Flow object.
2126
+
2127
+ Raises:
2128
+ UnknownClientSecretsFlowError: if the file describes an unknown kind of
2129
+ Flow.
2130
+ clientsecrets.InvalidClientSecretsError: if the clientsecrets file is
2131
+ invalid.
2132
+ """
2133
+ try:
2134
+ client_type, client_info = clientsecrets.loadfile(filename,
2135
+ cache=cache)
2136
+ if client_type in (clientsecrets.TYPE_WEB,
2137
+ clientsecrets.TYPE_INSTALLED):
2138
+ constructor_kwargs = {
2139
+ 'redirect_uri': redirect_uri,
2140
+ 'auth_uri': client_info['auth_uri'],
2141
+ 'token_uri': client_info['token_uri'],
2142
+ 'login_hint': login_hint,
2143
+ }
2144
+ revoke_uri = client_info.get('revoke_uri')
2145
+ optional = (
2146
+ 'revoke_uri',
2147
+ 'device_uri',
2148
+ 'pkce',
2149
+ 'code_verifier',
2150
+ 'prompt'
2151
+ )
2152
+ for param in optional:
2153
+ if locals()[param] is not None:
2154
+ constructor_kwargs[param] = locals()[param]
2155
+
2156
+ return OAuth2WebServerFlow(
2157
+ client_info['client_id'], client_info['client_secret'],
2158
+ scope, **constructor_kwargs)
2159
+
2160
+ except clientsecrets.InvalidClientSecretsError as e:
2161
+ if message is not None:
2162
+ if e.args:
2163
+ message = ('The client secrets were invalid: '
2164
+ '\n{0}\n{1}'.format(e, message))
2165
+ sys.exit(message)
2166
+ else:
2167
+ raise
2168
+ else:
2169
+ raise UnknownClientSecretsFlowError(
2170
+ 'This OAuth 2.0 flow is unsupported: {0!r}'.format(client_type))
.venv/lib/python3.11/site-packages/oauth2client/clientsecrets.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Utilities for reading OAuth 2.0 client secret files.
16
+
17
+ A client_secrets.json file contains all the information needed to interact with
18
+ an OAuth 2.0 protected service.
19
+ """
20
+
21
+ import json
22
+
23
+ import six
24
+
25
+
26
+ # Properties that make a client_secrets.json file valid.
27
+ TYPE_WEB = 'web'
28
+ TYPE_INSTALLED = 'installed'
29
+
30
+ VALID_CLIENT = {
31
+ TYPE_WEB: {
32
+ 'required': [
33
+ 'client_id',
34
+ 'client_secret',
35
+ 'redirect_uris',
36
+ 'auth_uri',
37
+ 'token_uri',
38
+ ],
39
+ 'string': [
40
+ 'client_id',
41
+ 'client_secret',
42
+ ],
43
+ },
44
+ TYPE_INSTALLED: {
45
+ 'required': [
46
+ 'client_id',
47
+ 'client_secret',
48
+ 'redirect_uris',
49
+ 'auth_uri',
50
+ 'token_uri',
51
+ ],
52
+ 'string': [
53
+ 'client_id',
54
+ 'client_secret',
55
+ ],
56
+ },
57
+ }
58
+
59
+
60
+ class Error(Exception):
61
+ """Base error for this module."""
62
+
63
+
64
+ class InvalidClientSecretsError(Error):
65
+ """Format of ClientSecrets file is invalid."""
66
+
67
+
68
+ def _validate_clientsecrets(clientsecrets_dict):
69
+ """Validate parsed client secrets from a file.
70
+
71
+ Args:
72
+ clientsecrets_dict: dict, a dictionary holding the client secrets.
73
+
74
+ Returns:
75
+ tuple, a string of the client type and the information parsed
76
+ from the file.
77
+ """
78
+ _INVALID_FILE_FORMAT_MSG = (
79
+ 'Invalid file format. See '
80
+ 'https://developers.google.com/api-client-library/'
81
+ 'python/guide/aaa_client_secrets')
82
+
83
+ if clientsecrets_dict is None:
84
+ raise InvalidClientSecretsError(_INVALID_FILE_FORMAT_MSG)
85
+ try:
86
+ (client_type, client_info), = clientsecrets_dict.items()
87
+ except (ValueError, AttributeError):
88
+ raise InvalidClientSecretsError(
89
+ _INVALID_FILE_FORMAT_MSG + ' '
90
+ 'Expected a JSON object with a single property for a "web" or '
91
+ '"installed" application')
92
+
93
+ if client_type not in VALID_CLIENT:
94
+ raise InvalidClientSecretsError(
95
+ 'Unknown client type: {0}.'.format(client_type))
96
+
97
+ for prop_name in VALID_CLIENT[client_type]['required']:
98
+ if prop_name not in client_info:
99
+ raise InvalidClientSecretsError(
100
+ 'Missing property "{0}" in a client type of "{1}".'.format(
101
+ prop_name, client_type))
102
+ for prop_name in VALID_CLIENT[client_type]['string']:
103
+ if client_info[prop_name].startswith('[['):
104
+ raise InvalidClientSecretsError(
105
+ 'Property "{0}" is not configured.'.format(prop_name))
106
+ return client_type, client_info
107
+
108
+
109
+ def load(fp):
110
+ obj = json.load(fp)
111
+ return _validate_clientsecrets(obj)
112
+
113
+
114
+ def loads(s):
115
+ obj = json.loads(s)
116
+ return _validate_clientsecrets(obj)
117
+
118
+
119
+ def _loadfile(filename):
120
+ try:
121
+ with open(filename, 'r') as fp:
122
+ obj = json.load(fp)
123
+ except IOError as exc:
124
+ raise InvalidClientSecretsError('Error opening file', exc.filename,
125
+ exc.strerror, exc.errno)
126
+ return _validate_clientsecrets(obj)
127
+
128
+
129
+ def loadfile(filename, cache=None):
130
+ """Loading of client_secrets JSON file, optionally backed by a cache.
131
+
132
+ Typical cache storage would be App Engine memcache service,
133
+ but you can pass in any other cache client that implements
134
+ these methods:
135
+
136
+ * ``get(key, namespace=ns)``
137
+ * ``set(key, value, namespace=ns)``
138
+
139
+ Usage::
140
+
141
+ # without caching
142
+ client_type, client_info = loadfile('secrets.json')
143
+ # using App Engine memcache service
144
+ from google.appengine.api import memcache
145
+ client_type, client_info = loadfile('secrets.json', cache=memcache)
146
+
147
+ Args:
148
+ filename: string, Path to a client_secrets.json file on a filesystem.
149
+ cache: An optional cache service client that implements get() and set()
150
+ methods. If not specified, the file is always being loaded from
151
+ a filesystem.
152
+
153
+ Raises:
154
+ InvalidClientSecretsError: In case of a validation error or some
155
+ I/O failure. Can happen only on cache miss.
156
+
157
+ Returns:
158
+ (client_type, client_info) tuple, as _loadfile() normally would.
159
+ JSON contents is validated only during first load. Cache hits are not
160
+ validated.
161
+ """
162
+ _SECRET_NAMESPACE = 'oauth2client:secrets#ns'
163
+
164
+ if not cache:
165
+ return _loadfile(filename)
166
+
167
+ obj = cache.get(filename, namespace=_SECRET_NAMESPACE)
168
+ if obj is None:
169
+ client_type, client_info = _loadfile(filename)
170
+ obj = {client_type: client_info}
171
+ cache.set(filename, obj, namespace=_SECRET_NAMESPACE)
172
+
173
+ return next(six.iteritems(obj))
.venv/lib/python3.11/site-packages/oauth2client/contrib/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Contributed modules.
2
+
3
+ Contrib contains modules that are not considered part of the core oauth2client
4
+ library but provide additional functionality. These modules are intended to
5
+ make it easier to use oauth2client.
6
+ """
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (428 Bytes). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/_appengine_ndb.cpython-311.pyc ADDED
Binary file (6.84 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/_metadata.cpython-311.pyc ADDED
Binary file (4.76 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/appengine.cpython-311.pyc ADDED
Binary file (44 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/devshell.cpython-311.pyc ADDED
Binary file (7.41 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/dictionary_storage.cpython-311.pyc ADDED
Binary file (2.92 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/flask_util.cpython-311.pyc ADDED
Binary file (22.9 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/gce.cpython-311.pyc ADDED
Binary file (6.75 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/keyring_storage.cpython-311.pyc ADDED
Binary file (3.8 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/multiprocess_file_storage.cpython-311.pyc ADDED
Binary file (15.7 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/sqlalchemy.cpython-311.pyc ADDED
Binary file (6.35 kB). View file
 
.venv/lib/python3.11/site-packages/oauth2client/contrib/__pycache__/xsrfutil.cpython-311.pyc ADDED
Binary file (4.11 kB). View file