lkrequest 0.2.2__tar.gz → 0.2.3__tar.gz

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.
Files changed (98) hide show
  1. {lkrequest-0.2.2 → lkrequest-0.2.3}/CHANGELOG.md +45 -0
  2. {lkrequest-0.2.2 → lkrequest-0.2.3}/Cargo.lock +101 -102
  3. {lkrequest-0.2.2 → lkrequest-0.2.3}/Cargo.toml +2 -2
  4. {lkrequest-0.2.2 → lkrequest-0.2.3}/PKG-INFO +49 -9
  5. {lkrequest-0.2.2 → lkrequest-0.2.3}/README-zh.md +47 -8
  6. {lkrequest-0.2.2 → lkrequest-0.2.3}/README.md +48 -8
  7. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/browser_fingerprints.py +1 -0
  8. {lkrequest-0.2.2 → lkrequest-0.2.3}/pyproject.toml +1 -1
  9. {lkrequest-0.2.2 → lkrequest-0.2.3}/python/lkrequest/__init__.py +2 -0
  10. {lkrequest-0.2.2 → lkrequest-0.2.3}/python/lkrequest/_lkrequest.pyi +121 -6
  11. {lkrequest-0.2.2 → lkrequest-0.2.3}/python/lkrequest/blocking.py +2 -0
  12. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/client.rs +87 -38
  13. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/error.rs +6 -2
  14. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/fingerprint.rs +62 -5
  15. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/lib.rs +2 -0
  16. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/middleware.rs +69 -8
  17. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/protocol.rs +33 -1
  18. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/proxy.rs +2 -0
  19. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/quic.rs +10 -0
  20. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/session.rs +264 -172
  21. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/session_pool.rs +6 -5
  22. lkrequest-0.2.3/src/session_state.rs +226 -0
  23. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/types.rs +4 -2
  24. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/websocket.rs +86 -90
  25. lkrequest-0.2.3/tests/_raw_http.py +131 -0
  26. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/conftest.py +27 -8
  27. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_advanced.py +85 -2
  28. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_client.py +26 -0
  29. lkrequest-0.2.3/tests/test_compression.py +91 -0
  30. lkrequest-0.2.3/tests/test_cookie_max_age.py +113 -0
  31. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_followup_features.py +151 -8
  32. lkrequest-0.2.3/tests/test_h2_wire.py +413 -0
  33. lkrequest-0.2.3/tests/test_h3_retry.py +175 -0
  34. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_local_server.py +9 -17
  35. lkrequest-0.2.3/tests/test_proxy_connect.py +78 -0
  36. lkrequest-0.2.3/tests/test_redirect_safety.py +196 -0
  37. lkrequest-0.2.3/tests/test_session_lifecycle.py +385 -0
  38. lkrequest-0.2.3/tests/test_websocket_concurrency.py +122 -0
  39. {lkrequest-0.2.2 → lkrequest-0.2.3}/.editorconfig +0 -0
  40. {lkrequest-0.2.2 → lkrequest-0.2.3}/.github/workflows/ci.yml +0 -0
  41. {lkrequest-0.2.2 → lkrequest-0.2.3}/.gitignore +0 -0
  42. {lkrequest-0.2.2 → lkrequest-0.2.3}/CONTRIBUTING.md +0 -0
  43. {lkrequest-0.2.2 → lkrequest-0.2.3}/LICENSE +0 -0
  44. {lkrequest-0.2.2 → lkrequest-0.2.3}/NOTICE +0 -0
  45. {lkrequest-0.2.2 → lkrequest-0.2.3}/SECURITY.md +0 -0
  46. {lkrequest-0.2.2 → lkrequest-0.2.3}/benchmarks/README.md +0 -0
  47. {lkrequest-0.2.2 → lkrequest-0.2.3}/benchmarks/bench_latency.py +0 -0
  48. {lkrequest-0.2.2 → lkrequest-0.2.3}/benchmarks/bench_memory.py +0 -0
  49. {lkrequest-0.2.2 → lkrequest-0.2.3}/benchmarks/bench_throughput.py +0 -0
  50. {lkrequest-0.2.2 → lkrequest-0.2.3}/benchmarks/requirements.txt +0 -0
  51. {lkrequest-0.2.2 → lkrequest-0.2.3}/build.rs +0 -0
  52. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/accept_encoding.py +0 -0
  53. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/authentication.py +0 -0
  54. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/basic_requests.py +0 -0
  55. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/certificate_config.py +0 -0
  56. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/client_pool.py +0 -0
  57. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/connection_prewarming.py +0 -0
  58. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/cookie_management.py +0 -0
  59. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/custom_fingerprint.py +0 -0
  60. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/error_handling.py +0 -0
  61. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/event_hooks.py +0 -0
  62. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/fingerprint_validation.py +0 -0
  63. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/http_methods.py +0 -0
  64. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/ip_family.py +0 -0
  65. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/logging_config.py +0 -0
  66. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/metrics.py +0 -0
  67. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/middleware.py +0 -0
  68. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/multipart_upload.py +0 -0
  69. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/pool_stats.py +0 -0
  70. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/proxy_pool.py +0 -0
  71. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/redirect_history.py +0 -0
  72. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/response_inspection.py +0 -0
  73. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/retry_strategies.py +0 -0
  74. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/session_pool.py +0 -0
  75. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/streaming_response.py +0 -0
  76. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/streaming_upload.py +0 -0
  77. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/timeout_config.py +0 -0
  78. {lkrequest-0.2.2 → lkrequest-0.2.3}/examples/websocket.py +0 -0
  79. {lkrequest-0.2.2 → lkrequest-0.2.3}/justfile +0 -0
  80. {lkrequest-0.2.2 → lkrequest-0.2.3}/python/lkrequest/pcap.py +0 -0
  81. {lkrequest-0.2.2 → lkrequest-0.2.3}/rustfmt.toml +0 -0
  82. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/bridge.rs +0 -0
  83. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/client_pool.rs +0 -0
  84. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/dns.rs +0 -0
  85. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/hsts.rs +0 -0
  86. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/masque.rs +0 -0
  87. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/metrics.rs +0 -0
  88. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/multipart.rs +0 -0
  89. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/network_partition.rs +0 -0
  90. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/priority.rs +0 -0
  91. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/randomize.rs +0 -0
  92. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/response.rs +0 -0
  93. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/retry.rs +0 -0
  94. {lkrequest-0.2.2 → lkrequest-0.2.3}/src/upload.rs +0 -0
  95. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/_masque_proxy.py +0 -0
  96. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_masque.py +0 -0
  97. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_package_metadata.py +0 -0
  98. {lkrequest-0.2.2 → lkrequest-0.2.3}/tests/test_stub_parity.py +0 -0
@@ -2,6 +2,51 @@
2
2
 
3
3
  All important changes to this project will be documented in this file.
4
4
 
5
+ ## lkrequest-py 0.2.3
6
+
7
+ Adds the Chrome 155 fingerprint and picks up upstream's switch of the default
8
+ fingerprint to it, plus upstream hardening of length fields read off the wire.
9
+ Sessions can now be closed, and a session whose event hooks refer back to its
10
+ owner is reclaimed instead of living for good. HTTP/2 requests abandoned after
11
+ they were sent are now reset instead of holding their stream open, a WebSocket
12
+ can send and close while another task is receiving, and upstream's pass over TLS,
13
+ ECH and QUIC handshake behaviour is picked up, along with fixes to what a
14
+ redirect carries along and to how a few responses are read. Several things are
15
+ worth reading before upgrading, because they change the wire or what a call
16
+ returns: a client built without a TLS or HTTP/2 profile now presents Chrome 155
17
+ instead of Chrome 144; Firefox 156 and the Chrome presets differ in their HTTP/2
18
+ stream numbering and dependencies; a redirect to another origin no longer carries
19
+ the client's default `Authorization`, `Cookie` and `Host` headers along; a
20
+ `Set-Cookie` with an empty `Max-Age` gives a session cookie instead of one that
21
+ lasts until the year 9999; a `deflate` body that ends early is now an error; and
22
+ `ProxyPool.acquire()` now waits, rather than answering "no proxy", while every
23
+ proxy is cooling down.
24
+
25
+ ### 🚀 Features
26
+ - **Closing A Session** — Added `Session.close()` and `Session.closed` on both session classes, and context-manager support: `with` on `BlockingSession`, `async with` on `Session`. Closing aborts the pooled connections, abandons the requests still in flight — they raise `RuntimeError("Session was closed while the request was in flight")` instead of carrying on — drops the event hooks and the callbacks of the session's own middleware, and makes the session refuse further requests and new hooks with `RuntimeError("Session is closed")`. Closing twice is harmless and cookies stay readable. Until now the only way to let go of a session's connections was `pool_clear()`, which empties the pool but leaves requests in flight running and the session usable; it is unchanged. `Client` has no counterpart, since it owns no connections — those belong to its sessions. The README gained a "Session lifecycle and memory" section: reuse a session rather than creating one per request, the default pool has no upper limit and drops an idle connection after 300 seconds (only when the session is next used), and the resident size of a process does not necessarily shrink when memory is freed.
27
+ - **Chrome 155 Fingerprint** — Added `Client.chrome_155()` / `BlockingClient.chrome_155()`, the `"chrome_155"` name for `tls_profile=` / `h2_profile=` / `quic_profile=`, and `TlsProfile.chrome_155()`, `TlsProfile.chrome_155_quic()`, `H2Profile.chrome_155()`, `QuicProfile.chrome_155()`. Captured from Chrome 155.0.8059.40 on Windows: the TCP and QUIC ClientHellos, H2, the QUIC transport parameters and the Initial-packet generation rules all match Chrome 154. What differs is how the QUIC profile behaves once connected: `QuicProfile.chrome_155()` switches on 16 booleans that Chrome 154's lacks — Chrome 155's HTTP/3 request handling (among them a PRIORITY_UPDATE sent for each request stream before its HEADERS) and its loss-recovery and packetization timing — and nothing else in it moves. Unlike the Chrome 154 capture, this one completed an HTTP/3 navigation, so the H3 SETTINGS were observed directly rather than carried over — and matched the inherited ones.
28
+ - **The Default Fingerprint Is Now Chrome 155** — inherited from upstream, and visible on the wire. `Client()` / `BlockingClient()` without `tls_profile=` used Chrome 144's TLS fingerprint and now uses Chrome 155's; without `h2_profile=`, the same for HTTP/2. Nothing else about the default changes: no TCP fingerprint, no QUIC profile, no default headers. The preset methods and named profiles are unaffected. Pass `tls_profile="chrome_144", h2_profile="chrome_144"` to keep the previous default.
29
+ - **Per-Request HTTP/2 Priority Weight** — Added `h2_priority_weight=` (0–255) to every request method on both session classes. It sets the weight in the legacy priority field of the request's HTTP/2 HEADERS frame — on the request that opens a connection as on any other — independently of the `priority` header, the stream dependency and the exclusive flag; the next request goes back to the weight its profile sends. A profile whose HEADERS carry no legacy priority field (Safari 26) still sends none, and HTTP/1.1 and HTTP/3 are not affected. It is what reproduces Firefox 156's favicon request: `priority=RequestPriority(6, False)` together with `h2_priority_weight=21`. A value outside 0–255 is rejected before anything is sent.
30
+ - **HTTP/2 Stream Numbering And Dependencies** — Added `H2Profile(initial_stream_id=...)` and a read-only `initial_stream_id`: the ID of the first request stream a connection opens, an odd number up to 2³¹ − 1 (`ValueError` otherwise; a profile saved without it keeps starting at 1). Added `"priority_chain"` as a `PriorityConfig(stream_dep_policy=...)` value. Two presets change on the wire as a result. Firefox 156 now opens its first request on stream 3, as its capture shows, and sends navigation HEADERS with weight 41 where it sent 42. The Chrome presets now point each stream's dependency at the newest still-active stream of equal or higher priority, dropping finished and reset streams, instead of at the stream opened just before it: `PriorityConfig.chrome().stream_dep_policy` is `"priority_chain"` where it was `"chain"`. `"chain"` itself behaves as it did.
31
+ - **HTTP/3 Protocol-Error Retries** — Added `H3RetryMode` and `session(h3_retry_mode=...)` on both clients. `H3RetryMode.Safe`, the default, keeps the behaviour of earlier releases. `H3RetryMode.Chrome155ProtocolErrors` retries HTTP/3 stream and connection protocol errors and stateless resets up to twice per redirect hop, as long as no response headers have arrived, which is what Chrome 155 does. That replays a finite buffered request, **including a POST the server may already have processed**. Streaming uploads are not retried, a request sent with `idempotency=Idempotency.NotIdempotent` opts out, and a request that is not HTTP/3-only needs a protocol policy that allows falling back to TCP. It adds no status-code retries and leaves 0-RTT eligibility alone. Without the `quic-h3` build feature only `Safe` is accepted.
32
+
33
+ ### 🐞 Fixes
34
+ - **Event Hooks No Longer Keep Their Session Alive** — a hook that referred back to the object holding the session — `session.on_response(self.log)` in a class that stores `self.session`, or a closure over the session — made a reference cycle that Python's collector could not see, because the hooks sat in a Rust `Vec` behind an `Arc`. The session, its connection pool and everything the owner held then stayed alive for the life of the process: on the order of 7 KB per session in a loop that created one per request, plus its open connection for as long as the server kept it open. The hooks are now kept in Python lists and the session classes report them to the collector, so such a cycle is reclaimed. The callbacks of **middleware** are still held where the collector cannot see them: those of a session's own middleware are released by `Session.close()`, and a callback of `Client(middleware=...)` that refers back to its own client keeps that client alive, so create such a client once rather than per request.
35
+ - **Overflow-Checked Length Fields** — inherited from upstream. Lengths declared in HPACK, QUIC transport parameters and MASQUE capsules were added to buffer offsets without overflow checks, and on a 32-bit target a declared length of 4 GiB or more was truncated outright. They are now compared against the bytes actually present. The one place this surfaces in Python is `H2Profile.from_h2_frames()`: a header block whose declared lengths run past the frame now ends the pseudo-header scan at the last field that fits, instead of relying on unchecked arithmetic.
36
+ - **Abandoned HTTP/2 Requests Are Reset** — inherited from upstream. A request the caller gave up on after it was sent — a timeout, a cancelled task, a dropped streaming response — was never reset with `RST_STREAM`, so its stream stayed open on the server for as long as the connection did. On a shared session that used up the server's concurrent-stream limit (typically 100): later requests then queued behind it without ever being sent, every one of them timed out, and the pool still counted the connection as healthy. A dropped session also kept its connections open for another 30 seconds, waiting out the abandoned streams. The client now sends `RST_STREAM(CANCEL)` for such a request and frees its slot, and a dropped session closes its connections at once. HTTP/1.1 was never affected.
37
+ - **WebSocket Send And Close No Longer Wait For `recv()`** — a connection held one lock across every operation, so while one task was waiting in `recv()`, `send_text()`, `send_binary()` and `close()` from any other task or thread waited too. Against a server that only speaks when spoken to that is a deadlock: the message `recv()` is waiting for is the one `send_text()` cannot send. Receiving, sending and closing now proceed independently, and closing hands a `recv()` that is waiting the closing handshake. As before, once a connection is closed every operation raises `RuntimeError("WebSocket is closed")` and iterating it ends.
38
+ - **WebSocket Upgrade Request Line And Host** — inherited from upstream. The HTTP/1.1 upgrade request went out in absolute-form (`GET https://host/path`), which only a proxy is meant to receive, and its `Host` header dropped the port. A server that routes on the path never matched it, and a `wss://` endpoint on any port but 443 was addressed as if it were on 443. The request line is now the path and query, and `Host` carries the port whenever it is not the scheme's default.
39
+ - **`ProxyPool.acquire()` Does Not Answer With "No Proxy" While Proxies Cool Down** — inherited from upstream. When every proxy of a pool was cooling down, a pool could answer with no proxy at all, which a caller takes for a direct connection and so exposes its own address. A pool that has proxies now waits for one to recover; bound the wait with `asyncio.wait_for` if you need to. Only a pool created with an empty list returns `""`.
40
+ - **First Request On A New HTTP/2 Connection Gets Its Priority** — inherited from upstream. The request that opens a connection now resolves its priority from the same request and client headers, and the same fetch-destination rules, as a request sent on a reused connection, so a navigation keeps its navigation priority on a fresh connection too.
41
+ - **TLS, ECH And QUIC Handshake Corrections** — inherited from upstream: a large pass over how the handshake behaves, much of it visible on the wire. The client now refuses, with the alert Chromium or NSS sends, a server hello a browser would refuse (a version, cipher suite, compression method or ALPN protocol it never offered; a malformed or padded extension; a wrong session-ID echo; falling back to TLS 1.2 after a HelloRetryRequest), and does so before deriving any key. A TLS 1.3 handshake queues its compatibility ChangeCipherSpec until the client's final flight, as Chromium and NSS do, instead of sending it straight after the server hello. After a HelloRetryRequest the client drops pre-shared keys whose hash no longer matches, keeps the first ClientHello's trust-anchor order, and carries the cookie into the inner ClientHello of a real ECH handshake. ECH rejection and acceptance are handled across HelloRetryRequest and TLS 1.2 (an encrypted `ech_required` after Finished, and no usable connection or ticket), and over QUIC as `CRYPTO_ERROR(ech_required)`. ECH GREASE is generated the way each browser does it: an X25519 key pair for the `enc` field on Chromium, and Firefox's per-handshake choice between AES-128-GCM and ChaCha20-Poly1305.
42
+ - **More Requests Count As Safe To Replay** — inherited from upstream. A request that an HTTP/2 GOAWAY cut off before any response headers arrived, or that HTTP/3 rejected (`H3_REQUEST_REJECTED`) before them, was never processed by the server and is now treated as safe to replay automatically.
43
+ - **`dns_timeout` Covers Every Lookup** — inherited from upstream. `Client(dns_timeout=...)` is now a per-query deadline for each DNS query the client makes: address lookups and HTTPS-record lookups, and custom resolvers as well.
44
+ - **A Redirect No Longer Resends The Client's Default Credentials To Another Origin** — inherited from upstream. The headers of `Client(default_headers=...)` were applied again on every hop of a redirect chain, so a default `Authorization`, `Cookie` or `Host` went along to the destination of a redirect to another origin, although the same headers set on the request itself were dropped there. Defaults are now filtered hop by hop the way a request's own headers are. A redirect that leaves the origin (scheme, host or port) no longer carries a default `Authorization`, `Cookie` or `Host`; one from https to http drops a default `Referer`; one that turns a POST into a GET drops the default `Content-Type`, `Content-Encoding`, `Content-Language`, `Content-Location` and `Content-Length`. A redirect within the origin keeps them, going back to the first origin does not bring back what an earlier hop dropped, and the client's defaults are untouched for the next request.
45
+ - **A `Location` Written In Raw UTF-8 Is Followed** — inherited from upstream. A redirect whose `Location` held raw UTF-8 bytes (`/café`) was reported as a response without a `Location` and failed with "redirect response missing Location header". The value is now decoded as UTF-8, resolved against the current URL and percent-encoded on the way out, exactly as a percent-encoded `Location` is. One that is not valid UTF-8 raises `RequestError` saying so.
46
+ - **An Empty `Max-Age` In `Set-Cookie` Is Ignored** — inherited from upstream. `Set-Cookie: sid=abc; Max-Age=` was read as a `Max-Age` too large to represent, so the cookie was stored with a deadline in the year 9999 instead of as a session cookie. Because `Max-Age` outranks `Expires`, a response that tried to delete the cookie with `Max-Age=; Expires=<a date in the past>` left it in place. `Session.set_cookie_raw()` behaved the same way. An empty `Max-Age` now counts as absent; the other attributes, and the last valid `Max-Age` when several are given, are read as before.
47
+ - **`deflate` Bodies: Truncation And A Raw Stream That Looks Like zlib** — inherited from upstream. A `deflate` body that ended before its stream did came back as a shorter body without any error, for buffered and streaming responses alike; it is now a `RequestError`. RFC 9110 defines `deflate` as zlib, but some servers send a raw deflate stream, which the client used to tell apart by the first two bytes. A raw stream that begins with a stored block can start with `0x78 0x9c`, which reads as a valid zlib header, and was rejected as corrupt. Buffered and streaming responses now share one detector: zlib is tried first, and a raw stream is accepted instead only when zlib fails before producing any output and the bytes are the start of a valid stored block. A zlib stream with a bad checksum is still an error.
48
+ - **A Proxy's Refusal After The Authentication Retry Keeps Its Status** — inherited from upstream. When a proxy answered a `CONNECT` with 407 and refused the retry that carried credentials, the `ProxyError` always said the authentication had failed, even for a 503 or a 402. A second 407 is still reported as an authentication failure; any other status is now reported as the tunnel being refused, with that status in the message.
49
+
5
50
  ## lkrequest-py 0.2.2
6
51
 
7
52
  Adds streaming uploads, the Chrome 153, Chrome 154 and Firefox 156 fingerprints,