Skip to content

Commit 7c25d03

Browse files
authored
Fix upload config compatibility and improve test cases (#447)
1 parent 9c087ba commit 7c25d03

28 files changed

+1340
-485
lines changed

.github/workflows/ci-test.yml

+18-5
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,37 @@ jobs:
3535
wget -qLO get-pip.py "$PIP_BOOTSTRAP_SCRIPT_PREFIX/$MAJOR.$MINOR/get-pip.py"
3636
python get-pip.py --user
3737
fi
38+
- name: Setup mock server
39+
shell: bash -el {0}
40+
run: |
41+
conda create -y -n mock-server python=3.10
42+
conda activate mock-server
43+
python3 --version
44+
nohup python3 tests/mock_server/main.py --port 9000 > py-mock-server.log &
45+
echo $! > mock-server.pid
46+
conda deactivate
3847
- name: Install dependencies
3948
shell: bash -l {0}
4049
run: |
4150
python -m pip install --upgrade pip
4251
python -m pip install -I -e ".[dev]"
4352
- name: Run cases
44-
shell: bash -l {0}
53+
shell: bash -el {0}
4554
env:
4655
QINIU_ACCESS_KEY: ${{ secrets.QINIU_ACCESS_KEY }}
4756
QINIU_SECRET_KEY: ${{ secrets.QINIU_SECRET_KEY }}
4857
QINIU_TEST_BUCKET: ${{ secrets.QINIU_TEST_BUCKET }}
4958
QINIU_TEST_DOMAIN: ${{ secrets.QINIU_TEST_DOMAIN }}
5059
QINIU_TEST_ENV: "travis"
60+
MOCK_SERVER_ADDRESS: "http://127.0.0.1:9000"
5161
PYTHONPATH: "$PYTHONPATH:."
5262
run: |
53-
set -e
54-
flake8 --show-source --max-line-length=160 .
55-
py.test --cov qiniu
63+
flake8 --show-source --max-line-length=160 ./qiniu
64+
coverage run -m pytest ./test_qiniu.py ./tests/cases
5665
ocular --data-file .coverage
57-
coverage run test_qiniu.py
5866
codecov
67+
cat mock-server.pid | xargs kill
68+
- name: Print mock server log
69+
if: ${{ failure() }}
70+
run: |
71+
cat py-mock-server.log

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
## 7.13.1(2024-02-04)
3+
* 对象存储,修复上传部分配置项的兼容
4+
25
## 7.13.0(2023-12-11)
36
* 对象存储,新增支持归档直读存储
47
* 对象存储,批量操作支持自动查询 rs 服务域名

qiniu/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
# flake8: noqa
1111

12-
__version__ = '7.13.0'
12+
__version__ = '7.13.1'
1313

1414
from .auth import Auth, QiniuMacAuth
1515

qiniu/compat.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
# ---------
3434

3535
if is_py2:
36+
from urllib import urlencode # noqa
3637
from urlparse import urlparse # noqa
3738
import StringIO
3839
StringIO = BytesIO = StringIO.StringIO
@@ -60,7 +61,7 @@ def is_seekable(data):
6061
return False
6162

6263
elif is_py3:
63-
from urllib.parse import urlparse # noqa
64+
from urllib.parse import urlparse, urlencode # noqa
6465
import io
6566
StringIO = io.StringIO
6667
BytesIO = io.BytesIO

qiniu/http/__init__.py

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: utf-8 -*-
22
import logging
33
import platform
4+
import functools
45

56
import requests
67
from requests.adapters import HTTPAdapter
@@ -21,6 +22,19 @@
2122
)
2223

2324

25+
# compatibility with some config from qiniu.config
26+
def _before_send(func):
27+
@functools.wraps(func)
28+
def wrapper(self, *args, **kwargs):
29+
if _session is None:
30+
_init()
31+
return func(self, *args, **kwargs)
32+
33+
return wrapper
34+
35+
36+
qn_http_client.send_request = _before_send(qn_http_client.send_request)
37+
2438
_sys_info = '{0}; {1}'.format(platform.system(), platform.machine())
2539
_python_ver = platform.python_version()
2640

qiniu/http/client.py

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import requests
55

6+
from qiniu.config import get_default
67
from .response import ResponseInfo
78
from .middleware import compose_middleware
89

@@ -14,6 +15,9 @@ def __init__(self, middlewares=None, send_opts=None):
1415
self.send_opts = {} if send_opts is None else send_opts
1516

1617
def _wrap_send(self, req, **kwargs):
18+
# compatibility with setting timeout by qiniu.config.set_default
19+
kwargs.setdefault('timeout', get_default('connection_timeout'))
20+
1721
resp = self.session.send(req.prepare(), **kwargs)
1822
return ResponseInfo(resp, None)
1923

qiniu/http/response.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ def ok(self):
4646
return self.status_code // 100 == 2
4747

4848
def need_retry(self):
49-
if 0 < self.status_code < 500:
49+
if 100 <= self.status_code < 500:
50+
return False
51+
if all([
52+
self.status_code < 0,
53+
self.exception is not None,
54+
'BadStatusLine' in str(self.exception)
55+
]):
5056
return False
5157
# https://developer.qiniu.com/fusion/kb/1352/the-http-request-return-a-status-code
5258
if self.status_code in [

qiniu/services/storage/uploaders/abc/resume_uploader_base.py

+2
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ def initial_parts(
145145
data_size,
146146
modify_time,
147147
part_size,
148+
file_name,
148149
**kwargs
149150
):
150151
"""
@@ -157,6 +158,7 @@ def initial_parts(
157158
data_size: int
158159
modify_time: int
159160
part_size: int
161+
file_name: str
160162
kwargs: dict
161163
162164
Returns

qiniu/services/storage/uploaders/abc/uploader_base.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def _get_regions(self):
8484
if self.regions:
8585
return self.regions
8686

87+
# handle compatibility for default_zone
8788
default_region = config.get_default('default_zone')
8889
if default_region:
8990
self.regions = [default_region]
@@ -108,11 +109,14 @@ def _get_up_hosts(self, access_key=None):
108109
if not regions:
109110
raise ValueError('No region available.')
110111

111-
if regions[0].up_host and regions[0].up_host_backup:
112-
return [
113-
regions[0].up_host,
114-
regions[0].up_host_backup
115-
]
112+
# get up hosts in region
113+
up_hosts = [
114+
regions[0].up_host,
115+
regions[0].up_host_backup
116+
]
117+
up_hosts = [h for h in up_hosts if h]
118+
if up_hosts:
119+
return up_hosts
116120

117121
# this is correct, it does return hosts. bad function name by legacy
118122
return regions[0].get_up_host(

qiniu/services/storage/uploaders/resume_uploader_v1.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import math
23
from collections import namedtuple
34
from concurrent import futures
45
from io import BytesIO
@@ -65,7 +66,11 @@ def _recover_from_record(
6566
record_context = [
6667
ctx
6768
for ctx in record_context
68-
if ctx.get('expired_at', 0) > now
69+
if (
70+
ctx.get('expired_at', 0) > now and
71+
ctx.get('part_no', None) and
72+
ctx.get('ctx', None)
73+
)
6974
]
7075

7176
# assign to context
@@ -173,6 +178,7 @@ def initial_parts(
173178
data=None,
174179
modify_time=None,
175180
data_size=None,
181+
file_name=None,
176182
**kwargs
177183
):
178184
"""
@@ -184,6 +190,7 @@ def initial_parts(
184190
data
185191
modify_time
186192
data_size
193+
file_name
187194
188195
kwargs
189196
@@ -222,7 +229,8 @@ def initial_parts(
222229
)
223230

224231
# try to recover from record
225-
file_name = path.basename(file_path) if file_path else None
232+
if not file_name and file_path:
233+
file_name = path.basename(file_path)
226234
context = self._recover_from_record(
227235
file_name,
228236
key,
@@ -275,7 +283,10 @@ def upload_parts(
275283

276284
# initial upload state
277285
part, resp = None, None
278-
uploaded_size = 0
286+
uploaded_size = context.part_size * len(context.parts)
287+
if math.ceil(data_size / context.part_size) in [p.part_no for p in context.parts]:
288+
# if last part uploaded, should correct the uploaded size
289+
uploaded_size += (data_size % context.part_size) - context.part_size
279290
lock = Lock()
280291

281292
if not self.concurrent_executor:
@@ -469,6 +480,7 @@ def upload(
469480
up_token,
470481
key,
471482
file_path=file_path,
483+
file_name=file_name,
472484
data=data,
473485
data_size=data_size,
474486
modify_time=modify_time,

qiniu/services/storage/uploaders/resume_uploader_v2.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
from collections import namedtuple
23
from concurrent import futures
34
from io import BytesIO
@@ -65,7 +66,7 @@ def _recover_from_record(
6566
if (
6667
not record_upload_id or
6768
record_modify_time != context.modify_time or
68-
record_expired_at > time()
69+
record_expired_at < time()
6970
):
7071
return context
7172

@@ -80,6 +81,10 @@ def _recover_from_record(
8081
etag=p['etag']
8182
)
8283
for p in record_etags
84+
if (
85+
p.get('partNumber', None) and
86+
p.get('etag', None)
87+
)
8388
],
8489
resumed=True
8590
)
@@ -105,7 +110,7 @@ def _set_to_record(self, file_name, key, context):
105110
'etags': [
106111
{
107112
'etag': part.etag,
108-
'part_no': part.part_no
113+
'partNumber': part.part_no
109114
}
110115
for part in context.parts
111116
]
@@ -170,6 +175,7 @@ def initial_parts(
170175
data_size=None,
171176
modify_time=None,
172177
part_size=None,
178+
file_name=None,
173179
**kwargs
174180
):
175181
"""
@@ -183,6 +189,7 @@ def initial_parts(
183189
data_size: int
184190
modify_time: int
185191
part_size: int
192+
file_name: str
186193
kwargs
187194
188195
Returns
@@ -222,7 +229,8 @@ def initial_parts(
222229
)
223230

224231
# try to recover from record
225-
file_name = path.basename(file_path) if file_path else None
232+
if not file_name and file_path:
233+
file_name = path.basename(file_path)
226234
context = self._recover_from_record(
227235
file_name,
228236
key,
@@ -307,7 +315,10 @@ def upload_parts(
307315

308316
# initial upload state
309317
part, resp = None, None
310-
uploaded_size = 0
318+
uploaded_size = context.part_size * len(context.parts)
319+
if math.ceil(data_size / context.part_size) in [p.part_no for p in context.parts]:
320+
# if last part uploaded, should correct the uploaded size
321+
uploaded_size += (data_size % context.part_size) - context.part_size
311322
lock = Lock()
312323

313324
if not self.concurrent_executor:
@@ -500,6 +511,7 @@ def upload(
500511
up_token,
501512
key,
502513
file_path=file_path,
514+
file_name=file_name,
503515
data=data,
504516
data_size=data_size,
505517
modify_time=modify_time,

0 commit comments

Comments
 (0)