miniopy-async 1.16__tar.gz → 1.18__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {miniopy-async-1.16 → miniopy-async-1.18}/PKG-INFO +2 -2
  2. {miniopy-async-1.16 → miniopy-async-1.18}/README.md +1 -1
  3. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/__init__.py +1 -1
  4. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/api.py +136 -5
  5. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/commonconfig.py +48 -0
  6. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/helpers.py +35 -44
  7. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/time.py +5 -0
  8. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async.egg-info/PKG-INFO +2 -2
  9. {miniopy-async-1.16 → miniopy-async-1.18}/LICENSE +0 -0
  10. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/credentials/__init__.py +0 -0
  11. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/credentials/credentials.py +0 -0
  12. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/credentials/providers.py +0 -0
  13. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/datatypes.py +0 -0
  14. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/deleteobjects.py +0 -0
  15. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/error.py +0 -0
  16. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/legalhold.py +0 -0
  17. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/lifecycleconfig.py +0 -0
  18. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/minioadmin.py +0 -0
  19. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/notificationconfig.py +0 -0
  20. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/objectlockconfig.py +0 -0
  21. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/progress.py +0 -0
  22. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/replicationconfig.py +0 -0
  23. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/retention.py +0 -0
  24. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/select.py +0 -0
  25. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/signer.py +0 -0
  26. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/sse.py +0 -0
  27. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/sseconfig.py +0 -0
  28. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/tagging.py +0 -0
  29. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/versioningconfig.py +0 -0
  30. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async/xml.py +0 -0
  31. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async.egg-info/SOURCES.txt +0 -0
  32. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async.egg-info/dependency_links.txt +0 -0
  33. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async.egg-info/requires.txt +0 -0
  34. {miniopy-async-1.16 → miniopy-async-1.18}/miniopy_async.egg-info/top_level.txt +0 -0
  35. {miniopy-async-1.16 → miniopy-async-1.18}/setup.cfg +0 -0
  36. {miniopy-async-1.16 → miniopy-async-1.18}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: miniopy-async
3
- Version: 1.16
3
+ Version: 1.18
4
4
  Summary: Asynchronous MinIO Client SDK for Python
5
5
  Home-page: https://github.com/hlf20010508/miniopy-async
6
6
  Author: L-ING
@@ -87,7 +87,7 @@ pipenv install miniopy-async
87
87
 
88
88
  Github Repository
89
89
  ```sh
90
- pipenv install git+https://github.com/hlf20010508/miniopy-async.git#egg=miniopy-async
90
+ pipenv install "miniopy-async@ git+https://github.com/hlf20010508/miniopy-async.git"
91
91
  ```
92
92
 
93
93
  <span id="example"></span>
@@ -60,7 +60,7 @@ pipenv install miniopy-async
60
60
 
61
61
  Github Repository
62
62
  ```sh
63
- pipenv install git+https://github.com/hlf20010508/miniopy-async.git#egg=miniopy-async
63
+ pipenv install "miniopy-async@ git+https://github.com/hlf20010508/miniopy-async.git"
64
64
  ```
65
65
 
66
66
  <span id="example"></span>
@@ -45,7 +45,7 @@ miniopy-async - Asynchronous MinIO Client SDK for Python
45
45
 
46
46
  __title__ = "miniopy-async"
47
47
  __author__ = "L-ING."
48
- __version__ = "1.16"
48
+ __version__ = "1.18"
49
49
  __license__ = "Apache 2.0"
50
50
  __copyright__ = "(C) 2022 L-ING <hlf01@icloud.com>"
51
51
 
@@ -33,6 +33,9 @@ import asyncio
33
33
  import itertools
34
34
  import os
35
35
  import platform
36
+ from random import random
37
+ from io import BytesIO
38
+ import tarfile
36
39
 
37
40
  # import weakref
38
41
  from concurrent.futures import ThreadPoolExecutor
@@ -44,7 +47,7 @@ import aiofile
44
47
  import aiohttp
45
48
 
46
49
  from . import __title__, __version__, time
47
- from .commonconfig import COPY, ComposeSource, CopySource, REPLACE, Tags
50
+ from .commonconfig import COPY, ComposeSource, CopySource, REPLACE, Tags, SnowballObject
48
51
  from .credentials import StaticProvider
49
52
  from .datatypes import (
50
53
  AsyncEventIterable,
@@ -376,7 +379,7 @@ class Minio: # pylint: disable=too-many-public-methods
376
379
  query_params=query_params,
377
380
  session=session,
378
381
  )
379
- return response
382
+ return response
380
383
  else:
381
384
  response = await self._url_open(
382
385
  method,
@@ -407,7 +410,7 @@ class Minio: # pylint: disable=too-many-public-methods
407
410
  query_params=query_params,
408
411
  session=session,
409
412
  )
410
- return response
413
+ return response
411
414
  else:
412
415
  response = await self._url_open(
413
416
  method,
@@ -2502,6 +2505,7 @@ class Minio: # pylint: disable=too-many-public-methods
2502
2505
  object_name,
2503
2506
  ssec=None,
2504
2507
  version_id=None,
2508
+ request_headers=None,
2505
2509
  extra_query_params=None,
2506
2510
  ):
2507
2511
  """
@@ -2511,6 +2515,7 @@ class Minio: # pylint: disable=too-many-public-methods
2511
2515
  :param object_name: Object name in the bucket.
2512
2516
  :param ssec: Server-side encryption customer key.
2513
2517
  :param version_id: Version ID of the object.
2518
+ :param request_headers: Any additional headers to be added with GET request.
2514
2519
  :param extra_query_params: Extra query parameters for advanced usage.
2515
2520
  :return: :class:`Object <Object>`.
2516
2521
 
@@ -2570,6 +2575,8 @@ class Minio: # pylint: disable=too-many-public-methods
2570
2575
  check_ssec(ssec)
2571
2576
 
2572
2577
  headers = ssec.headers() if ssec else {}
2578
+ if request_headers:
2579
+ headers.update(request_headers)
2573
2580
  query_params = extra_query_params or {}
2574
2581
  query_params.update({"versionId": version_id} if version_id else {})
2575
2582
  async with aiohttp.ClientSession() as session:
@@ -3973,8 +3980,11 @@ class Minio: # pylint: disable=too-many-public-methods
3973
3980
  days=10))
3974
3981
 
3975
3982
  async def main():
3976
- await client.set_object_retention("my-bucket", "my-object",
3977
- config)
3983
+ await client.set_object_retention(
3984
+ "my-bucket",
3985
+ "my-object",
3986
+ config
3987
+ )
3978
3988
 
3979
3989
  loop = asyncio.get_event_loop()
3980
3990
  loop.run_until_complete(main())
@@ -3996,6 +4006,127 @@ class Minio: # pylint: disable=too-many-public-methods
3996
4006
  query_params=query_params,
3997
4007
  )
3998
4008
 
4009
+ async def upload_snowball_objects(
4010
+ self,
4011
+ bucket_name,
4012
+ object_list,
4013
+ metadata=None,
4014
+ sse=None,
4015
+ tags=None,
4016
+ retention=None,
4017
+ legal_hold=False,
4018
+ staging_filename=None,
4019
+ compression=False,
4020
+ ):
4021
+ """
4022
+ Uploads multiple objects in a single put call. It is done by creating
4023
+ intermediate TAR file optionally compressed which is uploaded to S3
4024
+ service.
4025
+
4026
+ :param bucket_name: Name of the bucket.
4027
+ :param object_list: An iterable containing
4028
+ :class:`SnowballObject <SnowballObject>` object.
4029
+ :param metadata: Any additional metadata to be uploaded along
4030
+ with your PUT request.
4031
+ :param sse: Server-side encryption.
4032
+ :param tags: :class:`Tags` for the object.
4033
+ :param retention: :class:`Retention` configuration object.
4034
+ :param legal_hold: Flag to set legal hold for the object.
4035
+ :param staging_filename: A staging filename to create intermediate tarball.
4036
+ :param compression: Flag to compress TAR ball.
4037
+ :return: :class:`ObjectWriteResult` object.
4038
+
4039
+ Example::
4040
+ from miniopy_async import Minio
4041
+ from miniopy_async.commonconfig import SnowballObject
4042
+ import io
4043
+ from datetime import datetime
4044
+ import asyncio
4045
+
4046
+ client = Minio(
4047
+ "play.min.io",
4048
+ access_key="Q3AM3UQ867SPQQA43P2F",
4049
+ secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
4050
+ secure=True, # http for False, https for True
4051
+ )
4052
+
4053
+ async def main():
4054
+ result = await client.upload_snowball_objects(
4055
+ "my-bucket",
4056
+ [
4057
+ SnowballObject("my-object1", filename="filename"),
4058
+ SnowballObject(
4059
+ "my-object2", data=io.BytesIO(b"hello"), length=5,
4060
+ ),
4061
+ SnowballObject(
4062
+ "my-object3", data=io.BytesIO(b"world"), length=5,
4063
+ mod_time=datetime.now(),
4064
+ ),
4065
+ ],
4066
+ )
4067
+
4068
+ loop = asyncio.get_event_loop()
4069
+ loop.run_until_complete(main())
4070
+ loop.close()
4071
+ """
4072
+ check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host)
4073
+
4074
+ object_name = f"snowball.{random()}.tar"
4075
+
4076
+ # turn list like objects into an iterator.
4077
+ object_list = itertools.chain(object_list)
4078
+
4079
+ metadata = metadata or {}
4080
+ metadata["X-Amz-Meta-Snowball-Auto-Extract"] = "true"
4081
+
4082
+ name = staging_filename
4083
+ mode = "w:gz" if compression else "w"
4084
+ fileobj = None if name else BytesIO()
4085
+ with tarfile.open(name=name, mode=mode, fileobj=fileobj) as tar:
4086
+ for obj in object_list:
4087
+ if obj.filename:
4088
+ tar.add(obj.filename, obj.object_name)
4089
+ else:
4090
+ info = tarfile.TarInfo(obj.object_name)
4091
+ info.size = obj.length
4092
+ info.mtime = int(
4093
+ time.to_float(obj.mod_time or time.utcnow()),
4094
+ )
4095
+ tar.addfile(info, obj.data)
4096
+
4097
+ if not name:
4098
+ length = fileobj.tell()
4099
+ fileobj.seek(0)
4100
+ else:
4101
+ length = os.stat(name).st_size
4102
+
4103
+ part_size = 0 if length < MIN_PART_SIZE else length
4104
+
4105
+ if name:
4106
+ return await self.fput_object(
4107
+ bucket_name,
4108
+ object_name,
4109
+ staging_filename,
4110
+ metadata=metadata,
4111
+ sse=sse,
4112
+ tags=tags,
4113
+ retention=retention,
4114
+ legal_hold=legal_hold,
4115
+ part_size=part_size,
4116
+ )
4117
+ return await self.put_object(
4118
+ bucket_name,
4119
+ object_name,
4120
+ fileobj,
4121
+ length,
4122
+ metadata=metadata,
4123
+ sse=sse,
4124
+ tags=tags,
4125
+ retention=retention,
4126
+ legal_hold=legal_hold,
4127
+ part_size=part_size,
4128
+ )
4129
+
3999
4130
  async def _list_objects(
4000
4131
  # pylint: disable=too-many-arguments,too-many-branches
4001
4132
  self,
@@ -508,3 +508,51 @@ class ComposeSource(ObjectConditionalReadArgs):
508
508
  src.modified_since,
509
509
  src.unmodified_since,
510
510
  )
511
+
512
+
513
+ class SnowballObject:
514
+ """A source object definition for upload_snowball_objects method."""
515
+
516
+ def __init__(
517
+ self,
518
+ object_name,
519
+ filename=None,
520
+ data=None,
521
+ length=None,
522
+ mod_time=None,
523
+ ):
524
+ self._object_name = object_name
525
+ if (filename is not None) ^ (data is not None):
526
+ self._filename = filename
527
+ self._data = data
528
+ self._length = length
529
+ else:
530
+ raise ValueError("only one of filename or data must be provided")
531
+ if mod_time is not None and not isinstance(mod_time, datetime.datetime):
532
+ raise ValueError("mod_time must be datetime.datetime type")
533
+ self._mod_time = mod_time
534
+
535
+ @property
536
+ def object_name(self):
537
+ """Get object name."""
538
+ return self._object_name
539
+
540
+ @property
541
+ def filename(self):
542
+ """Get filename."""
543
+ return self._filename
544
+
545
+ @property
546
+ def data(self):
547
+ """Get data."""
548
+ return self._data
549
+
550
+ @property
551
+ def length(self):
552
+ """Get length."""
553
+ return self._length
554
+
555
+ @property
556
+ def mod_time(self):
557
+ """Get modification time."""
558
+ return self._mod_time
@@ -43,19 +43,16 @@ MAX_MULTIPART_OBJECT_SIZE = 5 * 1024 * 1024 * 1024 * 1024 # 5TiB
43
43
  MAX_PART_SIZE = 5 * 1024 * 1024 * 1024 # 5GiB
44
44
  MIN_PART_SIZE = 5 * 1024 * 1024 # 5MiB
45
45
 
46
- _VALID_BUCKETNAME_REGEX = re.compile(
47
- "^[A-Za-z0-9][A-Za-z0-9\\.\\-\\_\\:]{1,61}[A-Za-z0-9]$"
46
+ _BUCKET_NAME_REGEX = re.compile(r'^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$')
47
+ _OLD_BUCKET_NAME_REGEX = re.compile(
48
+ r'^[a-z0-9][a-z0-9_\.\-\:]{1,61}[a-z0-9]$',
49
+ re.IGNORECASE
48
50
  )
49
- _VALID_BUCKETNAME_STRICT_REGEX = re.compile("^[a-z0-9][a-z0-9\\.\\-]{1,61}[a-z0-9]$")
50
- _VALID_IP_ADDRESS = re.compile(r"^(\d+\.){3}\d+$")
51
- _ALLOWED_HOSTNAME_REGEX = re.compile(
52
- "^((?!-)(?!_)[A-Z_\\d-]{1,63}(?<!-)(?<!_)\\.)*((?!_)(?!-)"
53
- + "[A-Z_\\d-]{1,63}(?<!-)(?<!_))$",
54
- re.IGNORECASE,
55
- )
56
-
57
- _EXTRACT_REGION_REGEX = re.compile("s3[.-]?(.+?).amazonaws.com")
58
51
 
52
+ _IPV4_REGEX = re.compile(
53
+ r'^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}'
54
+ r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$'
55
+ )
59
56
 
60
57
  def quote(resource, safe="/", encoding=None, errors=None):
61
58
  """
@@ -191,44 +188,38 @@ def makedirs(path):
191
188
  ) from exc
192
189
 
193
190
 
194
- def check_bucket_name(bucket_name, strict=False):
191
+ def check_bucket_name(
192
+ bucket_name: str,
193
+ strict: bool = False,
194
+ s3_check: bool = False,
195
+ ):
195
196
  """Check whether bucket name is valid optional with strict check or not."""
196
197
 
197
- # Verify bucket name is not empty
198
- bucket_name = str(bucket_name).strip()
199
- if not bucket_name:
200
- raise ValueError("Bucket name cannot be empty.")
201
-
202
- # Verify bucket name length.
203
- if len(bucket_name) < 3:
204
- raise ValueError("Bucket name cannot be less than" " 3 characters.")
205
- if len(bucket_name) > 63:
206
- raise ValueError("Bucket name cannot be greater than" " 63 characters.")
198
+ if strict:
199
+ if not _BUCKET_NAME_REGEX.match(bucket_name):
200
+ raise ValueError(f'invalid bucket name {bucket_name}')
201
+ else:
202
+ if not _OLD_BUCKET_NAME_REGEX.match(bucket_name):
203
+ raise ValueError(f'invalid bucket name {bucket_name}')
207
204
 
208
- match = _VALID_IP_ADDRESS.match(bucket_name)
209
- if match:
210
- raise ValueError("Bucket name cannot be an ip address")
205
+ if _IPV4_REGEX.match(bucket_name):
206
+ raise ValueError(f'bucket name {bucket_name} must not be formatted '
207
+ 'as an IP address')
211
208
 
212
- unallowed_successive_chars = ["..", ".-", "-."]
209
+ unallowed_successive_chars = ['..', '.-', '-.']
213
210
  if any(x in bucket_name for x in unallowed_successive_chars):
214
- raise ValueError(
215
- "Bucket name contains invalid "
216
- "successive chars " + str(unallowed_successive_chars) + "."
217
- )
218
-
219
- if strict:
220
- match = _VALID_BUCKETNAME_STRICT_REGEX.match(bucket_name)
221
- if (not match) or match.end() != len(bucket_name):
222
- raise ValueError(
223
- "Bucket name contains invalid " "characters (strictly enforced)."
224
- )
225
-
226
- match = _VALID_BUCKETNAME_REGEX.match(bucket_name)
227
- if (not match) or match.end() != len(bucket_name):
228
- raise ValueError(
229
- "Bucket name does not follow S3 standards."
230
- " Bucket: {0}".format(bucket_name)
231
- )
211
+ raise ValueError(f'bucket name {bucket_name} contains invalid '
212
+ 'successive characters')
213
+
214
+ if (
215
+ s3_check and
216
+ bucket_name.startswith("xn--") or
217
+ bucket_name.endswith("-s3alias") or
218
+ bucket_name.endswith("--ol-s3")
219
+ ):
220
+ raise ValueError(f"bucket name {bucket_name} must not start with "
221
+ "'xn--' and must not end with '--s3alias' or "
222
+ "'--ol-s3'")
232
223
 
233
224
 
234
225
  def check_non_empty_string(string):
@@ -22,6 +22,7 @@ from __future__ import absolute_import
22
22
 
23
23
  import locale
24
24
  from contextlib import contextmanager
25
+ import time as ctime
25
26
  from datetime import datetime, timezone
26
27
 
27
28
  from . import __LOCALE_LOCK__
@@ -96,3 +97,7 @@ def utcnow():
96
97
  def to_signer_date(value):
97
98
  """Format datetime into SignatureV4 date formatted string."""
98
99
  return _to_utc(value).strftime("%Y%m%d")
100
+
101
+ def to_float(value: datetime) -> float:
102
+ """Convert datetime into float value."""
103
+ return ctime.mktime(value.timetuple()) + value.microsecond * 1e-6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: miniopy-async
3
- Version: 1.16
3
+ Version: 1.18
4
4
  Summary: Asynchronous MinIO Client SDK for Python
5
5
  Home-page: https://github.com/hlf20010508/miniopy-async
6
6
  Author: L-ING
@@ -87,7 +87,7 @@ pipenv install miniopy-async
87
87
 
88
88
  Github Repository
89
89
  ```sh
90
- pipenv install git+https://github.com/hlf20010508/miniopy-async.git#egg=miniopy-async
90
+ pipenv install "miniopy-async@ git+https://github.com/hlf20010508/miniopy-async.git"
91
91
  ```
92
92
 
93
93
  <span id="example"></span>
File without changes
File without changes
File without changes