99 PosixPath ,
1010 PurePosixPath ,
1111 WindowsPath ,
12- _PathParents ,
1312)
1413
1514import shutil
1615import sys
16+ from types import MethodType
1717from typing import (
1818 BinaryIO ,
1919 Literal ,
5656else :
5757 from typing_extensions import Self
5858
59- if sys .version_info >= (3 , 12 ):
59+
60+ if sys .version_info < (3 , 12 ):
61+ from pathlib import _posix_flavour # type: ignore[attr-defined] # noqa: F811
62+ from pathlib import _make_selector as _make_selector_pathlib # type: ignore[attr-defined] # noqa: F811
63+ from pathlib import _PathParents # type: ignore[attr-defined]
64+
65+ def _make_selector (pattern_parts , _flavour , case_sensitive = True ): # noqa: F811
66+ return _make_selector_pathlib (tuple (pattern_parts ), _flavour )
67+
68+ elif sys .version_info [:2 ] == (3 , 12 ):
69+ from pathlib import _PathParents # type: ignore[attr-defined]
6070 from pathlib import posixpath as _posix_flavour # type: ignore[attr-defined]
6171 from pathlib import _make_selector # type: ignore[attr-defined]
62- else :
63- from pathlib import _posix_flavour # type: ignore[attr-defined]
64- from pathlib import _make_selector as _make_selector_pathlib # type: ignore[attr-defined]
72+ elif sys . version_info >= ( 3 , 13 ) :
73+ from pathlib . _local import _PathParents
74+ import posixpath as _posix_flavour # type: ignore[attr-defined] # noqa: F811
6575
66- def _make_selector (pattern_parts , _flavour , case_sensitive = True ):
67- return _make_selector_pathlib (tuple (pattern_parts ), _flavour )
76+ from .legacy .glob import _make_selector # noqa: F811
6877
6978
7079from cloudpathlib .enums import FileCacheMode
7180
7281from . import anypath
73-
7482from .exceptions import (
7583 ClientMismatchError ,
7684 CloudPathFileExistsError ,
@@ -194,7 +202,12 @@ def __init__(cls, name: str, bases: Tuple[type, ...], dic: Dict[str, Any]) -> No
194202 and getattr (getattr (Path , attr ), "__doc__" , None )
195203 ):
196204 docstring = getattr (Path , attr ).__doc__ + " _(Docstring copied from pathlib.Path)_"
197- getattr (cls , attr ).__doc__ = docstring
205+
206+ if isinstance (getattr (cls , attr ), (MethodType )):
207+ getattr (cls , attr ).__func__ .__doc__ = docstring
208+ else :
209+ getattr (cls , attr ).__doc__ = docstring
210+
198211 if isinstance (getattr (cls , attr ), property ):
199212 # Properties have __doc__ duplicated under fget, and at least some parsers
200213 # read it from there.
@@ -383,16 +396,6 @@ def drive(self) -> str:
383396 """For example "bucket" on S3 or "container" on Azure; needs to be defined for each class"""
384397 pass
385398
386- @abc .abstractmethod
387- def is_dir (self ) -> bool :
388- """Should be implemented without requiring a dir is downloaded"""
389- pass
390-
391- @abc .abstractmethod
392- def is_file (self ) -> bool :
393- """Should be implemented without requiring that the file is downloaded"""
394- pass
395-
396399 @abc .abstractmethod
397400 def mkdir (self , parents : bool = False , exist_ok : bool = False ) -> None :
398401 """Should be implemented using the client API without requiring a dir is downloaded"""
@@ -427,24 +430,44 @@ def as_uri(self) -> str:
427430 def exists (self ) -> bool :
428431 return self .client ._exists (self )
429432
433+ def is_dir (self , follow_symlinks = True ) -> bool :
434+ return self .client ._is_file_or_dir (self ) == "dir"
435+
436+ def is_file (self , follow_symlinks = True ) -> bool :
437+ return self .client ._is_file_or_dir (self ) == "file"
438+
430439 @property
431440 def fspath (self ) -> str :
432441 return self .__fspath__ ()
433442
434- def _glob_checks (self , pattern : str ) -> None :
435- if ".." in pattern :
443+ @classmethod
444+ def from_uri (cls , uri : str ) -> Self :
445+ return cls (uri )
446+
447+ def _glob_checks (self , pattern : Union [str , os .PathLike ]) -> str :
448+ if isinstance (pattern , os .PathLike ):
449+ if isinstance (pattern , CloudPath ):
450+ str_pattern = str (pattern .relative_to (self ))
451+ else :
452+ str_pattern = os .fspath (pattern )
453+ else :
454+ str_pattern = str (pattern )
455+
456+ if ".." in str_pattern :
436457 raise CloudPathNotImplementedError (
437458 "Relative paths with '..' not supported in glob patterns."
438459 )
439460
440- if pattern .startswith (self .cloud_prefix ) or pattern .startswith ("/" ):
461+ if str_pattern .startswith (self .cloud_prefix ) or str_pattern .startswith ("/" ):
441462 raise CloudPathNotImplementedError ("Non-relative patterns are unsupported" )
442463
443464 if self .drive == "" :
444465 raise CloudPathNotImplementedError (
445466 ".glob is only supported within a bucket or container; you can use `.iterdir` to list buckets; for example, CloudPath('s3://').iterdir()"
446467 )
447468
469+ return str_pattern
470+
448471 def _build_subtree (self , recursive ):
449472 # build a tree structure for all files out of default dicts
450473 Tree : Callable = lambda : defaultdict (Tree )
@@ -488,9 +511,9 @@ def _glob(self, selector, recursive: bool) -> Generator[Self, None, None]:
488511 yield (self / str (p )[len (self .name ) + 1 :])
489512
490513 def glob (
491- self , pattern : str , case_sensitive : Optional [bool ] = None
514+ self , pattern : Union [ str , os . PathLike ] , case_sensitive : Optional [bool ] = None
492515 ) -> Generator [Self , None , None ]:
493- self ._glob_checks (pattern )
516+ pattern = self ._glob_checks (pattern )
494517
495518 pattern_parts = PurePosixPath (pattern ).parts
496519 selector = _make_selector (
@@ -505,9 +528,9 @@ def glob(
505528 )
506529
507530 def rglob (
508- self , pattern : str , case_sensitive : Optional [bool ] = None
531+ self , pattern : Union [ str , os . PathLike ] , case_sensitive : Optional [bool ] = None
509532 ) -> Generator [Self , None , None ]:
510- self ._glob_checks (pattern )
533+ pattern = self ._glob_checks (pattern )
511534
512535 pattern_parts = PurePosixPath (pattern ).parts
513536 selector = _make_selector (
@@ -812,8 +835,13 @@ def read_bytes(self) -> bytes:
812835 with self .open (mode = "rb" ) as f :
813836 return f .read ()
814837
815- def read_text (self , encoding : Optional [str ] = None , errors : Optional [str ] = None ) -> str :
816- with self .open (mode = "r" , encoding = encoding , errors = errors ) as f :
838+ def read_text (
839+ self ,
840+ encoding : Optional [str ] = None ,
841+ errors : Optional [str ] = None ,
842+ newline : Optional [str ] = None ,
843+ ) -> str :
844+ with self .open (mode = "r" , encoding = encoding , errors = errors , newline = newline ) as f :
817845 return f .read ()
818846
819847 def is_junction (self ):
@@ -904,6 +932,19 @@ def is_relative_to(self, other: Self) -> bool:
904932 def name (self ) -> str :
905933 return self ._dispatch_to_path ("name" )
906934
935+ def full_match (self , pattern : str , case_sensitive : Optional [bool ] = None ) -> bool :
936+ if sys .version_info < (3 , 13 ):
937+ raise NotImplementedError ("full_match requires Python 3.13 or higher" )
938+
939+ # strip scheme from start of pattern before testing
940+ if pattern .startswith (self .anchor + self .drive ):
941+ pattern = pattern [len (self .anchor + self .drive ) :]
942+
943+ # remove drive, which is kept on normal dispatch to pathlib
944+ return PurePosixPath (self ._no_prefix_no_drive ).full_match ( # type: ignore[attr-defined]
945+ pattern , case_sensitive = case_sensitive
946+ )
947+
907948 def match (self , path_pattern : str , case_sensitive : Optional [bool ] = None ) -> bool :
908949 # strip scheme from start of pattern before testing
909950 if path_pattern .startswith (self .anchor + self .drive + "/" ):
@@ -916,6 +957,13 @@ def match(self, path_pattern: str, case_sensitive: Optional[bool] = None) -> boo
916957
917958 return self ._dispatch_to_path ("match" , path_pattern , ** kwargs )
918959
960+ @property
961+ def parser (self ) -> Self :
962+ if sys .version_info < (3 , 13 ):
963+ raise NotImplementedError ("parser requires Python 3.13 or higher" )
964+
965+ return self ._dispatch_to_path ("parser" )
966+
919967 @property
920968 def parent (self ) -> Self :
921969 return self ._dispatch_to_path ("parent" )
0 commit comments