python-tls-client 1.0.3__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,364 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-tls-client
3
+ Version: 1.0.4
4
+ Summary: Advanced Python HTTP Client with TLS fingerprint spoofing (fork with binary-response fix and certificate pinning)
5
+ Author: Emanuele Scarlata
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Fenix46/Python-Tls-Client
8
+ Project-URL: Source, https://github.com/Fenix46/Python-Tls-Client
9
+ Project-URL: Original, https://github.com/FlorianREGAZ/Python-Tls-Client
10
+ Classifier: Environment :: Web Environment
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Operating System :: Unix
14
+ Classifier: Operating System :: MacOS :: MacOS X
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3 :: Only
24
+ Classifier: Topic :: Internet :: WWW/HTTP
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: typing-extensions
30
+ Dynamic: license-file
31
+
32
+ # Python-TLS-Client
33
+
34
+ Advanced Python HTTP client with TLS fingerprint spoofing, built on
35
+ [bogdanfinn/tls-client](https://github.com/bogdanfinn/tls-client) and inspired
36
+ by [requests](https://github.com/psf/requests).
37
+
38
+ > **Fork notice:** this is a fork of
39
+ > [Python-Tls-Client](https://github.com/FlorianREGAZ/Python-Tls-Client) by
40
+ > Florian Zager, published on PyPI under a different name
41
+ > (`python-tls-client`) because the original name `tls-client` is already
42
+ > taken by an unrelated project. The importable module is unchanged:
43
+ > `import tls_client`.
44
+ >
45
+ > Changes on top of the original:
46
+ > - Fixed corruption of binary response bodies (protobuf, images, ...) — see [Changelog](#changelog) 1.0.2 / 1.0.3
47
+ > - Certificate pinning — see [Changelog](#changelog) 1.0.1
48
+ > - Typed exceptions, safer session lifecycle, clearer native-library load errors — see [Changelog](#changelog) 1.0.4
49
+ > - Native binaries updated to `bogdanfinn/tls-client` v1.16.0, fixing a musl/glibc mismatch on Linux (Chrome 150/152, Firefox 147/148, Brave 146, HTTP/3 over SOCKS5, session ticket control, ...) — see [Changelog](#changelog) 1.0.4
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install python-tls-client
55
+ ```
56
+
57
+ ## Quick start
58
+
59
+ The API is intentionally close to [requests](https://github.com/psf/requests),
60
+ so most of what you already know carries over directly.
61
+
62
+ ```python
63
+ import tls_client
64
+
65
+ session = tls_client.Session(
66
+ client_identifier="chrome_150",
67
+ random_tls_extension_order=True,
68
+ )
69
+
70
+ response = session.get(
71
+ "https://www.example.com/",
72
+ headers={"key1": "value1"},
73
+ proxy="http://user:password@host:port",
74
+ )
75
+
76
+ print(response.status_code, response.text)
77
+ ```
78
+
79
+ Available `client_identifier` presets (see `tls_client/settings.py` for the
80
+ full, up-to-date list — kept in sync with the
81
+ [`MappedTLSClients`](https://github.com/bogdanfinn/tls-client/blob/v1.16.0/profiles/profiles.go)
82
+ map of the native library version shipped in `tls_client/dependencies/`,
83
+ currently v1.16.0):
84
+
85
+ | Browser | Identifiers |
86
+ |----------|-------------|
87
+ | Chrome | `chrome_103`...`chrome_112`, `chrome_116_PSK`, `chrome_116_PSK_PQ`, `chrome_117`, `chrome_120`, `chrome_124`, `chrome_130_PSK`, `chrome_131(_PSK)`, `chrome_133(_PSK)`, `chrome_144(_PSK)`, `chrome_146(_PSK)`, `chrome_150(_PSK)`, `chrome_152(_PSK)` |
88
+ | Brave | `brave_146`, `brave_146_PSK` |
89
+ | Firefox | `firefox_102`...`firefox_110`, `firefox_117`, `firefox_120`, `firefox_123`, `firefox_132`, `firefox_133`, `firefox_135`, `firefox_146_PSK`, `firefox_147(_PSK)`, `firefox_148` |
90
+ | Opera | `opera_89`, `opera_90`, `opera_91` |
91
+ | Safari | `safari_15_6_1`, `safari_16_0` |
92
+ | iOS | `safari_ios_15_5`, `safari_ios_15_6`, `safari_ios_16_0`, `safari_ios_17_0`, `safari_ios_18_0`, `safari_ios_18_5`, `safari_ios_26_0` |
93
+ | iPadOS | `safari_ipad_15_6` |
94
+ | Android | `okhttp4_android_7` through `okhttp4_android_13` |
95
+
96
+ > Only `chrome_144`, `chrome_144_PSK`, `firefox_147`, `firefox_147_PSK` and
97
+ > `firefox_148` carry an HTTP/3 fingerprint matching the real browser; every
98
+ > other profile sends a minimal, non-representative SETTINGS frame if you
99
+ > end up negotiating HTTP/3. This doesn't affect the TLS or HTTP/2
100
+ > fingerprint. Use `disable_http3=True` (see below) if the HTTP/3 fingerprint
101
+ > matters and you're not using one of those five profiles.
102
+
103
+ ## Releasing the session
104
+
105
+ Every `Session` holds native resources (a connection pool) in the underlying
106
+ Go library that must be released explicitly. Always use the context manager,
107
+ or call `.close()` yourself:
108
+
109
+ ```python
110
+ with tls_client.Session(client_identifier="chrome_150") as session:
111
+ response = session.get("https://www.example.com/")
112
+ # session is closed automatically here
113
+ ```
114
+
115
+ If you forget, the session emits a `ResourceWarning` and does a best-effort
116
+ cleanup when it is garbage collected — but that isn't guaranteed to run
117
+ promptly (or at all), so don't rely on it in production code.
118
+
119
+ ## Custom TLS fingerprint
120
+
121
+ Instead of a `client_identifier` preset, you can fully customize the TLS/HTTP2
122
+ fingerprint:
123
+
124
+ ```python
125
+ import tls_client
126
+
127
+ session = tls_client.Session(
128
+ ja3_string=(
129
+ "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-"
130
+ "156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0"
131
+ ),
132
+ h2_settings={
133
+ "HEADER_TABLE_SIZE": 65536,
134
+ "MAX_CONCURRENT_STREAMS": 1000,
135
+ "INITIAL_WINDOW_SIZE": 6291456,
136
+ "MAX_HEADER_LIST_SIZE": 262144,
137
+ },
138
+ h2_settings_order=[
139
+ "HEADER_TABLE_SIZE",
140
+ "MAX_CONCURRENT_STREAMS",
141
+ "INITIAL_WINDOW_SIZE",
142
+ "MAX_HEADER_LIST_SIZE",
143
+ ],
144
+ supported_signature_algorithms=[
145
+ "ECDSAWithP256AndSHA256",
146
+ "PSSWithSHA256",
147
+ "PKCS1WithSHA256",
148
+ "ECDSAWithP384AndSHA384",
149
+ "PSSWithSHA384",
150
+ "PKCS1WithSHA384",
151
+ "PSSWithSHA512",
152
+ "PKCS1WithSHA512",
153
+ ],
154
+ supported_versions=["GREASE", "1.3", "1.2"],
155
+ key_share_curves=["GREASE", "X25519"],
156
+ cert_compression_algo="brotli",
157
+ pseudo_header_order=[":method", ":authority", ":scheme", ":path"],
158
+ connection_flow=15663105,
159
+ header_order=["accept", "user-agent", "accept-encoding", "accept-language"],
160
+ )
161
+
162
+ response = session.post(
163
+ "https://www.example.com/",
164
+ headers={"key1": "value1"},
165
+ json={"key1": "key2"},
166
+ )
167
+ ```
168
+
169
+ ### Additional fingerprint and connection controls (v1.16.0+)
170
+
171
+ ```python
172
+ import tls_client
173
+
174
+ session = tls_client.Session(
175
+ client_identifier="chrome_150",
176
+ # Disable TLS session ticket caching/resumption
177
+ disable_session_tickets=True,
178
+ # Force HTTP/2, even for a profile that would otherwise try HTTP/3
179
+ disable_http3=True,
180
+ )
181
+ ```
182
+
183
+ For a custom client (`client_identifier=None`), you can additionally set:
184
+
185
+ ```python
186
+ session = tls_client.Session(
187
+ client_identifier=None,
188
+ ja3_string="...",
189
+ alpn_protocols=["h2", "http/1.1"],
190
+ alps_protocols=["h2"],
191
+ # Required if ja3_string lists TLS extension 51764 (trust_anchors)
192
+ trust_anchors_payload="...",
193
+ )
194
+ ```
195
+
196
+ ## Certificate pinning
197
+
198
+ Restrict which server certificates are accepted for given hosts by passing
199
+ their pinned public key hashes (SPKI, base64-encoded SHA-256 — the same
200
+ format used by HPKP `pin-sha256` and most certificate-pinning tooling):
201
+
202
+ ```python
203
+ import tls_client
204
+
205
+ session = tls_client.Session(
206
+ client_identifier="chrome_150",
207
+ certificate_pinning={
208
+ "example.com": [
209
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
210
+ "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", # backup pin
211
+ ],
212
+ },
213
+ )
214
+
215
+ response = session.get("https://example.com/")
216
+ ```
217
+
218
+ Requests to a pinned host whose certificate doesn't match any of the given
219
+ pins raise `tls_client.TLSClientCertificateError` (see
220
+ [Error handling](#error-handling)). Including a backup pin is recommended so
221
+ rotating the leaf certificate doesn't lock you out.
222
+
223
+ ## Error handling
224
+
225
+ Failures originating from the underlying Go client are raised as
226
+ `TLSClientExeption` (kept misspelled for backwards compatibility) or one of
227
+ its more specific subclasses, so you can catch broadly or narrowly:
228
+
229
+ ```python
230
+ from tls_client import (
231
+ TLSClientExeption,
232
+ TLSClientTimeoutError,
233
+ TLSClientProxyError,
234
+ TLSClientConnectionError,
235
+ TLSClientCertificateError,
236
+ )
237
+
238
+ try:
239
+ response = session.get("https://example.com/", timeout_seconds=5)
240
+ except TLSClientTimeoutError:
241
+ ... # request exceeded timeout_seconds
242
+ except TLSClientProxyError:
243
+ ... # the configured proxy refused/rejected the connection
244
+ except TLSClientConnectionError:
245
+ ... # DNS, TCP, or TLS handshake failure
246
+ except TLSClientCertificateError:
247
+ ... # response failed certificate pinning validation
248
+ except TLSClientExeption:
249
+ ... # any other backend error
250
+ ```
251
+
252
+ All four subclasses inherit from `TLSClientExeption`, so existing code that
253
+ only catches the base class keeps working unchanged. Classification is
254
+ best-effort pattern matching on the backend's error message (it doesn't
255
+ expose a structured error code) and falls back to the base exception when
256
+ the message doesn't match a known pattern.
257
+
258
+ ## Packaging with PyInstaller / PyArmor
259
+
260
+ The compiled native library ships inside `tls_client/dependencies/` and needs
261
+ to be included explicitly when bundling with PyInstaller or PyArmor.
262
+
263
+ | Platform | `--add-binary` argument |
264
+ |--------------------------|--------------------------|
265
+ | Linux (x86, 32-bit) | `'{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'` |
266
+ | Linux (AMD64 / x86_64) | `'{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'` |
267
+ | macOS (Intel) | `'{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'` |
268
+ | macOS (Apple Silicon) | `'{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'` |
269
+ | Windows (64-bit) | `'{path_to_library}/tls_client/dependencies/tls-client-64.dll;tls_client/dependencies'` |
270
+
271
+ ## Development
272
+
273
+ ```bash
274
+ pip install -r requirements.txt
275
+ python -m unittest discover -s tests -p "test_unit_*.py" -v
276
+ ```
277
+
278
+ `tests/test_unit_*.py` are network-free and run in CI on every push and pull
279
+ request. `tests/test_binary_response.py` is a regression suite that hits
280
+ httpbin.org and is meant for manual local runs (`python -m pytest tests/ -v`),
281
+ not CI.
282
+
283
+ ## Acknowledgements
284
+
285
+ Big shout out to [Bogdanfinn](https://github.com/bogdanfinn) for open
286
+ sourcing [tls-client](https://github.com/bogdanfinn/tls-client) in Go, and to
287
+ [FlorianREGAZ](https://github.com/FlorianREGAZ) for the original Python
288
+ wrapper this project is forked from. The syntax stays close to
289
+ [requests](https://github.com/psf/requests) since most people already know it.
290
+
291
+ ## Changelog
292
+
293
+ ### 1.0.4
294
+ - **Fixed**: the Linux `.so` binaries published in `1.0.3` on PyPI
295
+ (`tls-client-amd64.so`, and likely the other Linux binaries from that
296
+ build) were linked against musl libc instead of glibc, causing
297
+ `OSError: ... libc.musl-x86_64.so.1` on any glibc-based distro (Ubuntu,
298
+ Debian, most standard LXC containers — anything that isn't Alpine).
299
+ The v1.16.0 binaries in this update were downloaded from upstream's
300
+ `*-ubuntu-*` release assets and confirmed glibc-linked (checked for
301
+ `GLIBC_*` version symbols and the absence of a `libc.musl` dependency).
302
+ - **Breaking**: `Session`'s default `client_identifier` changed from
303
+ `chrome_120` to `chrome_150` (matching the v1.16.0 native library's own
304
+ default profile). `chrome_120` imitates a browser version no longer in
305
+ circulation, which made it a distinguishing fingerprint rather than a
306
+ neutral one; pin `client_identifier="chrome_120"` explicitly if you
307
+ relied on the old default.
308
+ - **Added**: typed exception hierarchy (`TLSClientTimeoutError`,
309
+ `TLSClientProxyError`, `TLSClientConnectionError`,
310
+ `TLSClientCertificateError`), all subclasses of the existing
311
+ `TLSClientExeption` so current `except` clauses keep working.
312
+ - **Added**: `Session` now warns (`ResourceWarning`) and best-effort
313
+ auto-closes if garbage collected without an explicit `close()` call.
314
+ - **Fixed**: native library load failures now raise a clear `OSError`
315
+ naming the platform, architecture, and resolved library path instead of
316
+ a raw, hard-to-diagnose `ctypes` error.
317
+ - **Fixed**: invalid/misleading type hints in `Session.__init__` (bare
318
+ `Optional` on `bool` parameters, `str` instead of `Optional[str]`).
319
+ - **Added**: network-free unit tests (`tests/test_unit_*.py`) and a CI
320
+ workflow that runs them on every push and pull request.
321
+ - **Updated**: precompiled native binaries bumped from
322
+ `bogdanfinn/tls-client` ~v1.7.x to v1.16.0. Newest previously available
323
+ profile was `chrome_120`/`firefox_120` (obsolete browser versions by
324
+ 2026, which made the fingerprint itself a distinguishing signal). Now
325
+ includes Chrome 150/152, Firefox 147/148, Brave 146, and 20+ other new
326
+ profiles — see the [client identifier table](#quick-start) above. Verified
327
+ against a live handshake (`chrome_150` against tls.peet.ws) and against
328
+ the full existing test suite (unit tests + the httpbin-backed binary
329
+ response regression suite) on macOS arm64.
330
+ - **Added**: `Session` parameters `alpn_protocols`, `alps_protocols`,
331
+ `trust_anchors_payload` (custom client only), `disable_session_tickets`,
332
+ `disable_http3`, exposing v1.16.0 request fields. All default to the
333
+ previous behavior, so existing calls are unaffected.
334
+ - **Fixed**: `cert_compression_algo` now sends `certCompressionAlgos`
335
+ (the v1.16.0 field name, plural/list) internally instead of the old
336
+ singular `certCompressionAlgo`, which the new binary silently ignored.
337
+ The public parameter itself is unchanged.
338
+ - **Fixed**: `ClientIdentifiers` type list corrected — `safari_ipad_15_6`
339
+ was previously listed as a duplicate of `safari_ios_15_6`, making the
340
+ real iPadOS identifier inaccessible through the type; `confirmed_android_2`
341
+ removed as it was never a valid identifier in any checked upstream version.
342
+
343
+ ### 1.0.3
344
+ - **Fixed**: on Linux, 64-bit x86 machines (`platform.machine() == "x86_64"`)
345
+ were incorrectly matched by the `"x86" in machine()` check in `cffi.py`
346
+ and loaded the 32-bit `tls-client-x86.so` binary instead of the 64-bit
347
+ `tls-client-amd64.so`, causing the native library to fail to load on the
348
+ vast majority of Linux hosts. `machine()` is now matched explicitly
349
+ against `x86_64`/`amd64`/`AMD64` before falling back to the 32-bit binary.
350
+
351
+ ### 1.0.2
352
+ - **Fixed**: binary response bodies (protobuf, images, ...) were corrupted
353
+ by a UTF-8 `byteReplacer` (U+FFFD) inside the Go C library. Every byte
354
+ greater than 127 that did not form a valid UTF-8 sequence was replaced by
355
+ `EF BF BD`.
356
+ - `sessions.py` now sends `isByteResponse: True` in the request payload
357
+ so the backend returns the body as a base64 data-URI.
358
+ - `response.py`'s `build_response` decodes the data-URI back to raw
359
+ bytes for `Response.content`; `Response.text` is a UTF-8 view (with
360
+ replacement characters for invalid sequences).
361
+ - Added `tests/test_binary_response.py` (regression suite, needs network).
362
+
363
+ ### 1.0.1
364
+ - Certificate pinning.
@@ -0,0 +1,22 @@
1
+ python_tls_client-1.0.4.dist-info/licenses/LICENSE,sha256=issbIg7ujiqhtvXsx5mmKhdeZ6zd39ew_nEzvvwImfw,1123
2
+ tls_client/__init__.py,sha256=pa9nyTBgrTRUPcEH7m3A8_U2s69AdTGECp5MfyO9kbs,819
3
+ tls_client/__version__.py,sha256=MTezpEv9Aa9fBY5Q8ToVM6LirC8gjSdxLRjv-NCF1to,445
4
+ tls_client/cffi.py,sha256=BMtpaZp87ZyuPslNF41eVMygmhDI5ilrjJXJc2_fBqE,1570
5
+ tls_client/cookies.py,sha256=5ujSFL6V8bDMCH8Mow6qoj5dP5GjfMJTd8_jWottvpM,15153
6
+ tls_client/exceptions.py,sha256=MELEkIjO410QTYls9UDbUuJlG_PstAv5ysao-WTcDJU,1917
7
+ tls_client/response.py,sha256=5Ji09s3fnrSst3N5n61fJ7motHNF3Jjsk6WBmsZ6iOk,3126
8
+ tls_client/sessions.py,sha256=JlSR5m77OffjY25_aCuEJAZjk-qZm_i1FdCWPaUpDCw,21322
9
+ tls_client/settings.py,sha256=nhpow6IsksoZw-u3yWBFvvQaSM-u23eBWRcCxxoVodo,2376
10
+ tls_client/structures.py,sha256=zb1wflxxWoKFqe8NMT2qEvIh7RA-kN459ZcNhW8p_Uo,2443
11
+ tls_client/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ tls_client/dependencies/tls-client-32.dll,sha256=UgOjb4DqP5zfpDvwcqd1cC0YAqy45DBOeJBSEPZ90q8,24192773
13
+ tls_client/dependencies/tls-client-64.dll,sha256=U9ymNrMtll7m_kVi8535WbIGP-uqPXQO-pJdGHPPEdc,27843480
14
+ tls_client/dependencies/tls-client-amd64.so,sha256=LshTSWY0VF56fqAocVdjlI1Vu92XrKfsqp_qjC67CN8,17379216
15
+ tls_client/dependencies/tls-client-arm64.dylib,sha256=mZhNATkhx1OrKdKHIMsJnv9q32NTg0fhNlLLXP5b3AI,11884770
16
+ tls_client/dependencies/tls-client-arm64.so,sha256=45hiL5nAzo_MtQ_25BTzc7WTKgJ37OkEaKeWmS8K5Rg,16413368
17
+ tls_client/dependencies/tls-client-x86.dylib,sha256=ZGNFfqcTqWs7jJT9nYdG57xRDLZ4T88PS7ZNnIPjJRo,12584208
18
+ tls_client/dependencies/tls-client-x86.so,sha256=XqgTCOU-I22uc7Nb1KCa8SARkcMnE-opaLhbvP6UthY,16210792
19
+ python_tls_client-1.0.4.dist-info/METADATA,sha256=CatC2OhRC7AydypRfQyp4HfEtqUmflhTYB3nloaQQvY,15461
20
+ python_tls_client-1.0.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
21
+ python_tls_client-1.0.4.dist-info/top_level.txt,sha256=yLrfQtbTRfECzPN62knWdGNmbhcPRev1grCAMuEsEz0,11
22
+ python_tls_client-1.0.4.dist-info/RECORD,,
tls_client/__init__.py CHANGED
@@ -12,4 +12,11 @@
12
12
  # tls-client: https://github.com/bogdanfinn/tls-client
13
13
  # requests: https://github.com/psf/requests
14
14
 
15
- from .sessions import Session
15
+ from .sessions import Session
16
+ from .exceptions import (
17
+ TLSClientExeption,
18
+ TLSClientTimeoutError,
19
+ TLSClientProxyError,
20
+ TLSClientConnectionError,
21
+ TLSClientCertificateError,
22
+ )
tls_client/__version__.py CHANGED
@@ -6,6 +6,6 @@
6
6
 
7
7
  __title__ = "tls_client"
8
8
  __description__ = "Advanced Python HTTP Client (fork with binary-response fix and certificate pinning)."
9
- __version__ = "1.0.3"
9
+ __version__ = "1.0.4"
10
10
  __author__ = "Emanuele Scarlata"
11
11
  __license__ = "MIT"
tls_client/cffi.py CHANGED
@@ -17,7 +17,19 @@ else:
17
17
  file_ext = '-x86.so'
18
18
 
19
19
  root_dir = os.path.abspath(os.path.dirname(__file__))
20
- library = ctypes.cdll.LoadLibrary(f'{root_dir}/dependencies/tls-client{file_ext}')
20
+ library_path = f'{root_dir}/dependencies/tls-client{file_ext}'
21
+
22
+ try:
23
+ library = ctypes.cdll.LoadLibrary(library_path)
24
+ except OSError as exc:
25
+ raise OSError(
26
+ f"Failed to load the native tls-client library for this platform "
27
+ f"(platform={platform!r}, machine={machine()!r}) from {library_path!r}. "
28
+ "The binary may be missing from the package, incompatible with this "
29
+ "OS/architecture, or blocked by the OS (e.g. macOS Gatekeeper). "
30
+ "If you believe this platform should be supported, please open an "
31
+ "issue at https://github.com/Fenix46/Python-Tls-Client/issues."
32
+ ) from exc
21
33
 
22
34
  # extract the exposed request function from the shared package
23
35
  request = library.request
Binary file
Binary file
Binary file
Binary file
Binary file
tls_client/exceptions.py CHANGED
@@ -1,3 +1,51 @@
1
1
 
2
2
  class TLSClientExeption(IOError):
3
- """General error with the TLS client"""
3
+ """General error with the TLS client"""
4
+
5
+
6
+ class TLSClientTimeoutError(TLSClientExeption):
7
+ """The request did not complete within ``timeout_seconds``."""
8
+
9
+
10
+ class TLSClientProxyError(TLSClientExeption):
11
+ """The proxy refused the connection, timed out, or rejected the credentials."""
12
+
13
+
14
+ class TLSClientConnectionError(TLSClientExeption):
15
+ """The connection to the target host could not be established (DNS, TCP, TLS handshake)."""
16
+
17
+
18
+ class TLSClientCertificateError(TLSClientExeption):
19
+ """The server certificate did not match the configured certificate pin."""
20
+
21
+
22
+ # Substrings looked up (case-insensitively) in the Go backend's error message
23
+ # to classify it into one of the typed exceptions above. The backend
24
+ # (bogdanfinn/tls-client) does not expose a structured error code, only this
25
+ # free-text message, so this is best-effort pattern matching kept close to
26
+ # the strings the Go client is known to emit.
27
+ _ERROR_PATTERNS = (
28
+ ("certificate", TLSClientCertificateError),
29
+ ("pin", TLSClientCertificateError),
30
+ ("proxy", TLSClientProxyError),
31
+ ("timeout", TLSClientTimeoutError),
32
+ ("deadline exceeded", TLSClientTimeoutError),
33
+ ("no such host", TLSClientConnectionError),
34
+ ("connection refused", TLSClientConnectionError),
35
+ ("connection reset", TLSClientConnectionError),
36
+ ("dial tcp", TLSClientConnectionError),
37
+ ("eof", TLSClientConnectionError),
38
+ )
39
+
40
+
41
+ def build_exception(message: str) -> TLSClientExeption:
42
+ """Map a Go backend error message to the most specific matching exception.
43
+
44
+ Falls back to the base ``TLSClientExeption`` when nothing matches, so
45
+ callers that only catch the base class keep working unchanged.
46
+ """
47
+ lowered = (message or "").lower()
48
+ for pattern, exception_cls in _ERROR_PATTERNS:
49
+ if pattern in lowered:
50
+ return exception_cls(message)
51
+ return TLSClientExeption(message)
tls_client/sessions.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from .cffi import request, freeMemory, destroySession
2
2
  from .cookies import cookiejar_from_dict, merge_cookies, extract_cookies_to_jar
3
- from .exceptions import TLSClientExeption
3
+ from .exceptions import build_exception
4
4
  from .response import build_response, Response
5
5
  from .settings import ClientIdentifiers
6
6
  from .structures import CaseInsensitiveDict
@@ -18,7 +18,7 @@ class Session:
18
18
 
19
19
  def __init__(
20
20
  self,
21
- client_identifier: ClientIdentifiers = "chrome_120",
21
+ client_identifier: ClientIdentifiers = "chrome_150",
22
22
  ja3_string: Optional[str] = None,
23
23
  h2_settings: Optional[Dict[str, int]] = None,
24
24
  h2_settings_order: Optional[List[str]] = None,
@@ -26,20 +26,26 @@ class Session:
26
26
  supported_delegated_credentials_algorithms: Optional[List[str]] = None,
27
27
  supported_versions: Optional[List[str]] = None,
28
28
  key_share_curves: Optional[List[str]] = None,
29
- cert_compression_algo: str = None,
30
- additional_decode: str = None,
29
+ cert_compression_algo: Optional[str] = None,
30
+ additional_decode: Optional[str] = None,
31
31
  pseudo_header_order: Optional[List[str]] = None,
32
32
  connection_flow: Optional[int] = None,
33
33
  priority_frames: Optional[list] = None,
34
34
  header_order: Optional[List[str]] = None,
35
35
  header_priority: Optional[List[str]] = None,
36
- random_tls_extension_order: Optional = False,
37
- force_http1: Optional = False,
38
- catch_panics: Optional = False,
39
- debug: Optional = False,
36
+ random_tls_extension_order: bool = False,
37
+ force_http1: bool = False,
38
+ catch_panics: bool = False,
39
+ debug: bool = False,
40
40
  certificate_pinning: Optional[Dict[str, List[str]]] = None,
41
+ alpn_protocols: Optional[List[str]] = None,
42
+ alps_protocols: Optional[List[str]] = None,
43
+ trust_anchors_payload: Optional[str] = None,
44
+ disable_session_tickets: bool = False,
45
+ disable_http3: bool = False,
41
46
  ) -> None:
42
47
  self._session_id = str(uuid.uuid4())
48
+ self._closed = False
43
49
  # --- Standard Settings ----------------------------------------------------------------------------------------
44
50
 
45
51
  # Case-insensitive dictionary of headers, send on each request
@@ -278,6 +284,24 @@ class Session:
278
284
  # debugging
279
285
  self.debug = debug
280
286
 
287
+ # ALPN protocols offered during the TLS handshake for a custom client
288
+ # Example: ["h2", "http/1.1"]
289
+ self.alpn_protocols = alpn_protocols
290
+
291
+ # ALPS protocols (Application-Layer Protocol Settings) for a custom client
292
+ self.alps_protocols = alps_protocols
293
+
294
+ # Trust anchors payload for the TLS 1.3 "trust_anchors" extension.
295
+ # Required when ja3_string lists extension 51764; pass "" (default
296
+ # None here) to keep the previous behaviour.
297
+ self.trust_anchors_payload = trust_anchors_payload
298
+
299
+ # Disable TLS session ticket caching and resumption
300
+ self.disable_session_tickets = disable_session_tickets
301
+
302
+ # Disable HTTP/3, even for profiles that would otherwise use it
303
+ self.disable_http3 = disable_http3
304
+
281
305
  def __enter__(self):
282
306
  return self
283
307
 
@@ -298,9 +322,32 @@ class Session:
298
322
  destroy_session_response_object = loads(destroy_session_response_string)
299
323
 
300
324
  freeMemory(destroy_session_response_object['id'].encode('utf-8'))
325
+ self._closed = True
301
326
 
302
327
  return destroy_session_response_string
303
328
 
329
+ def __del__(self):
330
+ # Safety net for callers who forget to call close() or use the `with`
331
+ # statement: the Go backend keeps the session (and its connection
332
+ # pool) alive until destroySession() is called, so a forgotten
333
+ # session leaks native memory for the life of the process.
334
+ if not getattr(self, "_closed", True):
335
+ import warnings
336
+ warnings.warn(
337
+ f"Session {self._session_id} was never closed; call session.close() "
338
+ "or use it as a context manager (`with tls_client.Session() as session`) "
339
+ "to release the native TLS client resources.",
340
+ ResourceWarning,
341
+ stacklevel=2,
342
+ )
343
+ try:
344
+ self.close()
345
+ except Exception:
346
+ # Best-effort cleanup during garbage collection / interpreter
347
+ # shutdown; the native library or event loop it depends on
348
+ # may already be gone.
349
+ pass
350
+
304
351
  def execute_request(
305
352
  self,
306
353
  method: str,
@@ -409,6 +456,8 @@ class Session:
409
456
  "requestBody": base64.b64encode(request_body).decode() if is_byte_request else request_body,
410
457
  "requestCookies": request_cookies,
411
458
  "timeoutSeconds": timeout_seconds,
459
+ "disableSessionTickets": self.disable_session_tickets,
460
+ "disableHttp3": self.disable_http3,
412
461
  }
413
462
  if certificate_pinning:
414
463
  request_payload["certificatePinningHosts"] = certificate_pinning
@@ -421,11 +470,17 @@ class Session:
421
470
  "connectionFlow": self.connection_flow,
422
471
  "priorityFrames": self.priority_frames,
423
472
  "headerPriority": self.header_priority,
424
- "certCompressionAlgo": self.cert_compression_algo,
473
+ # certCompressionAlgos (plural) takes a list as of tls-client
474
+ # v1.16.0's CustomTlsClient schema; wrap the single algorithm
475
+ # this wrapper still accepts as a one-element list.
476
+ "certCompressionAlgos": [self.cert_compression_algo] if self.cert_compression_algo else None,
425
477
  "supportedVersions": self.supported_versions,
426
478
  "supportedSignatureAlgorithms": self.supported_signature_algorithms,
427
479
  "supportedDelegatedCredentialsAlgorithms": self.supported_delegated_credentials_algorithms ,
428
480
  "keyShareCurves": self.key_share_curves,
481
+ "alpnProtocols": self.alpn_protocols,
482
+ "alpsProtocols": self.alps_protocols,
483
+ "trustAnchorsPayload": self.trust_anchors_payload,
429
484
  }
430
485
  else:
431
486
  request_payload["tlsClientIdentifier"] = self.client_identifier
@@ -444,7 +499,7 @@ class Session:
444
499
  # --- Response -------------------------------------------------------------------------------------------------
445
500
  # Error handling
446
501
  if response_object["status"] == 0:
447
- raise TLSClientExeption(response_object["body"])
502
+ raise build_exception(response_object["body"])
448
503
  # Set response cookies
449
504
  response_cookie_jar = extract_cookies_to_jar(
450
505
  request_url=url,
tls_client/settings.py CHANGED
@@ -1,5 +1,12 @@
1
1
  from typing_extensions import Literal, TypeAlias
2
2
 
3
+ # Keep this list in sync with the `MappedTLSClients` map in
4
+ # bogdanfinn/tls-client's profiles/profiles.go for the version of the
5
+ # native binaries shipped in tls_client/dependencies/ (currently v1.16.0).
6
+ # Passing a string that isn't in that map fails at the native layer, not
7
+ # here, so an outdated list here just breaks autocomplete/typing, not
8
+ # runtime behavior. Source:
9
+ # https://github.com/bogdanfinn/tls-client/blob/v1.16.0/profiles/profiles.go
3
10
  ClientIdentifiers: TypeAlias = Literal[
4
11
  # Chrome
5
12
  "chrome_103",
@@ -16,6 +23,23 @@ ClientIdentifiers: TypeAlias = Literal[
16
23
  "chrome_116_PSK_PQ",
17
24
  "chrome_117",
18
25
  "chrome_120",
26
+ "chrome_124",
27
+ "chrome_130_PSK",
28
+ "chrome_131",
29
+ "chrome_131_PSK",
30
+ "chrome_133",
31
+ "chrome_133_PSK",
32
+ "chrome_144",
33
+ "chrome_144_PSK",
34
+ "chrome_146",
35
+ "chrome_146_PSK",
36
+ "chrome_150",
37
+ "chrome_150_PSK",
38
+ "chrome_152",
39
+ "chrome_152_PSK",
40
+ # Brave
41
+ "brave_146",
42
+ "brave_146_PSK",
19
43
  # Safari
20
44
  "safari_15_6_1",
21
45
  "safari_16_0",
@@ -23,8 +47,12 @@ ClientIdentifiers: TypeAlias = Literal[
23
47
  "safari_ios_15_5",
24
48
  "safari_ios_15_6",
25
49
  "safari_ios_16_0",
50
+ "safari_ios_17_0",
51
+ "safari_ios_18_0",
52
+ "safari_ios_18_5",
53
+ "safari_ios_26_0",
26
54
  # iPadOS (Safari)
27
- "safari_ios_15_6",
55
+ "safari_ipad_15_6",
28
56
  # FireFox
29
57
  "firefox_102",
30
58
  "firefox_104",
@@ -34,6 +62,14 @@ ClientIdentifiers: TypeAlias = Literal[
34
62
  "firefox_110",
35
63
  "firefox_117",
36
64
  "firefox_120",
65
+ "firefox_123",
66
+ "firefox_132",
67
+ "firefox_133",
68
+ "firefox_135",
69
+ "firefox_146_PSK",
70
+ "firefox_147",
71
+ "firefox_147_PSK",
72
+ "firefox_148",
37
73
  # Opera
38
74
  "opera_89",
39
75
  "opera_90",
@@ -51,14 +87,17 @@ ClientIdentifiers: TypeAlias = Literal[
51
87
  "zalando_android_mobile",
52
88
  "nike_ios_mobile",
53
89
  "nike_android_mobile",
90
+ "cloudscraper",
54
91
  "mms_ios",
92
+ "mms_ios_1",
55
93
  "mms_ios_2",
56
94
  "mms_ios_3",
57
95
  "mesh_ios",
96
+ "mesh_ios_1",
58
97
  "mesh_ios_2",
59
98
  "mesh_android",
99
+ "mesh_android_1",
60
100
  "mesh_android_2",
61
101
  "confirmed_ios",
62
102
  "confirmed_android",
63
- "confirmed_android_2",
64
- ]
103
+ ]
@@ -1,195 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-tls-client
3
- Version: 1.0.3
4
- Summary: Advanced Python HTTP Client with TLS fingerprint spoofing (fork with binary-response fix and certificate pinning)
5
- Author: Emanuele Scarlata
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/Fenix46/Python-Tls-Client
8
- Project-URL: Source, https://github.com/Fenix46/Python-Tls-Client
9
- Project-URL: Original, https://github.com/FlorianREGAZ/Python-Tls-Client
10
- Classifier: Environment :: Web Environment
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Natural Language :: English
13
- Classifier: Operating System :: Unix
14
- Classifier: Operating System :: MacOS :: MacOS X
15
- Classifier: Operating System :: Microsoft :: Windows
16
- Classifier: Programming Language :: Python
17
- Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
- Classifier: Programming Language :: Python :: 3.9
20
- Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: 3.11
22
- Classifier: Programming Language :: Python :: 3.12
23
- Classifier: Programming Language :: Python :: 3 :: Only
24
- Classifier: Topic :: Internet :: WWW/HTTP
25
- Classifier: Topic :: Software Development :: Libraries
26
- Requires-Python: >=3.8
27
- Description-Content-Type: text/markdown
28
- License-File: LICENSE
29
- Requires-Dist: typing-extensions
30
- Dynamic: license-file
31
-
32
- # Python-TLS-Client
33
-
34
- > **Nota:** questo è un fork di [Python-Tls-Client](https://github.com/FlorianREGAZ/Python-Tls-Client)
35
- > di Florian Zager, pubblicato su PyPI con nome diverso (`python-tls-client`) perché
36
- > il nome originale `tls-client` è già occupato dal progetto upstream.
37
- > Il modulo importabile resta invariato: `import tls_client`.
38
- >
39
- > Modifiche rispetto all'originale:
40
- > - Fix corruzione risposte binarie (protobuf, immagini, ...) — vedi Changelog 1.0.2
41
- > - Certificate pinning (vedi Changelog 1.0.1)
42
-
43
- Python-TLS-Client is an advanced HTTP library based on requests and tls-client.
44
-
45
- # Installation
46
- ```
47
- pip install python-tls-client
48
- ```
49
-
50
- # Examples
51
- The syntax is inspired by [requests](https://github.com/psf/requests), so its very similar and there are only very few things that are different.
52
-
53
- Example 1 - Preset:
54
- ```python
55
- import tls_client
56
-
57
- # You can also use the following as `client_identifier`:
58
- # Chrome --> chrome_103, chrome_104, chrome_105, chrome_106, chrome_107, chrome_108, chrome109, Chrome110,
59
- # chrome111, chrome112, chrome_116_PSK, chrome_116_PSK_PQ, chrome_117, chrome_120
60
- # Firefox --> firefox_102, firefox_104, firefox108, Firefox110, firefox_117, firefox_120
61
- # Opera --> opera_89, opera_90
62
- # Safari --> safari_15_3, safari_15_6_1, safari_16_0
63
- # iOS --> safari_ios_15_5, safari_ios_15_6, safari_ios_16_0
64
- # iPadOS --> safari_ios_15_6
65
- # Android --> okhttp4_android_7, okhttp4_android_8, okhttp4_android_9, okhttp4_android_10, okhttp4_android_11,
66
- # okhttp4_android_12, okhttp4_android_13
67
- #
68
- # more client identifiers can be found in settings.py
69
-
70
- session = tls_client.Session(
71
- client_identifier="chrome112",
72
- random_tls_extension_order=True
73
- )
74
-
75
- res = session.get(
76
- "https://www.example.com/",
77
- headers={
78
- "key1": "value1",
79
- },
80
- proxy="http://user:password@host:port"
81
- )
82
- ```
83
-
84
- Example 2 - Custom:
85
- ```python
86
- import tls_client
87
-
88
- session = tls_client.Session(
89
- ja3_string="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
90
- h2_settings={
91
- "HEADER_TABLE_SIZE": 65536,
92
- "MAX_CONCURRENT_STREAMS": 1000,
93
- "INITIAL_WINDOW_SIZE": 6291456,
94
- "MAX_HEADER_LIST_SIZE": 262144
95
- },
96
- h2_settings_order=[
97
- "HEADER_TABLE_SIZE",
98
- "MAX_CONCURRENT_STREAMS",
99
- "INITIAL_WINDOW_SIZE",
100
- "MAX_HEADER_LIST_SIZE"
101
- ],
102
- supported_signature_algorithms=[
103
- "ECDSAWithP256AndSHA256",
104
- "PSSWithSHA256",
105
- "PKCS1WithSHA256",
106
- "ECDSAWithP384AndSHA384",
107
- "PSSWithSHA384",
108
- "PKCS1WithSHA384",
109
- "PSSWithSHA512",
110
- "PKCS1WithSHA512",
111
- ],
112
- supported_versions=["GREASE", "1.3", "1.2"],
113
- key_share_curves=["GREASE", "X25519"],
114
- cert_compression_algo="brotli",
115
- pseudo_header_order=[
116
- ":method",
117
- ":authority",
118
- ":scheme",
119
- ":path"
120
- ],
121
- connection_flow=15663105,
122
- header_order=[
123
- "accept",
124
- "user-agent",
125
- "accept-encoding",
126
- "accept-language"
127
- ]
128
- )
129
-
130
- res = session.post(
131
- "https://www.example.com/",
132
- headers={
133
- "key1": "value1",
134
- },
135
- json={
136
- "key1": "key2"
137
- }
138
- )
139
- ```
140
-
141
- # Pyinstaller / Pyarmor
142
- **If you want to pack the library with Pyinstaller or Pyarmor, make sure to add this to your command:**
143
-
144
- Linux - Ubuntu / x86:
145
- ```
146
- --add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'
147
- ```
148
-
149
- Linux Alpine / AMD64:
150
- ```
151
- --add-binary '{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'
152
- ```
153
-
154
- MacOS M1 and older:
155
- ```
156
- --add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'
157
- ```
158
-
159
- MacOS M2:
160
- ```
161
- --add-binary '{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'
162
- ```
163
-
164
- Windows:
165
- ```
166
- --add-binary '{path_to_library}/tls_client/dependencies/tls-client-64.dll;tls_client/dependencies'
167
- ```
168
-
169
- # Acknowledgements
170
- Big shout out to [Bogdanfinn](https://github.com/bogdanfinn) for open sourcing his [tls-client](https://github.com/bogdanfinn/tls-client) in Golang.
171
- Also I wanted to keep the syntax as similar as possible to [requests](https://github.com/psf/requests), as most people use it and are familiar with it!
172
-
173
- # Changelog
174
-
175
- ## 1.0.3
176
- - **Fix**: on Linux, 64-bit x86 machines (`platform.machine() == "x86_64"`) were
177
- incorrectly matched by the `"x86" in machine()` check in `cffi.py` and loaded
178
- the 32-bit `tls-client-x86.so` binary instead of the 64-bit
179
- `tls-client-amd64.so`, causing the native library to fail to load on the vast
180
- majority of Linux hosts. `machine()` is now matched explicitly against
181
- `x86_64`/`amd64`/`AMD64` before falling back to the 32-bit binary.
182
-
183
- ## 1.0.2
184
- - **Fix**: binary response bodies (protobuf, images, ...) were corrupted by a
185
- UTF-8 `byteReplacer` (U+FFFD) inside the Go C library. Every byte > 127 that
186
- did not form a valid UTF-8 sequence was replaced by `EF BF BD`.
187
- - `sessions.py`: now sends `isByteResponse: True` in the request payload so
188
- the backend returns the body as a base64 data-URI.
189
- - `response.py`: `build_response` decodes the data-URI back to raw bytes for
190
- `Response.content`; `Response.text` is a UTF-8 view (with replacement
191
- chars for invalid sequences).
192
- - Added `tests/test_binary_response.py` (regression suite, needs network).
193
-
194
- ## 1.0.1
195
- - Certificate pinning
@@ -1,22 +0,0 @@
1
- python_tls_client-1.0.3.dist-info/licenses/LICENSE,sha256=issbIg7ujiqhtvXsx5mmKhdeZ6zd39ew_nEzvvwImfw,1123
2
- tls_client/__init__.py,sha256=ORP-QdcnLxOEs42G3yPr6txUxryz7jUOA_AtHMafpf8,655
3
- tls_client/__version__.py,sha256=44prGvLt4nYxvVWOCD33Ky2Zb-QVa7TxgYWnW86DWvU,445
4
- tls_client/cffi.py,sha256=p6arENyBf1-x43CPOsKAT0ADhy2CaAFAEXdyLj2duzs,1018
5
- tls_client/cookies.py,sha256=5ujSFL6V8bDMCH8Mow6qoj5dP5GjfMJTd8_jWottvpM,15153
6
- tls_client/exceptions.py,sha256=edxP4P0NVOK-1r0zNRAMt4yh97LArXz8i6SU9oTSYQY,78
7
- tls_client/response.py,sha256=5Ji09s3fnrSst3N5n61fJ7motHNF3Jjsk6WBmsZ6iOk,3126
8
- tls_client/sessions.py,sha256=Uwx4YlhwmUkJonuqu6t0rtINiEpxbwPHbuJ4R2fc3Jw,18651
9
- tls_client/settings.py,sha256=sknNBPsNOujgIDO5Jo4L8RZFaY6gtGuWUvpJkJ0rBog,1289
10
- tls_client/structures.py,sha256=zb1wflxxWoKFqe8NMT2qEvIh7RA-kN459ZcNhW8p_Uo,2443
11
- tls_client/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- tls_client/dependencies/tls-client-32.dll,sha256=RYiPCa-Df2FRn1E5A-hqFs-vgkKNq-HyI5BrBZjgj7Q,16512055
13
- tls_client/dependencies/tls-client-64.dll,sha256=sXd3hlZFX2uEghVCONMjo95NdPKot6Yr2XMlGiWe24c,18685673
14
- tls_client/dependencies/tls-client-amd64.so,sha256=atLDjHTHV_REcR4XfmtzjeffRnObntzoPzV2GudSJCg,12091312
15
- tls_client/dependencies/tls-client-arm64.dylib,sha256=9aKdKT0X97KOrXIyi2ilnGDpyi1iIBE8QFaWqxHf8to,10778130
16
- tls_client/dependencies/tls-client-arm64.so,sha256=MCwijkBgGDvxs9KPpSU53zou73NxzXjmUiZMZwUTpFE,11116584
17
- tls_client/dependencies/tls-client-x86.dylib,sha256=dEzNbwEuzbc_CWkBW3NyqhBOQY4IJJSMUT_2h5z8LBk,11687888
18
- tls_client/dependencies/tls-client-x86.so,sha256=xY-FoLKXMRxEJHyb9W1TKXVmuARnSeQw4T8w6cbuHLI,12045392
19
- python_tls_client-1.0.3.dist-info/METADATA,sha256=ouTIRQ4XKfhMjoplf8POykNnVou4jG5RnLCAsjhjlWA,6784
20
- python_tls_client-1.0.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
21
- python_tls_client-1.0.3.dist-info/top_level.txt,sha256=yLrfQtbTRfECzPN62knWdGNmbhcPRev1grCAMuEsEz0,11
22
- python_tls_client-1.0.3.dist-info/RECORD,,