Skip to content

Commit 1587507

Browse files
committed
feat: add idle-time fop support and get fop status
1 parent 908228c commit 1587507

File tree

8 files changed

+206
-71
lines changed

8 files changed

+206
-71
lines changed

qiniu/auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
str('persistentOps'), # 持久化处理操作
3838
str('persistentNotifyUrl'), # 持久化处理结果通知URL
3939
str('persistentPipeline'), # 持久化处理独享队列
40-
str('persistentType'), # 指定是否开始闲时任务
40+
str('persistentType'), # 为 `1` 时,开启闲时任务,必须是 int 类型
41+
4142
str('deleteAfterDays'), # 文件多少天后自动删除
4243
str('fileType'), # 文件的存储类型,0为标准存储,1为低频存储,2为归档存储,3为深度归档存储,4为归档直读存储
4344
str('isPrefixalScope'), # 指定上传文件必须使用的前缀

qiniu/services/processing/pfop.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,56 @@ def __init__(self, auth, bucket, pipeline=None, notify_url=None):
2424
self.pipeline = pipeline
2525
self.notify_url = notify_url
2626

27-
def execute(self, key, fops, force=None):
28-
"""执行持久化处理:
29-
30-
Args:
31-
key: 待处理的源文件
32-
fops: 处理详细操作,规格详见 https://developer.qiniu.com/dora/manual/1291/persistent-data-processing-pfop
33-
force: 强制执行持久化处理开关
27+
def execute(self, key, fops, force=None, persistent_type=None):
28+
"""
29+
执行持久化处理
3430
35-
Returns:
36-
一个dict变量,返回持久化处理的persistentId,类似{"persistentId": 5476bedf7823de4068253bae};
37-
一个ResponseInfo对象
31+
Parameters
32+
----------
33+
key: str
34+
待处理的源文件
35+
fops: list[str]
36+
处理详细操作,规格详见 https://developer.qiniu.com/dora/manual/1291/persistent-data-processing-pfop
37+
force: int or str, optional
38+
强制执行持久化处理开关
39+
persistent_type: int or str, optional
40+
持久化处理类型,为 '1' 时开启闲时任务
41+
Returns
42+
-------
43+
ret: dict
44+
持久化处理的 persistentId,类似 {"persistentId": 5476bedf7823de4068253bae};
45+
resp: ResponseInfo
3846
"""
3947
ops = ';'.join(fops)
4048
data = {'bucket': self.bucket, 'key': key, 'fops': ops}
4149
if self.pipeline:
4250
data['pipeline'] = self.pipeline
4351
if self.notify_url:
4452
data['notifyURL'] = self.notify_url
45-
if force == 1:
46-
data['force'] = 1
53+
if force == 1 or force == '1':
54+
data['force'] = str(force)
55+
if persistent_type and type(int(persistent_type)) is int:
56+
data['type'] = str(persistent_type)
4757

4858
url = '{0}/pfop'.format(config.get_default('default_api_host'))
4959
return http._post_with_auth(url, data, self.auth)
60+
61+
def get_status(self, persistent_id):
62+
"""
63+
获取持久化处理状态
64+
65+
Parameters
66+
----------
67+
persistent_id: str
68+
69+
Returns
70+
-------
71+
ret: dict
72+
持久化处理的状态,详见 https://developer.qiniu.com/dora/1294/persistent-processing-status-query-prefop
73+
resp: ResponseInfo
74+
"""
75+
url = '{0}/status/get/prefop'.format(config.get_default('default_api_host'))
76+
data = {
77+
'id': persistent_id
78+
}
79+
return http._get_with_auth(url, data, self.auth)

test_qiniu.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -434,18 +434,6 @@ def test_private_url(self):
434434
assert r.status_code == 200
435435

436436

437-
class MediaTestCase(unittest.TestCase):
438-
def test_pfop(self):
439-
q = Auth(access_key, secret_key)
440-
pfop = PersistentFop(q, 'testres', 'sdktest')
441-
op = op_save('avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', 'pythonsdk', 'pfoptest')
442-
ops = []
443-
ops.append(op)
444-
ret, info = pfop.execute('sintel_trailer.mp4', ops, 1)
445-
print(info)
446-
assert ret['persistentId'] is not None
447-
448-
449437
class EtagTestCase(unittest.TestCase):
450438
def test_zero_size(self):
451439
open("x", 'a').close()

tests/cases/test_services/test_processing/__init__.py

Whitespace-only changes.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import pytest
2+
3+
4+
from qiniu import PersistentFop, op_save
5+
6+
7+
persistent_id = None
8+
9+
10+
class TestPersistentFop:
11+
def test_pfop_execute(self, qn_auth):
12+
pfop = PersistentFop(qn_auth, 'testres', 'sdktest')
13+
op = op_save('avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', 'pythonsdk', 'pfoptest')
14+
ops = [
15+
op
16+
]
17+
ret, resp = pfop.execute('sintel_trailer.mp4', ops, 1)
18+
assert resp.status_code == 200, resp
19+
assert ret['persistentId'] is not None, resp
20+
global persistent_id
21+
persistent_id = ret['persistentId']
22+
23+
def test_pfop_get_status(self, qn_auth):
24+
assert persistent_id is not None
25+
pfop = PersistentFop(qn_auth, 'testres', 'sdktest')
26+
ret, resp = pfop.get_status(persistent_id)
27+
assert resp.status_code == 200, resp
28+
assert ret is not None, resp
29+
30+
def test_pfop_idle_time_task(self, set_conf_default, qn_auth):
31+
persistence_key = 'python-sdk-pfop-test/test-pfop-by-api'
32+
33+
key = 'sintel_trailer.mp4'
34+
pfop = PersistentFop(qn_auth, 'testres')
35+
ops = [
36+
op_save(
37+
op='avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240',
38+
bucket='pythonsdk',
39+
key=persistence_key
40+
)
41+
]
42+
ret, resp = pfop.execute(key, ops, force=1, persistent_type=1)
43+
assert resp.status_code == 200, resp
44+
assert 'persistentId' in ret, resp
45+
46+
ret, resp = pfop.get_status(ret['persistentId'])
47+
assert resp.status_code == 200, resp
48+
assert ret['type'] == 1, resp
49+
assert ret['creationDate'] is not None, resp

tests/cases/test_services/test_storage/conftest.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import os
2+
from collections import namedtuple
3+
from hashlib import new as hashlib_new
4+
import tempfile
5+
16
import pytest
27

38
import requests
@@ -92,3 +97,44 @@ def regions_with_fake_endpoints(regions_with_real_endpoints):
9297

9398
yield regions
9499

100+
101+
TempFile = namedtuple(
102+
'TempFile',
103+
[
104+
'path',
105+
'md5',
106+
'name',
107+
'size'
108+
]
109+
)
110+
111+
112+
@pytest.fixture(scope='function')
113+
def temp_file(request):
114+
size = 4 * 1024
115+
if hasattr(request, 'param'):
116+
size = request.param
117+
118+
tmp_file_path = tempfile.mktemp()
119+
chunk_size = 4 * 1024
120+
121+
md5_hasher = hashlib_new('md5')
122+
with open(tmp_file_path, 'wb') as f:
123+
remaining_bytes = size
124+
while remaining_bytes > 0:
125+
chunk = os.urandom(min(chunk_size, remaining_bytes))
126+
f.write(chunk)
127+
md5_hasher.update(chunk)
128+
remaining_bytes -= len(chunk)
129+
130+
yield TempFile(
131+
path=tmp_file_path,
132+
md5=md5_hasher.hexdigest(),
133+
name=os.path.basename(tmp_file_path),
134+
size=size
135+
)
136+
137+
try:
138+
os.remove(tmp_file_path)
139+
except Exception:
140+
pass
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import pytest
2+
3+
import qiniu
4+
5+
6+
KB = 1024
7+
MB = 1024 * KB
8+
GB = 1024 * MB
9+
10+
11+
# set a bucket lifecycle manually to delete prefix `test-pfop`!
12+
# or this test will continue to occupy bucket space.
13+
class TestPersistentFopByUpload:
14+
@pytest.mark.parametrize('temp_file', [10 * MB], indirect=True)
15+
@pytest.mark.parametrize('persistent_type', [None, 0, 1])
16+
def test_pfop_with_upload(
17+
self,
18+
set_conf_default,
19+
qn_auth,
20+
bucket_name,
21+
temp_file,
22+
persistent_type
23+
):
24+
key = 'test-pfop-upload-file'
25+
persistent_key = '_'.join([
26+
'test-pfop-by-upload',
27+
'type',
28+
str(persistent_type)
29+
])
30+
persistent_ops = ';'.join([
31+
qiniu.op_save(
32+
op='avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240',
33+
bucket=bucket_name,
34+
key=persistent_key
35+
)
36+
])
37+
38+
upload_policy = {
39+
'persistentOps': persistent_ops
40+
}
41+
42+
if persistent_type is not None:
43+
upload_policy['persistentType'] = persistent_type
44+
45+
token = qn_auth.upload_token(
46+
bucket_name,
47+
key,
48+
policy=upload_policy
49+
)
50+
ret, resp = qiniu.put_file(
51+
token,
52+
key,
53+
temp_file.path,
54+
check_crc=True
55+
)
56+
57+
assert ret is not None, resp
58+
assert ret['key'] == key, resp
59+
assert 'persistentId' in ret, resp
60+
61+
pfop = qiniu.PersistentFop(qn_auth, bucket_name)
62+
ret, resp = pfop.get_status(ret['persistentId'])
63+
assert resp.status_code == 200, resp
64+
if persistent_type == 1:
65+
assert ret['type'] == 1, resp
66+
assert ret['creationDate'] is not None, resp

tests/cases/test_services/test_storage/test_uploader.py

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import os
21
from collections import namedtuple
3-
from hashlib import new as hashlib_new
42

5-
import tempfile
63
import pytest
74

85
from qiniu.compat import json, is_py2
@@ -16,7 +13,7 @@
1613
build_batch_delete
1714
)
1815
from qiniu.http.endpoint import Endpoint
19-
from qiniu.http.region import Region, ServiceName
16+
from qiniu.http.region import ServiceName
2017
from qiniu.services.storage.uploader import _form_put
2118

2219
KB = 1024
@@ -112,48 +109,6 @@ def set_default_up_host_zone(request, valid_up_host):
112109
qn_config._is_customized_default['default_zone'] = False
113110

114111

115-
TempFile = namedtuple(
116-
'TempFile',
117-
[
118-
'path',
119-
'md5',
120-
'name',
121-
'size'
122-
]
123-
)
124-
125-
126-
@pytest.fixture(scope='function')
127-
def temp_file(request):
128-
size = 4 * KB
129-
if hasattr(request, 'param'):
130-
size = request.param
131-
132-
tmp_file_path = tempfile.mktemp()
133-
chunk_size = 4 * KB
134-
135-
md5_hasher = hashlib_new('md5')
136-
with open(tmp_file_path, 'wb') as f:
137-
remaining_bytes = size
138-
while remaining_bytes > 0:
139-
chunk = os.urandom(min(chunk_size, remaining_bytes))
140-
f.write(chunk)
141-
md5_hasher.update(chunk)
142-
remaining_bytes -= len(chunk)
143-
144-
yield TempFile(
145-
path=tmp_file_path,
146-
md5=md5_hasher.hexdigest(),
147-
name=os.path.basename(tmp_file_path),
148-
size=size
149-
)
150-
151-
try:
152-
os.remove(tmp_file_path)
153-
except Exception:
154-
pass
155-
156-
157112
class TestUploadFuncs:
158113
def test_put(self, qn_auth, bucket_name, get_key):
159114
key = get_key('a\\b\\c"hello', no_rand_trail=True)

0 commit comments

Comments
 (0)