Repository navigation
Added
max_workersfor recursivecopy(),rm()andPathSyncer.copy()(and
copy_into()),rm()andPathSyncer(...)takemax_workers=None.None, the
default, is one thread, exactly as before. An int of 2 or more hands the files
and links of each directory to a pool of at most that many threads, which
helps a backend that pays a round trip per entry. It applies to a class that sets
the new class attribute_threadsafe = True(LocalPathandFTPPathdo); any other
class, including every class of another distribution until it sets the attribute,
runs serially whatever was asked. Callbacks (progress,on_error,ignore_error,
the synchook, follow policies) run one at a time but on pool threads, in no
fixed order, and the error that ends the operation is the first to happen. A value
below 1 isValueError, one that is not an int isTypeError.PathContract
has asupports_concurrent_useswitch for the matching test.AsyncPathandAsyncFile: a path for use from an event loop.AsyncPath(path)
wraps anyPath; the pure members (name,parent,/,with_*(),match(), ...)
are plain and giveAsyncPath, every method that does I/O is a coroutine of the same
name and arguments,iterdir(),glob(),rglob()andwalk()return async iterators,
andopen()returns an object that is awaited or used withasync withand gives an
AsyncFile(read,readline,write,seek, ...,aclose()). They are exported
frompathapiandpathapi.path. Each call runs the wrapped path's sync method on a
worker thread (asyncio.to_thread()), so every backend can be used from a loop and the
loop is not blocked; cancelling anawaitabandons the wait and the worker finishes the
call. Recursivecopy(),rm()andmove()are one such call, somax_workers=is the
sync fan-out.supports()andcapabilities()of anAsyncPathanswer for the wrapped
path. A subclass gets its own class for every path derived from it, built by the
classmethodAsyncPath._adopt(path)(cls(path)), which a subclass with another
constructor overrides.AsyncHTTPClient: the HTTP client for an event loop.pathapi.http.AsyncHTTPClient
is the sibling ofHTTPClient: the same constructor arguments, redirect and
Authorizationrules,timeout,deadline, reply limit and errors, withrequest()
andredirect()as coroutines,aclose()andasync with. It returns an
AsyncHTTPResponse(await read(),iter_bytes()as an async iterator,await aclose()). It sends through an async transport, an object withasync send()and
async aclose(). The default one,ThreadedTransport, runs the synchronous transport
PATHAPI_HTTP_TRANSPORTnames on worker threads, so every synchronous transport works
from a loop; a native one is declared in the entry-point group
pathapi.http_async_transportsand chosen byPATHAPI_HTTP_ASYNC_TRANSPORT(same
rules and messages as the synchronous registry;async_transport_names(),
get_async_transport()anddefault_async_transport()).- The async half of the transport suite.
python -m pathapi.http.testing --http-async-transports NAMEholds an async transport to the promises of the
synchronous suite (test_async_transport.py,test_async_client.py). Without the
option the async tests run overbuiltinwhen--http-transportsis left out too,
and are not collected when it names a transport: a distribution that runs
--http-transports NAMEgets the tests it got before. pathapi.testing.AsyncPathContract. The pytest contract ofAsyncPathover a
backend: it takes therootfixture ofPathContractand checks each coroutine
against the sync path's result, withasyncio.run()and no extra test dependency. Its
class attributeasync_path_class(defaultAsyncPath) names the class under test, so a
subclass ofAsyncPathis tested with the paths it derives.
Changed
- An unknown HTTP transport name is told where to look, not which package to
install.get_transport()andPATHAPI_HTTP_TRANSPORTraiseValueErrorfor a
name no installed distribution declares in the grouppathapi.http_transports,
naming the group and suggestingpathapi-NAMEas a name to look for. The error is
aValueErrorthat is also anImportError, so code that caught either still
does.requestsis no longer special:transport_names()lists it only when an
installed distribution declares it. The refusal of a proxy that is nothttp://
points at a transport of that group that supports it. Install the distribution
that provides the transport.
Fixed
- A
nanor infinite HTTP timeout is refused withValueError.timeout=float("nan")
passed the check, and the built-in transport then failed insocket.settimeout()after
it had opened the connection, which leaked the socket. Pass a positive finite number of
seconds, orNone. move(overwrite=True)no longer destroys the old target when the move fails.
An existing target was removed before the source was renamed or copied over it, so
a failing rename or copy lost the target. The target now stays until the source
has taken its place: a local file is replaced atomically,rename()is tried
over a file elsewhere, a backend that refuses has the target renamed aside and put
back on failure, and a copy and delete finishes the new file or tree beside the
target first. A directory target is swapped in by rename where its class has one.
If you list the directory while a move runs, you may see a hidden
.NAME.<hex>.pathapi-tmpsibling.copy(follow_symlinks=False, overwrite=True)of a link no longer destroys the old
target when the new link cannot be made. The existing target was unlinked before
symlink_to(), so a refused link lost it. Where the target's class can rename, the
link is now made in a hidden sibling and renamed over the target; a class that
cannot rename still unlinks first. A target class that cannot make links now raises
NotImplementedErrorbefore anything is removed.
Full Changelog: v0.1.1...v0.1.2