Repository navigation
v0.10.0
pathlib-next 0.10.0 is the core alone: the path classes, pathlib_next.http,
FTPPath and the archive paths, with no dependency, no extra and no console
script. The URI layer, every scheme that needs a library, and the uripath
command are distributions of their own that depend on it. Under Changed, the
migration comes in this order: the table of names, then every change of
behaviour with what to do about it.
Added
pathlib_next.http: HTTP requests on the standard library, with a
transport you can replace, and no URI.import pathlib_next.httpneeds no
extra and no third-party library (it loadshttp.clientandssl;
import pathlib_nextstill loads neither).HTTPClient(transport=None, *, headers=None, timeout=(10, 60), user_agent=None)sendsrequest(method, url, *, data=None, headers=None, stream=False, allow_redirects=None, timeout, auth=None)through a transport and follows redirects:GET,
HEADandOPTIONSup to 30, withAuthorizationdropped for good by a hop
to another scheme, host or port; every other method is sent again once after
a 307 or 308 to the same origin and is otherwise anOSError(EIO)naming the
status. A transport is any object withsend(method, url, *, headers, body=None, timeout, stream=False)that returns anHTTPResponse, and a
close();HTTPTransport(*, ssl_context=None, verify=True, proxies=None)is
the built-in one onhttp.client(TLS verified by default,proxies=as an
object indexed with the request URL, one idle connection per thread and
server). The package also holds the helpers a path built on it needs:
raise_for_status,translate_errors(a failed request as theOSError
typepathlibraises),read_limited,json_bodyanddrop_body(a reply
read whole is held toMAX_REPLY_BYTES, 8 MiB after decoding, and the
setting is assignable),peer_text,split_userinfo,server_sizeand
is_redirect.PATHLIB_NEXT_HTTP_TRANSPORTnames the transport a client
builds when it is given none:builtin, or the name of one an installed
distribution declares in the entry-point grouppathlib_next.http_transports
(requests, from thepathlib-next-requestsdistribution;transport_names()
lists them without importing any andget_transport(name)loads one).
prepare_request()makes the checks of the transport contract for a
transport of your own.pathlib_next.http.testing: the suite that holds a transport to the
promises of the built-in one. With the distribution that provides the
transport andpytestinstalled,python -m pathlib_next.http.testing --http-transports requests -qruns the tests that depend on the transport
against it, over loopback servers scripted at the byte level. The test
files ship in the wheel. It is testing support and not part of the stable
API: its contents, including test names, fixtures and helper signatures, may
change in any release, andpathlib_next.httpnever imports it.pathlib_next.ftp:FTPPathandFTPBackend, an FTP tree that needs no
URI and no extra.FTPPath("/pub/x", host="h", user="u", tls=True)builds
the path's ownFTPBackend;FTPPath("/pub/x", backend=backend)shares one
connection cache between paths built separately (or plugs in a
BaseFTPBackendof your own), and giving both is aTypeError. Nothing
connects until the first operation.FTPBackend(host, port=21, *, user, password, tls, timeout, ssl_context, verify)keeps a control connection per
thread, is a context manager, andclose()ends every connection it holds.
str()is the remote path,repr()names the endpoint and never shows a
password, and two paths are one tree when they share a backend or name the
same host, port, account and security.pathlib_next.FTPPathis imported on
first use, soimport pathlib_nextstill loads neitherftplibnorssl.pathlib_next.archive:ZipPath,TarPathandArchivePath, the members
of an archive file that is anyPath, with no URI and no extra.
ZipPath("d/x", archive="a.zip")takes the archive as aPath, or astr
oros.PathLike(aLocalPath); the segments are the member path, read
from the archive root.ZipPathandTarPathfix the format,ArchivePath
reads it from the extension or the first bytes on first use, and building a
path reads nothing. One handle serves each archive file and format, so paths
built separately see each other's writes. A zip that is a local file is
written in place (write_bytes,mkdir,unlink,rmdir,rename,
rm(recursive=True),batch()); every other archive is read-only, in
memory or on any otherPath.str()is the member path,repr()names the
archive, andas_uri()iszip:<archive uri>!/<member>.
ArchivePath.backend_for()gives the handle to pass asarchive=.
pathlib_next.ZipPath,TarPathandArchivePathare imported on first use.pathlib_next.providers.TreePath, the baseFTPPathand the archive
paths are built on, for a path whose names are POSIX segments below the
root of a tree a backend serves.pathlib_next.providers.name_stem(),name_suffix()and
name_suffixes(). One path component split into stem and suffix as
pathlibof the running interpreter splits it, for a class that computes
those itself and must agree withPathname.pathlib_next.exceptions:PathError, the base of the exceptions the
package defines.NonRelativePatternError(an absolute glob pattern) is
now aPathErroras well as aNotImplementedErrorand aValueError, so
everyexceptclause that caught it still does. I/O failures stay the
OSErrortypespathlibraises.PathError,NonRelativePatternErrorand
MemPathare exported frompathlib_next.pathlib_next.mempath,pathlib_next.syncandpathlib_next.testing
declare__all__, sofrom pathlib_next.sync import *publishes
PathSyncer,SyncEventandPathAndStatand nothing else, and a type
checker reads those names as the module's exports.pathlib_next.providers.DiscardOnError, a mixin for a write stream that
commits when it is closed: its__exit__callsdiscard()when thewith
block raised, then closes.CommitOnCloseinherits it and has a
discard(); see the entry under Changed.
Changed
-
Where every name went. Change each import and each install as the table
says. There is no alias for an old name: the old import raises
ModuleNotFoundErrororImportError, and a name that is not in the table
was never public. The URI layer, the schemes and the command line are the
distributionsuripath,sftppath,objpath,repopathand
pathlib-next-requests, which each document their own changes of behaviour;
the core's own moves come first.Was Is now pathlib_next.utils.sync(PathSyncer,SyncEvent,PathAndStat)pathlib_next.syncpathlib_next.utils.glob(glob,select,full_match,parse_pattern, ...)pathlib_next.globpathlib_next.utils.glob.NonRelativePatternErrorpathlib_next.NonRelativePatternError(defined inpathlib_next.exceptions)pathlib_next.utils.checksum(md5,sha256,stream,native)pathlib_next.checksumpathlib_next.utils.md5,pathlib_next.utils.sha256pathlib_next.checksum.md5,pathlib_next.checksum.sha256pathlib_next.utils.archive,pathlib_next.utils.make_archive,pathlib_next.utils.unpack_archivepathlib_next.archive.make_archive,pathlib_next.archive.unpack_archivepathlib_next.utils.archive.detect_format(also_detect_format)pathlib_next.archive.detect_formatpathlib_next.utils.stat.FileStatpathlib_next.FileStatpathlib_next.utils.as_modepathlib_next.providers.parse_modepathlib_next.utils.as_ownerpathlib_next.providers.parse_ownerpathlib_next.utils.UNCHANGEDpathlib_next.providers.UNCHANGEDpathlib_next.utils.as_error_handler,as_error_predicate,OfferedOncepathlib_next.providers.as_error_handler,as_error_predicate,OfferedOncepathlib_next.utils.is_safe_child_name,is_windows_flavouredpathlib_next.providers.is_safe_child_name,is_windows_flavouredpathlib_next.utils.notimplementedpathlib_next.providers.notimplementedpathlib_next.utils.LRUpathlib_next.providers.LRUpathlib_next.utils._commit.CommitOnClose(also_CommitOnClose)pathlib_next.providers.CommitOnClosepathlib_next.path.check_follow,contains,copy_error_predicate(also_check_follow,_contains)pathlib_next.providers.check_follow,contains,copy_error_predicatepathlib_next.utils.sizeof_fmt,pathlib_next.utils.parsedateremoved: no replacement is public the package pathlib_next.urithe distribution uripath, import nameuripathpathlib_next.Uri,pathlib_next.UriPath(andpathlib_next.uri.Uri,UriPath,UriLike)uripath.URI,uripath.URIPath,uripath.URILikePath,Pathname,FileStatimported frompathlib_next.urithe same names in pathlib_nextpathlib_next.uri.source.Sourceuripath.Source(alsouripath.source.Source)pathlib_next.uri.source.compose_uri,compose_host,percent_encode,is_drive,SAFE_PATH,ERRORSthe same names in uripath.sourcepathlib_next.uri.same_authorityuripath.source.same_authoritypathlib_next.uri.DerivedBackenduripath.schemes.DerivedBackendpathlib_next.uri.query.Query;Query(text),Query.decode()uripath.Query, no longer astr;Query.parse(text),Query.to_pairs()Source.from_str(text, strict);Source.as_str(sanitize=True),as_str(sanitize=False)Source.parse(text, *, strict=True);str(source),source.reveal()pathlib_next.uri.schemes.file.FileUri,schemes.data.DataUriuripath.schemes.FileURIPath,DataURIPathschemes.archive.ZipUri,TarUri,ArchiveUri,ArchiveZipUri,ArchiveTarUriuripath.schemes.ZipURIPath,TarURIPath,ArchiveURIPath,ArchiveZipURIPath,ArchiveTarURIPathschemes.ftp.FtpPathuripath.schemes.FTPURIPathforftp:;pathlib_next.FTPPathfor a path with no URIschemes.ftp.FtpBackend,BaseFtpBackenduripath.schemes.FTPURIBackend,BaseFTPURIBackend(pathlib_next.ftp.FTPBackendbelongs to one host)schemes.ftp.DEFAULT_TIMEOUT,IDLE_PROBE_SECONDS;schemes.archive.MAX_LINKS,LinkTargetpathlib_next.ftpandpathlib_next.archive, which define themschemes.archive._base.MEMBER_SPOOL_BYTESuripath.schemes.MEMBER_SPOOL_BYTES(pathlib_next.archive.MEMBER_SPOOL_BYTESforZipPath)schemes.http.HttpPath,HttpBackend,HttpWriteStream,HttpAppendStreamuripath.schemes.HTTPURIPath,HTTPBackend,HTTPWriteStream,HTTPAppendStreamschemes.http.HTTPTransport,HTTPResponsepathlib_next.http.HTTPTransport,HTTPResponseschemes.http.MAX_LISTING_BYTESpathlib_next.http.MAX_REPLY_BYTES(assign it there)schemes.http.RequestsTransportpathlib_next_requests.RequestsTransport, from the distributionpathlib-next-requestsschemes.dav.DavPathuripath.schemes.DAVURIPathschemes.sftp.SftpPathsftppath.SFTPPathfor a path built from keywords;sftppath.uri.SFTPURIPathforsftp:schemes.sftp.SftpBackend,AsyncsshSftpBackend,BaseSftpBackend,CheckFileSftpBackendsftppath.SFTPBackend,AsyncSSHBackend,BaseSFTPBackend,CheckFileSFTPBackendschemes.s3.S3Path,S3Backend,BaseS3Backendobjpath.S3Path,S3Backend,BaseS3Backend;objpath.uri.S3URIPathfors3:schemes.gs.GsPath,GsBackend,BaseGsBackendobjpath.GSPath,GSBackend,BaseGSBackend;objpath.uri.GSURIPathforgs:schemes.az.AzPath,AzBackend,BaseAzBackendobjpath.AZPath,AZBackend,BaseAZBackend;objpath.uri.AZURIPathforaz:schemes.github.GitHubPath,schemes.gitlab.GitLabPathrepopath.GitHubPath,GitLabPathfor paths with no URI;repopath.uri.GitHubURIPath,GitLabURIPathforgithub:,gitlab:schemes.git.GitPath(git:);GitHubGitPath,GitLabGitPath(git+github:,git+gitlab:)repopath.RepoPath,repopath.uri.RepoURIPath(repo:);github:andgitlab:replacegit+github:andgit+gitlab:;git:itself is goneschemes.github.RepoBackend;schemes._gitrepo.BaseRepoBackend;InsecureTransportWarningrepopath.RepoBackend,BaseRepoBackend,InsecureTransportWarningpathlib_next.tools.uripath(main(argv, *, stdin, stdout, stderr),build_parser())uripath.cli.main(argv=None); there is no parser to buildthe script uripath = pathlib_next.tools.uripath:mainuripath = uripath.cli:main, which needsuripath[cli];python -m uripathruns it toothe entry-point group pathlib_next.schemesthe group uripath.schemesthe environment variable PATHLIB_NEXT_SFTP_BACKENDSFTPPATH_BACKEND, read bysftppath; the old name is not readthe extra pathlib-next[uri]install uripathpathlib-next[http]nothing for pathlib_next.http;uripathforhttp:anddav:paths;pathlib-next-requestsfor arequests.SessionorPATHLIB_NEXT_HTTP_TRANSPORT=requestspathlib-next[sftp]sftppath[paramiko]pathlib-next[sftp-async]sftppath[asyncssh]pathlib-next[s3],[gs],[az]objpath[s3],objpath[gs],objpath[az]github:andgitlab:throughpathlib-next[uri]or[http]repopath[uri];repopathalone forGitHubPathandGitLabPathwith no URIgit://github.com/o/r/f,git+github://host/o/r/f,git+gitlab://host/o/r/frepo://github.com/o/r/f,github://host/o/r/f,gitlab://host/o/r/fthe uripathcommand, installed withpathlib-next[uri]uripath[cli]sftp:,s3:,gs:oraz:paths throughUriPathwith the matching extraadd the uriextra to the provider (sftppath[uri],objpath[uri],repopath[uri]), or installuripathbeside itpathlib_next.path,pathlib_next.glob,pathlib_next.syncand
pathlib_next.testingare now packages whose private modules hold the code;
they export the same names, andpathlib_next.path.Pathis still
pathlib_next.Path. A module or name that starts with an underscore is not
part of the API. Code that builds a class onPathtakes its helpers from
pathlib_next.providers. -
A write stream that commits on close is not committed when its
withblock
raised.FTPPathand the members of a zip (ZipPath,ArchivePath) hold
what is written and send it when the file is closed. Awith path.open("wb") as f:block that wrote part of a file and then raised used to close the
stream on the way out, so the part replaced the complete remote file. Now a
block that ends in an exception (anyBaseException,KeyboardInterrupt
included) discards what was written: the target keeps its previous content, or
is not created, and nothing is sent (noSTORor rewrite; aZipPathinside
batch()does not stage the member, and the block's other changes still
commit). The same holds in text mode, where the text the wrapper still held is
dropped, and forwrite_bytes(),write_text(),copy()andPathSyncer,
which open their target in awithblock. A block that ends normally, and a
stream closed withclose()outside awithblock, commit as before, and an
error raised by the commit still propagates and is not retried.LocalPath
andMemPathwrite through aspathlibdoes and are unchanged: a failed
block leaves what was written. If you relied on the partial upload, close
the stream yourself instead of raising through the block (f.close()commits
what was written). A path of your own whose_open()returns a stream that
commits on close gets the same behaviour by returning
providers.CommitOnClose, or by giving its stream class
providers.DiscardOnErroras its first base and adiscard()that drops what
was written; a stream that hasdiscard()and not the mixin is discarded in
text mode only. A failedopen()of a text file (an unknownencoding=) no
longer creates an empty file on such a backend. -
A path class that does not define
__fspath__is not anos.PathLike.
EveryPathname(MemPath,FTPPath,ZipPath,TarPath,ArchivePath
and any class built onPath) was registered withos.PathLike, so
isinstance(path, os.PathLike)was true whileos.fspath(path),open(path)
andpathlib.Path(path)raisedNotImplementedError: Method not implemented: __fspath__. Now only a class that defines__fspath__is one (LocalPath,
PosixPathname,WindowsPathname, and a class of your own that defines it):
isinstance(MemPath("/a"), os.PathLike)is false and
os.fspath(MemPath("/a"))is aTypeError, as for any object.FsPathLike
stays a protocol for annotations, is no longer a base ofPathnameand is no
longer registered withos.PathLike. What to do: catchTypeErrorwhere you
caughtNotImplementedErrorfromos.fspath(), askisinstance(path, os.PathLike)instead of assuming it, and define__fspath__on a class of
your own that has a local name. -
touch(mode=...)raises where the mode cannot be applied.
MemPath("/f").touch(mode=0o600), andtouch()with a mode on any class
withoutchmod(), created the file and returnedNone, dropping the mode
without a word. It now raisesNotImplementedErrorand removes the file it
created. An existing file is left as it is, its mode included, aspathlib
does. What to do: calltouch()withoutmodeon such a class, andchmod()
where the class has one.
Removed
- The URI layer, its schemes and the
uripathcommand.pathlib_next.uri
(Uri,UriPath,Source,Query, every scheme) andpathlib_next.tools
are gone,pathlib_nextno longer exportsUriorUriPath, and
import pathlib_nextnever looks foruritoolsornetimps. The package
declares no extra butdevanddocs: the extrasuri,http,sftp,
sftp-async,s3,gsandaz, the entry-point group
pathlib_next.schemesand theuripathscript are removed. A project that
imported one of those names, installed one of those extras or ran the command
installs the distribution the table names. Two distributions defining one
script would collide, which is why the command is not kept here.
Full Changelog: v0.9.14...v0.10.0