Skip to content

Instantly share code, notes, and snippets.

@abdusco
Created April 10, 2021 04:56
Show Gist options
  • Save abdusco/ac48c27ed6b8771a3145d3f374051dea to your computer and use it in GitHub Desktop.
Save abdusco/ac48c27ed6b8771a3145d3f374051dea to your computer and use it in GitHub Desktop.
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()))})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment