22
33import re
44import sys
5- import typing
65import fnmatch
76import posixpath
87from datetime import timedelta
98from contextlib import suppress
109from urllib .parse import unquote
1110from pathlib import PurePath , Path
12- from typing import Union , Literal , Optional
11+ from typing import TYPE_CHECKING , Literal , Self , Generator
1312from io import DEFAULT_BUFFER_SIZE , TextIOWrapper
1413
1514from botocore .exceptions import ClientError
1615
17- if typing .TYPE_CHECKING :
16+ if TYPE_CHECKING :
17+ from os import PathLike
1818 import smart_open
19- from boto3 .resources .factory import ServiceResource
20- KeyFileObjectType = Union [ TextIOWrapper , smart_open .s3 .Reader , smart_open .s3 .MultipartWriter ]
19+ from boto3 .resources .base import ServiceResource
20+ KeyFileObjectType = TextIOWrapper | smart_open .s3 .Reader | smart_open .s3 .MultipartWriter
2121
2222from . import accessor
2323
2424
2525def register_configuration_parameter (
2626 path : PureS3Path ,
2727 * ,
28- parameters : Optional [ dict ] = None ,
29- resource : Optional [ ServiceResource ] = None ,
30- glob_new_algorithm : Optional [ bool ] = None ):
28+ parameters : dict | None = None ,
29+ resource : ServiceResource | None = None ,
30+ glob_new_algorithm : bool | None = None ):
3131 if not isinstance (path , PureS3Path ):
3232 raise TypeError (f'path argument have to be a { PurePath } type. got { type (path )} ' )
3333 if parameters and not isinstance (parameters , dict ):
@@ -74,7 +74,7 @@ def __init__(self, *args):
7474 self ._load_parts ()
7575
7676 @classmethod
77- def from_uri (cls , uri : str ):
77+ def from_uri (cls , uri : str ) -> PureS3Path :
7878 """
7979 from_uri class method create a class instance from url
8080
@@ -88,7 +88,7 @@ def from_uri(cls, uri: str):
8888 return cls (unquoted_uri [4 :])
8989
9090 @classmethod
91- def from_bucket_key (cls , bucket : str , key : str ) :
91+ def from_bucket_key (cls , bucket : str | PathLike , key : str | PathLike ) -> PureS3Path :
9292 """
9393 from_bucket_key class method create a class instance from bucket, key pair's
9494
@@ -279,7 +279,7 @@ def is_mount(self) -> Literal[False]:
279279
280280
281281class S3Path (_PathNotSupportedMixin , PureS3Path , Path ):
282- def stat (self , * , follow_symlinks : bool = True ) -> accessor .StatResult :
282+ def stat (self , * , follow_symlinks : bool = True ) -> accessor .StatResult | None :
283283 """
284284 Returns information about this path (similarly to boto3's ObjectSummary).
285285 For compatibility with pathlib, the returned object some similar attributes like os.stat_result.
@@ -294,7 +294,7 @@ def stat(self, *, follow_symlinks: bool = True) -> accessor.StatResult:
294294 return None
295295 return accessor .stat (self , follow_symlinks = follow_symlinks )
296296
297- def absolute (self ) -> S3Path :
297+ def absolute (self ) -> Self :
298298 """
299299 Handle absolute method only if the path is already an absolute one
300300 since we have no way to compute an absolute path from a relative one in S3.
@@ -304,17 +304,19 @@ def absolute(self) -> S3Path:
304304 # We can't compute the absolute path from a relative one
305305 raise ValueError ("Absolute path can't be determined for relative S3Path objects" )
306306
307- def owner (self ) -> str :
307+ def owner (self , * , follow_symlinks : bool = False ) -> str :
308308 """
309309 Returns the name of the user owning the Bucket or key.
310310 Similarly to boto3's ObjectSummary owner attribute
311311 """
312312 self ._absolute_path_validation ()
313+ if follow_symlinks :
314+ raise NotImplementedError (f'Setting follow_symlinks to { follow_symlinks } is unsupported on S3 service.' )
313315 if not self .is_file ():
314316 raise KeyError ('file not found' )
315317 return accessor .owner (self )
316318
317- def rename (self , target ):
319+ def rename (self , target ) -> S3Path :
318320 """
319321 Renames this file or Bucket / key prefix / key to the given target.
320322 If target exists and is a file, it will be replaced silently if the user has permission.
@@ -328,7 +330,7 @@ def rename(self, target):
328330 accessor .rename (self , target )
329331 return type (self )(target )
330332
331- def replace (self , target ):
333+ def replace (self , target ) -> S3Path :
332334 """
333335 Renames this Bucket / key prefix / key to the given target.
334336 If target points to an existing Bucket / key prefix / key, it will be unconditionally replaced.
@@ -346,13 +348,13 @@ def rmdir(self):
346348 raise FileNotFoundError ()
347349 accessor .rmdir (self )
348350
349- def samefile (self , other_path : Union [ str , S3Path ] ) -> bool :
351+ def samefile (self , other_path : str | PathLike ) -> bool :
350352 """
351353 Returns whether this path points to the same Bucket key as other_path,
352354 Which can be either a Path object, or a string
353355 """
354356 self ._absolute_path_validation ()
355- if not isinstance (other_path , Path ):
357+ if not isinstance (other_path , S3Path ):
356358 other_path = type (self )(other_path )
357359 return self .bucket == other_path .bucket and self .key == other_path .key and self .is_file ()
358360
@@ -393,41 +395,47 @@ def mkdir(self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
393395 if not exist_ok :
394396 raise
395397
396- def is_dir (self ) -> bool :
398+ def is_dir (self , * , follow_symlinks : bool = False ) -> bool :
397399 """
398400 Returns True if the path points to a Bucket or a key prefix, False if it points to a full key path.
399401 False is also returned if the path doesn’t exist.
400402 Other errors (such as permission errors) are propagated.
401403 """
402404 self ._absolute_path_validation ()
405+ if follow_symlinks :
406+ raise NotImplementedError (f'Setting follow_symlinks to { follow_symlinks } is unsupported on S3 service.' )
403407 if self .bucket and not self .key :
404408 return True
405409 return accessor .is_dir (self )
406410
407- def is_file (self ) -> bool :
411+ def is_file (self , * , follow_symlinks : bool = False ) -> bool :
408412 """
409413 Returns True if the path points to a Bucket key, False if it points to Bucket or a key prefix.
410414 False is also returned if the path doesn’t exist.
411415 Other errors (such as permission errors) are propagated.
412416 """
413417 self ._absolute_path_validation ()
418+ if follow_symlinks :
419+ raise NotImplementedError (f'Setting follow_symlinks to { follow_symlinks } is unsupported on S3 service.' )
414420 if not self .bucket or not self .key :
415421 return False
416422 try :
417423 return bool (self .stat ())
418424 except ClientError :
419425 return False
420426
421- def exists (self ) -> bool :
427+ def exists (self , * , follow_symlinks : bool = False ) -> bool :
422428 """
423429 Whether the path points to an existing Bucket, key or key prefix.
424430 """
425431 self ._absolute_path_validation ()
432+ if follow_symlinks :
433+ raise NotImplementedError (f'Setting follow_symlinks to { follow_symlinks } is unsupported on S3 service.' )
426434 if not self .bucket :
427435 return True
428436 return accessor .exists (self )
429437
430- def iterdir (self ):
438+ def iterdir (self ) -> Generator [ 'S3Path' ] :
431439 """
432440 When the path points to a Bucket or a key prefix, yield path objects of the directory contents
433441 """
@@ -441,9 +449,9 @@ def open(
441449 self ,
442450 mode : Literal ['r' , 'w' , 'rb' , 'wb' ] = 'r' ,
443451 buffering : int = DEFAULT_BUFFER_SIZE ,
444- encoding : Optional [ str ] = None ,
445- errors : Optional [ str ] = None ,
446- newline : Optional [ str ] = None ) -> KeyFileObjectType :
452+ encoding : str | None = None ,
453+ errors : str | None = None ,
454+ newline : str | None = None ) -> KeyFileObjectType :
447455 """
448456 Opens the Bucket key pointed to by the path, returns a Key file object that you can read/write with
449457 """
@@ -458,7 +466,11 @@ def open(
458466 errors = errors ,
459467 newline = newline )
460468
461- def glob (self , pattern : str , * , case_sensitive = None , recurse_symlinks = False ):
469+ def glob (
470+ self ,
471+ pattern : str , * ,
472+ case_sensitive : bool | None = None ,
473+ recurse_symlinks : bool = False ) -> Generator ['S3Path' ]:
462474 """
463475 Glob the given relative pattern in the Bucket / key prefix represented by this path,
464476 yielding all matching files (of any kind)
@@ -481,7 +493,11 @@ def glob(self, pattern: str, *, case_sensitive=None, recurse_symlinks=False):
481493 selector = _Selector (self , pattern = pattern )
482494 yield from selector .select ()
483495
484- def rglob (self , pattern : str , * , case_sensitive = None , recurse_symlinks = False ):
496+ def rglob (
497+ self ,
498+ pattern : str , * ,
499+ case_sensitive : bool | None = None ,
500+ recurse_symlinks : bool = False ) -> Generator ['S3Path' ]:
485501 """
486502 This is like calling S3Path.glob with "**/" added in front of the given relative pattern
487503
@@ -502,7 +518,7 @@ def rglob(self, pattern: str, *, case_sensitive=None, recurse_symlinks=False):
502518 selector = _Selector (self , pattern = pattern )
503519 yield from selector .select ()
504520
505- def get_presigned_url (self , expire_in : Union [ timedelta , int ] = 3600 ) -> str :
521+ def get_presigned_url (self , expire_in : timedelta | int = 3600 ) -> str :
506522 """
507523 Returns a pre-signed url. Anyone with the url can make a GET request to get the file.
508524 You can set an expiration date with the expire_in argument (integer or timedelta object).
@@ -573,7 +589,11 @@ def unlink(self, missing_ok: bool = False):
573589 if not missing_ok :
574590 raise
575591
576- def walk (self , top_down : bool = True , on_error :bool = None , follow_symlinks : bool = False ):
592+ def walk (
593+ self ,
594+ top_down : bool = True ,
595+ on_error :bool = None ,
596+ follow_symlinks : bool = False ) -> Generator [tuple ['S3Path' , list [str ], list [str ]]]:
577597 if follow_symlinks :
578598 raise NotImplementedError (f'Setting follow_symlinks to { follow_symlinks } is unsupported on S3 service.' )
579599
@@ -612,7 +632,7 @@ def __rtruediv__(self, key):
612632 return new_path
613633
614634 @classmethod
615- def from_uri (cls , uri : str , * , version_id : str ):
635+ def from_uri (cls , uri : str , * , version_id : str ) -> PureVersionedS3Path :
616636 """
617637 from_uri class method creates a class instance from uri and version id
618638
@@ -625,7 +645,7 @@ def from_uri(cls, uri: str, *, version_id: str):
625645 return cls (self , version_id = version_id )
626646
627647 @classmethod
628- def from_bucket_key (cls , bucket : str , key : str , * , version_id : str ):
648+ def from_bucket_key (cls , bucket : str , key : str , * , version_id : str ) -> PureVersionedS3Path :
629649 """
630650 from_bucket_key class method creates a class instance from bucket, key and version id
631651
@@ -637,7 +657,7 @@ def from_bucket_key(cls, bucket: str, key: str, *, version_id: str):
637657 self = PureS3Path .from_bucket_key (bucket = bucket , key = key )
638658 return cls (self , version_id = version_id )
639659
640- def with_segments (self , * pathsegments ):
660+ def with_segments (self , * pathsegments ) -> PureVersionedS3Path :
641661 """Construct a new path object from any number of path-like objects.
642662 Subclasses may override this method to customize how new path objects
643663 are created from methods like `iterdir()`.
0 commit comments