Skip to content
This repository was archived by the owner on Feb 1, 2023. It is now read-only.

Commit 15b27d5

Browse files
feat!: stream get; pass client; hook guard (#16)
Co-authored-by: JamzumSum <zzzzss990315@gmail.com>
1 parent ac38f0d commit 15b27d5

File tree

3 files changed

+91
-56
lines changed

3 files changed

+91
-56
lines changed

poetry.lock

Lines changed: 33 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "AssetsUpdater"
3-
version = "1.4.1"
3+
version = "1.4.2"
44
description = "Update assets from network."
55
authors = ["JamzumSum <zzzzss990315@gmail.com>"]
66
license = "MIT"
@@ -25,7 +25,7 @@ rich = { version = "^12.6.0", optional = true }
2525
optional = false
2626

2727
[tool.poetry.group.test.dependencies]
28-
pytest = "^7.1.3"
28+
pytest = "^7.2.0"
2929
pytest-asyncio = "~0.20.1"
3030

3131
[tool.poetry.group.dev]

src/updater/download.py

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import logging
3+
from functools import wraps
34
from os import PathLike
45
from pathlib import Path
56
from typing import Awaitable, Callable, Optional
@@ -13,26 +14,57 @@
1314
__all__ = ["download"]
1415

1516

17+
def _guard(func: Callable):
18+
warned = False
19+
20+
@wraps(func)
21+
async def wrapper(*args, **kwds):
22+
try:
23+
return await func(*args, **kwds)
24+
except:
25+
nonlocal warned
26+
if not warned:
27+
log.error("Error caught by guard.", exc_info=True)
28+
warned = True
29+
30+
return wrapper
31+
32+
1633
async def download(
1734
url: URLTypes,
1835
local: Optional[PathLike] = None,
1936
buffer: int = 32768,
2037
echo_progress: Optional[Callable[..., Awaitable]] = None,
21-
proxy: ProxiesTypes = ...,
22-
):
38+
*,
39+
client: Optional[AsyncClient] = None,
40+
proxy: Optional[ProxiesTypes] = None,
41+
) -> int:
2342
"""async-download a url to a the given path.
2443
2544
:param local: where to download
45+
:param buffer: transfer buffer
2646
:param echo_progress: async function to receive downloaded size and total size as int.
27-
47+
:param client: use this client, otherwise we will create one and close it on return.
48+
:param proxy: download proxy.
2849
:return: size
50+
51+
.. warning:: actual proxy = client.proxy | param.proxy
52+
53+
.. versionchanged:: 1.4.2
54+
55+
added `client`; `proxy` is set as a keyword-only parameter.
2956
"""
57+
if client is None:
58+
async with AsyncClient(proxies=proxy) as client:
59+
return await download(url, local, buffer, echo_progress, client=client, proxy=proxy)
60+
3061
url = url if isinstance(url, URL) else URL(url)
3162
remote_name = Path(url.path).name
3263
local = Path(local or remote_name)
3364
local.parent.mkdir(parents=True, exist_ok=True)
3465

3566
if local.is_dir():
67+
# if local not exist or is a directory
3668
local = local / remote_name
3769
log.info(f"Changing local path from {local.parent.as_posix()} to {local.as_posix()}")
3870
if local.is_dir():
@@ -41,29 +73,29 @@ async def download(
4173
if local.exists():
4274
log.warning(f"{local.as_posix()} exists. Overwrite.")
4375

44-
client_dict = {}
45-
if proxy != ...:
46-
client_dict["proxies"] = proxy
76+
if echo_progress:
77+
echo_progress = _guard(echo_progress)
4778

4879
acc = 0
49-
async with AsyncClient(**client_dict) as client, aiofiles.open(local, "wb") as f:
50-
r = await client.get(url, follow_redirects=True)
51-
r.raise_for_status()
52-
53-
size = int(r.headers.get("Content-Length", -1))
54-
it = r.aiter_bytes(chunk_size=buffer)
55-
56-
log.info(f"Starting download task {url} -> {local}")
57-
log.debug(f"File size: {size}")
58-
59-
async for c in it:
60-
if not c:
61-
continue
62-
if echo_progress:
63-
ad, _ = await asyncio.gather(f.write(c), echo_progress(completed=acc, total=size))
64-
acc += ad
65-
else:
66-
acc += await f.write(c)
80+
async with aiofiles.open(local, "wb") as f:
81+
async with client.stream("GET", url, follow_redirects=True) as r:
82+
r.raise_for_status()
83+
84+
size = int(r.headers.get("Content-Length", -1))
85+
86+
log.info(f"Starting download task {url} -> {local}")
87+
log.debug(f"File size: {size}")
88+
89+
async for c in r.aiter_bytes(chunk_size=buffer):
90+
if not c:
91+
continue
92+
if echo_progress:
93+
ad, _ = await asyncio.gather(
94+
f.write(c), echo_progress(completed=acc, total=size)
95+
)
96+
acc += ad
97+
else:
98+
acc += await f.write(c)
6799

68100
if size > 0 and acc != size:
69101
log.error(f"Content-Length is {size} but {acc} downloaded")

0 commit comments

Comments
 (0)