initial commit
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import dataclasses
|
||||
import functools
|
||||
import io
|
||||
import pathlib
|
||||
from datetime import timedelta, datetime
|
||||
from io import FileIO
|
||||
from typing import Callable
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class ProgressIO(io.BufferedReader):
|
||||
@dataclasses.dataclass
|
||||
class Progress:
|
||||
total: int
|
||||
processed: int
|
||||
|
||||
def percent(self) -> float:
|
||||
return round(100 * self.processed / self.total, 2)
|
||||
|
||||
def __init__(self, file: pathlib.Path, on_progress: Callable[[Progress], None]):
|
||||
super().__init__(raw=FileIO(str(file)))
|
||||
self._size = file.stat().st_size
|
||||
self._on_progress_throttled = self.throttle(1)(on_progress)
|
||||
self._on_progress = on_progress
|
||||
self._processed = 0
|
||||
self._iter = None
|
||||
|
||||
def __next__(self) -> bytes:
|
||||
chunk = super().__next__()
|
||||
self._processed += len(chunk)
|
||||
progress = ProgressIO.Progress(self._size, self._processed)
|
||||
if self._processed == self._size:
|
||||
self._on_progress(progress)
|
||||
else:
|
||||
self._on_progress_throttled(progress)
|
||||
|
||||
return chunk
|
||||
|
||||
@staticmethod
|
||||
def throttle(seconds: float = 0):
|
||||
period = timedelta(seconds=seconds)
|
||||
|
||||
def decorator(fn):
|
||||
last_called = datetime.min
|
||||
|
||||
@functools.wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
now = datetime.now()
|
||||
nonlocal last_called
|
||||
if now - last_called > period:
|
||||
last_called = now
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
f = pathlib.Path(r"/path/to/file")
|
||||
httpx.post("http://httpbin.org/post", files={"file": ProgressIO(f, on_progress=lambda p: print(p.percent()))})
|
||||
Reference in New Issue
Block a user