mirror of
https://git.launchpad.net/ubuntu-dev-tools
synced 2025-05-11 00:41:28 +00:00
misc: Change download() method to use python requests and optional authentication
Since private PPAs require authentication, use python requests library instead of urlopen(), since requests handles authentication easily
This commit is contained in:
parent
4f6d6bf2d8
commit
3f2983c157
1
debian/control
vendored
1
debian/control
vendored
@ -139,6 +139,7 @@ Depends:
|
|||||||
python3-httplib2,
|
python3-httplib2,
|
||||||
python3-launchpadlib,
|
python3-launchpadlib,
|
||||||
python3-lazr.restfulclient,
|
python3-lazr.restfulclient,
|
||||||
|
python3-requests,
|
||||||
sensible-utils,
|
sensible-utils,
|
||||||
${misc:Depends},
|
${misc:Depends},
|
||||||
${python3:Depends},
|
${python3:Depends},
|
||||||
|
@ -27,7 +27,6 @@ Approach:
|
|||||||
3. Verify checksums.
|
3. Verify checksums.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from urllib.error import (URLError, HTTPError)
|
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
import codecs
|
import codecs
|
||||||
import functools
|
import functools
|
||||||
@ -46,8 +45,12 @@ import debian.deb822
|
|||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
|
|
||||||
from ubuntutools.config import UDTConfig
|
from ubuntutools.config import UDTConfig
|
||||||
from ubuntutools.lp.lpapicache import (Launchpad, Distribution, PersonTeam, Project,
|
from ubuntutools.lp.lpapicache import (Launchpad,
|
||||||
SourcePackagePublishingHistory)
|
Distribution,
|
||||||
|
PersonTeam,
|
||||||
|
Project,
|
||||||
|
SourcePackagePublishingHistory,
|
||||||
|
HTTPError)
|
||||||
from ubuntutools.lp.udtexceptions import (PackageNotFoundException,
|
from ubuntutools.lp.udtexceptions import (PackageNotFoundException,
|
||||||
SeriesNotFoundException,
|
SeriesNotFoundException,
|
||||||
PocketDoesNotExistError,
|
PocketDoesNotExistError,
|
||||||
@ -56,7 +59,8 @@ from ubuntutools.misc import (download,
|
|||||||
download_bytes,
|
download_bytes,
|
||||||
verify_file_checksum,
|
verify_file_checksum,
|
||||||
verify_file_checksums,
|
verify_file_checksums,
|
||||||
DownloadError)
|
DownloadError,
|
||||||
|
NotFoundError)
|
||||||
from ubuntutools.version import Version
|
from ubuntutools.version import Version
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -397,15 +401,12 @@ class SourcePackage(ABC):
|
|||||||
if self._download_file(url, filename, size, dscverify=dscverify,
|
if self._download_file(url, filename, size, dscverify=dscverify,
|
||||||
sha1sum=sha1sum, sha256sum=sha256sum):
|
sha1sum=sha1sum, sha256sum=sha256sum):
|
||||||
return
|
return
|
||||||
except HTTPError as e:
|
except NotFoundError:
|
||||||
if e.code == 404:
|
# It's ok if the file isn't found, just try the next url
|
||||||
# It's ok if the file isn't found, just try the next url
|
Logger.debug(f'File not found at {url}')
|
||||||
Logger.debug("File not found at %s" % url)
|
except DownloadError as e:
|
||||||
else:
|
Logger.error(f'Download Error: {e}')
|
||||||
Logger.error('HTTP Error %i: %s', e.code, str(e))
|
raise DownloadError(f'Failed to download {filename}')
|
||||||
except URLError as e:
|
|
||||||
Logger.error('URL Error: %s', e.reason)
|
|
||||||
raise DownloadError('Failed to download %s' % filename)
|
|
||||||
|
|
||||||
def pull_dsc(self):
|
def pull_dsc(self):
|
||||||
'''DEPRECATED
|
'''DEPRECATED
|
||||||
|
@ -26,6 +26,7 @@ import distro_info
|
|||||||
import hashlib
|
import hashlib
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
|
import requests
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -34,7 +35,6 @@ from contextlib import suppress
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import check_output, CalledProcessError
|
from subprocess import check_output, CalledProcessError
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from urllib.request import urlopen
|
|
||||||
|
|
||||||
from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
|
from ubuntutools.lp.udtexceptions import PocketDoesNotExistError
|
||||||
|
|
||||||
@ -58,6 +58,11 @@ class DownloadError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundError(DownloadError):
|
||||||
|
"Source package not found"
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def system_distribution_chain():
|
def system_distribution_chain():
|
||||||
""" system_distribution_chain() -> [string]
|
""" system_distribution_chain() -> [string]
|
||||||
|
|
||||||
@ -286,74 +291,97 @@ def download(src, dst, size=0):
|
|||||||
|
|
||||||
src: str or Path
|
src: str or Path
|
||||||
Source to copy from (file path or url)
|
Source to copy from (file path or url)
|
||||||
dst: str
|
dst: str or Path
|
||||||
Destination dir or filename
|
Destination dir or filename
|
||||||
size: int
|
size: int
|
||||||
Size of source, if known
|
Size of source, if known
|
||||||
|
|
||||||
This calls urllib.request.urlopen() so it may raise the same
|
If the URL contains authentication data in the URL 'netloc',
|
||||||
exceptions as that method (URLError or HTTPError)
|
it will be stripped from the URL and passed to the requests library.
|
||||||
|
|
||||||
|
This may throw a DownloadError.
|
||||||
"""
|
"""
|
||||||
src = str(src)
|
src = str(src)
|
||||||
if not urlparse(src).scheme:
|
parsedsrc = urlparse(src)
|
||||||
src = 'file://%s' % os.path.abspath(os.path.expanduser(src))
|
|
||||||
dst = os.path.abspath(os.path.expanduser(dst))
|
|
||||||
|
|
||||||
filename = os.path.basename(urlparse(src).path)
|
dst = Path(dst).expanduser().resolve()
|
||||||
|
if dst.is_dir():
|
||||||
|
dst = dst / Path(parsedsrc.path).name
|
||||||
|
|
||||||
if os.path.isdir(dst):
|
# Copy if src is a local file
|
||||||
dst = os.path.join(dst, filename)
|
if parsedsrc.scheme in ['', 'file']:
|
||||||
|
src = Path(parsedsrc.path).expanduser().resolve()
|
||||||
if urlparse(src).scheme == 'file':
|
if src != parsedsrc.path:
|
||||||
srcfile = urlparse(src).path
|
Logger.info(f'Parsed {parsedsrc.path} as {src}')
|
||||||
if os.path.exists(srcfile) and os.path.exists(dst):
|
if not src.exists():
|
||||||
if os.path.samefile(srcfile, dst):
|
raise NotFoundError(f'Source file {src} not found')
|
||||||
Logger.info(f"Using existing file {dst}")
|
if dst.exists():
|
||||||
|
if src.samefile(dst):
|
||||||
|
Logger.info(f'Using existing file {dst}')
|
||||||
return
|
return
|
||||||
|
Logger.info(f'Replacing existing file {dst}')
|
||||||
|
Logger.info(f'Copying file {src} to {dst}')
|
||||||
|
shutil.copyfile(src, dst)
|
||||||
|
return
|
||||||
|
|
||||||
with urlopen(src) as fsrc, open(dst, 'wb') as fdst:
|
(src, username, password) = extract_authentication(src)
|
||||||
url = fsrc.geturl()
|
auth = (username, password) if username or password else None
|
||||||
Logger.debug(f"Using URL: {url}")
|
|
||||||
|
|
||||||
if not size:
|
try:
|
||||||
with suppress(AttributeError, TypeError, ValueError):
|
with requests.get(src, stream=True, auth=auth) as fsrc, dst.open('wb') as fdst:
|
||||||
size = int(fsrc.info().get('Content-Length'))
|
fsrc.raise_for_status()
|
||||||
|
_download(fsrc, fdst, size)
|
||||||
|
except requests.RequestException as e:
|
||||||
|
if e.response and e.response.status_code == 404:
|
||||||
|
raise NotFoundError(f'URL {src} not found') from e
|
||||||
|
raise DownloadError(f'Could not download {src} to {dst}') from e
|
||||||
|
|
||||||
hostname = urlparse(url).hostname
|
|
||||||
sizemb = ' (%0.3f MiB)' % (size / 1024.0 / 1024) if size else ''
|
|
||||||
Logger.info(f'Downloading {filename} from {hostname}{sizemb}')
|
|
||||||
|
|
||||||
if not all((Logger.isEnabledFor(logging.INFO),
|
def _download(fsrc, fdst, size):
|
||||||
sys.stderr.isatty(), size)):
|
""" helper method to download src to dst using requests library. """
|
||||||
shutil.copyfileobj(fsrc, fdst)
|
url = fsrc.url
|
||||||
return
|
Logger.debug(f'Using URL: {url}')
|
||||||
|
|
||||||
blocksize = 4096
|
if not size:
|
||||||
XTRALEN = len('[] 99%')
|
with suppress(AttributeError, TypeError, ValueError):
|
||||||
downloaded = 0
|
size = int(fsrc.headers.get('Content-Length'))
|
||||||
bar_width = 60
|
|
||||||
term_width = os.get_terminal_size(sys.stderr.fileno())[0]
|
|
||||||
if term_width < bar_width + XTRALEN + 1:
|
|
||||||
bar_width = term_width - XTRALEN - 1
|
|
||||||
|
|
||||||
try:
|
parsed = urlparse(url)
|
||||||
while True:
|
filename = Path(parsed.path).name
|
||||||
block = fsrc.read(blocksize)
|
hostname = parsed.hostname
|
||||||
if not block:
|
sizemb = ' (%0.3f MiB)' % (size / 1024.0 / 1024) if size else ''
|
||||||
break
|
Logger.info(f'Downloading {filename} from {hostname}{sizemb}')
|
||||||
fdst.write(block)
|
|
||||||
downloaded += len(block)
|
if not all((Logger.isEnabledFor(logging.INFO), sys.stderr.isatty(), size)):
|
||||||
pct = float(downloaded) / size
|
shutil.copyfileobj(fsrc.raw, fdst)
|
||||||
bar = ('=' * int(pct * bar_width))[:-1] + '>'
|
return
|
||||||
fmt = '\r[{bar:<%d}]{pct:>3}%%\r' % bar_width
|
|
||||||
sys.stderr.write(fmt.format(bar=bar, pct=int(pct * 100)))
|
blocksize = 4096
|
||||||
sys.stderr.flush()
|
XTRALEN = len('[] 99%')
|
||||||
finally:
|
downloaded = 0
|
||||||
sys.stderr.write('\r' + ' ' * (term_width - 1) + '\r')
|
bar_width = 60
|
||||||
if downloaded < size:
|
term_width = os.get_terminal_size(sys.stderr.fileno())[0]
|
||||||
Logger.error('Partial download: %0.3f MiB of %0.3f MiB' %
|
if term_width < bar_width + XTRALEN + 1:
|
||||||
(downloaded / 1024.0 / 1024,
|
bar_width = term_width - XTRALEN - 1
|
||||||
size / 1024.0 / 1024))
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
block = fsrc.raw.read(blocksize)
|
||||||
|
if not block:
|
||||||
|
break
|
||||||
|
fdst.write(block)
|
||||||
|
downloaded += len(block)
|
||||||
|
pct = float(downloaded) / size
|
||||||
|
bar = ('=' * int(pct * bar_width))[:-1] + '>'
|
||||||
|
fmt = '\r[{bar:<%d}]{pct:>3}%%\r' % bar_width
|
||||||
|
sys.stderr.write(fmt.format(bar=bar, pct=int(pct * 100)))
|
||||||
|
sys.stderr.flush()
|
||||||
|
finally:
|
||||||
|
sys.stderr.write('\r' + ' ' * (term_width - 1) + '\r')
|
||||||
|
if downloaded < size:
|
||||||
|
Logger.error('Partial download: %0.3f MiB of %0.3f MiB' %
|
||||||
|
(downloaded / 1024.0 / 1024,
|
||||||
|
size / 1024.0 / 1024))
|
||||||
|
|
||||||
|
|
||||||
def _download_text(src, binary):
|
def _download_text(src, binary):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user