Dynamic attributes - #605
Open
gyozaaaa wants to merge 6 commits into
Open
Conversation
a list for both sync and async clients. Note that reading the `proxies` value list will always be a list, similar to how `Cookies` currently works.
for both the sync and async clients
A `Client` keeps its configuration around to rebuild itself whenever its headers or proxies change, and `tls_verify` was left in it as a `CertificatePath`. Every rebuild therefore re-read the file from the disk, so a plain `client.headers["x"] = "1"` failed with an I/O error once the file was gone, and silently switched trust roots when the file had been swapped in the meantime. Resolve the path into a `CertStore` in `Client::new`, the way an in-memory store given to `tls_verify` is already reused across rebuilds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Client.headers` handed out a snapshot of the headers taken outside of the rebuild lock, so `client.headers.update(...)` was a read-modify-write whose read was unlocked. Two threads could snapshot the same map, mutate their own copies and serialize only on the store, which replaces the header set wholesale: the update of whichever thread stored first was dropped without a trace. The lock only ever covered the `headers` and `proxies` setters, not the view the guide teaches as the way to change the headers of a client. Express each mutation as a `HeaderUpdate` and replay it, under the lock, onto the headers read back from the client, then refresh the view with the result. A view that has gone stale now updates the current headers instead of restoring the ones it was created with. `HeaderMap` applies the same `HeaderUpdate`, so both share one implementation of every operation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test only inspects the headers of a client, it never makes a request, so the fixture started and tore down an HTTP server for nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Dynamic
headersandproxieson an instantiated clientBoth settings could only be given to the constructor and were fixed for the life of the
client. They are now readable and writable attributes on
Clientandblocking.Client.proxiesreads back as a list, orNone.Proxy.all(...)is accepted wherever asequence is, on the attribute and on the constructor argument alike.
headersreads back aClientHeaders, aHeaderMapbound to the client, so theusual lookups work on it and it can be handed to another client.
HeaderMap.update()is new and available on plain maps too.
headers of the client are replaced, so those coming from an emulation are kept.
Requests already in flight keep the settings they started with.
Implementation
wreq::Clientbakes default headers and proxies in at build time, so a change rebuildsthe underlying client from the retained configuration and swaps it in through an
ArcSwap. The request path stays lock-free; a mutex serializes the rebuilds so thatconcurrent updates of the headers and the proxies cannot lose each other. Dropping the
old connection pool is intended: connections opened through the previous proxy are not
reused.
Two details worth flagging for review:
Builder.user_agentmoved fromPyBackedStrtoString, becausePyBackedStris notClonein pyo3 0.29 and the retained configuration has to be cloneable. One smallallocation per client build.
tls_verifycertificate file is now read once, at construction, rather than on everyrebuild.
Tests
tests/proxy_test.pyand the client half oftests/header_test.pyare hermetic — localservers standing in for the origin and for two forward proxies, no network. They cover
switching, clearing, scheme-specific proxy lists, per-request override, configuration and
cookie-jar preservation, emulation headers surviving a header change, concurrent updates
from threads, and the blocking client.