Skip to content

Commit 228178d

Browse files
committed
add some type hints
1 parent 57c5c5b commit 228178d

File tree

6 files changed

+64
-48
lines changed

6 files changed

+64
-48
lines changed

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ twine = "*"
1212
pytest-cov = "*"
1313
smart-open = "*"
1414
packaging = "*"
15+
mypy = "*"
1516

1617
[dev-packages]
1718
ipython = "*"

s3path/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pathlib import Path
66
from . import accessor
77

8-
__version__ = '0.6.2'
8+
__version__ = '0.6.3'
99
__all__ = (
1010
'Path',
1111
'register_configuration_parameter',

s3path/current_version.py

Lines changed: 51 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,32 @@
22

33
import re
44
import sys
5-
import typing
65
import fnmatch
76
import posixpath
87
from datetime import timedelta
98
from contextlib import suppress
109
from urllib.parse import unquote
1110
from pathlib import PurePath, Path
12-
from typing import Union, Literal, Optional
11+
from typing import TYPE_CHECKING, Literal, Self, Generator
1312
from io import DEFAULT_BUFFER_SIZE, TextIOWrapper
1413

1514
from 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

2222
from . import accessor
2323

2424

2525
def 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

281281
class 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()`.

s3path/old_versions.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -434,14 +434,13 @@ def _boto3_method_with_parameters(self, boto3_method, config=None, args=(), kwar
434434
return boto3_method(*args, **kwargs)
435435

436436
def _boto3_method_with_extraargs(
437-
self,
438-
boto3_method,
439-
config=None,
440-
args=(),
441-
kwargs=None,
442-
extra_args=None,
443-
allowed_extra_args=(),
444-
):
437+
self,
438+
boto3_method,
439+
config=None,
440+
args=(),
441+
kwargs=None,
442+
extra_args=None,
443+
allowed_extra_args=()):
445444
kwargs = kwargs or {}
446445
extra_args = extra_args or {}
447446
if config is not None:
@@ -459,8 +458,7 @@ def _update_smart_open_kwargs(
459458
resource,
460459
config,
461460
transport_params,
462-
smart_open_kwargs,
463-
):
461+
smart_open_kwargs):
464462
"""
465463
New Smart-Open (>=5.1.0) api
466464
Doc: https://github.com/RaRe-Technologies/smart_open/blob/develop/MIGRATING_FROM_OLDER_VERSIONS.rst
@@ -725,8 +723,7 @@ def register_configuration_parameter(
725723
*,
726724
parameters: Optional[dict] = None,
727725
resource: Optional[ServiceResource] = None,
728-
glob_new_algorithm: Optional[bool] = None,
729-
):
726+
glob_new_algorithm: Optional[bool] = None):
730727
if not isinstance(path, PureS3Path):
731728
raise TypeError(f'path argument have to be a {PurePath} type. got {type(path)}')
732729
if parameters and not isinstance(parameters, dict):
@@ -964,8 +961,6 @@ def open(
964961
Opens the Bucket key pointed to by the path, returns a Key file object that you can read/write with
965962
"""
966963
self._absolute_path_validation()
967-
if smart_open.__version__ < '4.0.0' and mode.startswith('b'):
968-
mode = ''.join(reversed(mode))
969964
return self._accessor.open(
970965
self,
971966
mode=mode,

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
long_description = fh.read()
66
setup(
77
name='s3path',
8-
version='0.6.2',
8+
version='0.6.3',
99
url='https://github.com/liormizr/s3path',
1010
author='Lior Mizrahi',
1111
author_email='li.mizr@gmail.com',

tests/test_path_operations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def test_read_lines_hint(s3_mock):
402402
object_summary.put(Body=b'test data\ntest data')
403403

404404
with S3Path('/test-bucket/directory/Test.test').open() as fp:
405-
assert len(fp.readlines(1)) == (1 if sys.version_info >= (3, 6) else 2)
405+
assert len(fp.readlines(1)) == 1
406406

407407
with S3Path('/test-bucket/directory/Test.test').open('br') as fp:
408408
assert len(fp.readlines(1)) == 1 # work only in binary mode

0 commit comments

Comments
 (0)