-
-
Save pudquick/4317095 to your computer and use it in GitHub Desktop.
import os, os.path, sys, urllib2, requests, tempfile, zipfile, shutil, gzip, tarfile | |
__pypi_base__ = os.path.abspath(os.path.dirname(__file__)) | |
class PyPiError(Exception): | |
def __init__(self, value): | |
self.value = value | |
def __str__(self): | |
return repr(self.value) | |
def _chunk_report(bytes_so_far, chunk_size, total_size): | |
if (total_size != None): | |
percent = float(bytes_so_far) / total_size | |
percent = round(percent*100, 2) | |
print 'Downloaded %d of %d bytes (%0.2f%%)' % (bytes_so_far, total_size, percent) | |
if bytes_so_far >= total_size: | |
print '' | |
else: | |
print 'Downloaded %d bytes' % (bytes_so_far) | |
def _chunk_read(response, chunk_size=32768, report_hook=None, filename=None): | |
file_data = [] | |
if response.info().has_key('Content-Length'): | |
total_size = response.info().getheader('Content-Length').strip() | |
total_size = int(total_size) | |
else: | |
# No size | |
total_size = None | |
if report_hook: | |
print '* Warning: No total file size available.' | |
if (filename == None) and (response.info().has_key('Content-Disposition')): | |
# If the response has Content-Disposition, we take file name from it | |
try: | |
filename = response.info()['Content-Disposition'].split('filename=')[1] | |
if filename[0] == '"' or filename[0] == "'": | |
filename = filename[1:-1] | |
except Exception: | |
sys.exc_clear() | |
filename = 'output' | |
if (filename == None): | |
if report_hook: | |
print "* No detected filename, using 'output'" | |
filename = 'output' | |
bytes_so_far = 0 | |
while True: | |
chunk = response.read(chunk_size) | |
bytes_so_far += len(chunk) | |
if not chunk: | |
break | |
else: | |
file_data.append(chunk) | |
report_hook(bytes_so_far, chunk_size, total_size) | |
return (file_data, filename) | |
def _download(src_dict, print_progress=True): | |
headers = {'User-Agent' : 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6;en-US; rv:1.9.2.9) Gecko/20100824 Firefox/3.6.9'} | |
if print_progress: | |
print '* Downloading:', src_dict['url'] | |
req = urllib2.Request(src_dict['url'], headers=headers) | |
response = urllib2.urlopen(req) | |
output = src_dict['url'].split('/')[-1].split('#')[0].split('?')[0] | |
if print_progress: | |
data,filename = _chunk_read(response, report_hook=_chunk_report, filename=output) | |
else: | |
data,filename = _chunk_read(response, report_hook=None, filename=output) | |
if (len(data) > 0): | |
if os.path.exists(filename): | |
os.remove(filename) | |
try: | |
f = open(filename, 'wb') | |
for x in data: | |
f.write(x) | |
f.close() | |
if print_progress: | |
print '* Saved to:', filename | |
return os.path.abspath(filename) | |
except Exception: | |
if print_progress: | |
print '! Error:', sys.exc_info()[1] | |
raise | |
else: | |
if print_progress: | |
print '* Error: 0 bytes downloaded, not saved' | |
def pypi_download(pkg_name, pkg_ver='', print_progress=True): | |
import xmlrpclib | |
pypi = xmlrpclib.ServerProxy('http://pypi.python.org/pypi') | |
hits = pypi.package_releases(pkg_name, True) | |
if not hits: | |
raise PyPiError('No package found with that name') | |
if not pkg_ver: | |
pkg_ver = hits[0] | |
elif not pkg_ver in hits: | |
raise PyPiError('That package version is not available') | |
hits = pypi.release_urls(pkg_name, pkg_ver) | |
if not hits: | |
raise PyPiError('No public download links for that version') | |
source = ([x for x in hits if x['packagetype'] == 'sdist'][:1] + [None])[0] | |
if not source: | |
raise PyPiError('No source-only download links for that version') | |
return _download(source, print_progress) | |
def pypi_versions(pkg_name, limit=10, show_hidden=True): | |
if not pkg_name: | |
return [] | |
pypi = xmlrpclib.ServerProxy('http://pypi.python.org/pypi') | |
hits = pypi.package_releases(pkg_name, show_hidden) | |
if not hits: | |
return [] | |
if len(hits) > limit: | |
hits = hits[:limit] | |
return hits | |
def pypi_search(search_str, limit=5): | |
if not search_str: | |
return [] | |
pypi = xmlrpclib.ServerProxy('http://pypi.python.org/pypi') | |
hits = pypi.search({'name': search_str}, 'and') | |
if not hits: | |
return [] | |
hits = sorted(hits, key=lambda pkg: pkg['_pypi_ordering'], reverse=True) | |
if len(hits) > limit: | |
hits = hits[:limit] | |
return hits | |
def _install_ConfigParser(path='.'): | |
# Grab the 2.7.3 version of ConfigParser - even though Pythonista 1.2 is 2.7.0 | |
r = requests.get('http://hg.python.org/cpython/raw-file/70274d53c1dd/Lib/ConfigParser.py') | |
lib_file = os.path.join(path, 'ConfigParser.py') | |
with open(lib_file, 'w') as f: | |
f.write(r.text) | |
def _install_xmlrpclib(path='.'): | |
# Grab the 2.7.3 version of xmlrpclib - even though Pythonista 1.2 is 2.7.0 | |
r = requests.get('http://hg.python.org/cpython/raw-file/70274d53c1dd/Lib/xmlrpclib.py') | |
lib_file = os.path.join(path, 'xmlrpclib.py') | |
with open(lib_file, 'w') as f: | |
f.write(r.text) | |
def _unzip(a_zip=None, path='.'): | |
if a_zip is None: | |
return | |
filename = os.path.abspath(a_zip) | |
if not os.path.isfile(filename): | |
return | |
# PK magic marker check | |
f = open(filename) | |
try: | |
pk_check = f.read(2) | |
except Exception: | |
pk_check = '' | |
finally: | |
f.close() | |
if pk_check != 'PK': | |
print "unzip: %s: does not appear to be a zip file" % a_zip | |
else: | |
if (os.path.basename(filename).lower().endswith('.zip')): | |
altpath = os.path.splitext(os.path.basename(filename))[0] | |
else: | |
altpath = os.path.basename(filename) + '_unzipped' | |
altpath = os.path.join(os.path.dirname(filename), altpath) | |
location = os.path.abspath(path) | |
if not os.path.exists(location): | |
os.makedirs(location) | |
zipfp = open(filename, 'rb') | |
try: | |
zipf = zipfile.ZipFile(zipfp) | |
# check for a leading directory common to all files and remove it | |
dirnames = [os.path.join(os.path.dirname(x), '') for x in zipf.namelist()] | |
common_dir = os.path.commonprefix(dirnames or ['/']) | |
# Check to make sure there aren't 2 or more sub directories with the same prefix | |
if not common_dir.endswith('/'): | |
common_dir = os.path.join(os.path.dirname(common_dir), '') | |
for name in zipf.namelist(): | |
data = zipf.read(name) | |
fn = name | |
if common_dir: | |
if fn.startswith(common_dir): | |
fn = fn.split(common_dir, 1)[-1] | |
elif fn.startswith('/' + common_dir): | |
fn = fn.split('/' + common_dir, 1)[-1] | |
fn = fn.lstrip('/') | |
fn = os.path.join(location, fn) | |
dirf = os.path.dirname(fn) | |
if not os.path.exists(dirf): | |
os.makedirs(dirf) | |
if fn.endswith('/'): | |
# A directory | |
if not os.path.exists(fn): | |
os.makedirs(fn) | |
else: | |
fp = open(fn, 'wb') | |
try: | |
fp.write(data) | |
finally: | |
fp.close() | |
except Exception: | |
zipfp.close() | |
print "unzip: %s: zip file is corrupt" % a_zip | |
return | |
zipfp.close() | |
return os.path.abspath(location) | |
def _ungzip(a_gz=None, path='.'): | |
fname = 'ungzip' | |
if a_gz is None: | |
return | |
filename = os.path.abspath(a_gz) | |
if not os.path.isfile(filename): | |
return | |
else: | |
# '\x1f\x8b\x08' magic marker check | |
f = open(filename, 'rb') | |
try: | |
gz_check = f.read(3) | |
except Exception: | |
gz_check = '' | |
finally: | |
f.close() | |
if gz_check != '\x1f\x8b\x08': | |
print "%s: %s: does not appear to be a gzip file" % (fname,a_gz) | |
return | |
else: | |
if (os.path.basename(filename).lower().endswith('.gz') or os.path.basename(filename).lower().endswith('.gzip')): | |
altpath = os.path.splitext(os.path.basename(filename))[0] | |
elif os.path.basename(filename).lower().endswith('.tgz'): | |
altpath = os.path.splitext(os.path.basename(filename))[0] + '.tar' | |
else: | |
altpath = os.path.basename(filename) + '_ungzipped' | |
location = os.path.join(os.path.abspath(path), altpath) | |
if os.path.exists(location): | |
# Older file exists somehow, get rid of it | |
os.remove(location) | |
dirf = os.path.dirname(os.path.dirname(os.path.abspath(location))) | |
try: | |
if not os.path.exists(dirf): | |
os.makedirs(dirf) | |
with open(location, 'wb') as outfile: | |
with gzip.open(filename, 'rb') as gzfile: | |
outfile.write(gzfile.read()) | |
except Exception: | |
print "%s: %s: gzip file is corrupt" % (fname, a_gz) | |
return | |
return os.path.abspath(location) | |
def _untar(a_tar=None, path='.'): | |
if a_tar is None: | |
return | |
filename = os.path.abspath(a_tar) | |
if not os.path.isfile(filename): | |
return | |
# 'ustar' magic marker check | |
f = open(filename) | |
try: | |
f.seek(257) | |
ustar_check = f.read(5) | |
except Exception: | |
ustar_check = '' | |
finally: | |
f.close() | |
if ustar_check != 'ustar': | |
print "untar: %s: does not appear to be a tar file" % a_tar | |
return | |
else: | |
if (os.path.basename(filename).lower().endswith('.tar')): | |
altpath = os.path.splitext(os.path.basename(filename))[0] | |
else: | |
altpath = os.path.basename(filename) + '_untarred' | |
altpath = os.path.join(os.path.dirname(filename), altpath) | |
location = os.path.abspath(path) | |
if not os.path.exists(location): | |
os.makedirs(location) | |
try: | |
tar = tarfile.open(filename, 'r') | |
# check for a leading directory common to all files and remove it | |
dirnames = [os.path.join(os.path.dirname(x.name), '') for x in tar.getmembers() if x.name != 'pax_global_header'] | |
common_dir = os.path.commonprefix(dirnames or ['/']) | |
if not common_dir.endswith('/'): | |
common_dir = os.path.join(os.path.dirname(common_dir), '') | |
for member in tar.getmembers(): | |
fn = member.name | |
if fn == 'pax_global_header': | |
continue | |
if common_dir: | |
if fn.startswith(common_dir): | |
fn = fn.split(common_dir, 1)[-1] | |
elif fn.startswith('/' + common_dir): | |
fn = fn.split('/' + common_dir, 1)[-1] | |
fn = fn.lstrip('/') | |
fn = os.path.join(location, fn) | |
dirf = os.path.dirname(fn) | |
if member.isdir(): | |
# A directory | |
if not os.path.exists(fn): | |
os.makedirs(fn) | |
elif member.issym(): | |
# skip symlinks | |
continue | |
else: | |
try: | |
fp = tar.extractfile(member) | |
except (KeyError, AttributeError): | |
# invalid member, not necessarily a bad tar file | |
continue | |
if not os.path.exists(dirf): | |
os.makedirs(dirf) | |
with open(fn, 'wb') as destfp: | |
shutil.copyfileobj(fp, destfp) | |
fp.close() | |
except Exception: | |
tar.close() | |
print "untar: %s: tar file is corrupt" % a_tar | |
return | |
tar.close() | |
return os.path.abspath(location) | |
def _install_setuptools(path='.'): | |
# Can't use requests - need https access and it's not availabe in Pythonista 1.2 | |
r = urllib2.urlopen('https://raw.github.com/pudquick/pipista/master/st-lite.zip') | |
temp_zip = tempfile.TemporaryFile() | |
temp_zip.write(r.read()) | |
r.close() | |
temp_zip.seek(0) | |
unzipped = zipfile.ZipFile(temp_zip) | |
for name in unzipped.namelist(): | |
data = unzipped.read(name) | |
fn = os.path.abspath(os.path.join(path, name)) | |
dirn = os.path.dirname(fn) | |
if not os.path.exists(dirn): | |
os.makedirs(dirn) | |
if fn.endswith('/') or fn.endswith('\\'): | |
if not os.path.exists(fn): | |
os.makedirs(fn) | |
else: | |
fp = open(fn, 'wb') | |
try: | |
fp.write(data) | |
finally: | |
fp.close() | |
temp_zip.close() | |
def _rm(path=None): | |
if path is None: | |
return | |
full_file = os.path.abspath(path).rstrip('/') | |
if not os.path.exists(path): | |
return | |
if (os.path.isdir(full_file)): | |
try: | |
shutil.rmtree(full_file, True) | |
if (os.path.exists(full_file)): | |
return | |
except Exception: | |
sys.exc_clear() | |
return | |
else: | |
try: | |
os.remove(full_file) | |
except Exception: | |
sys.exc_clear() | |
return | |
def _prep_pipista(): | |
# This function does some prep work for pipista: | |
# - Sets up the 'pypi-modules' directory if it doesn't exist | |
# - Sets up '.tmp' in pypi-modules if it doesn't exist, for temp storage | |
# - Adds 'pypi-modules' to the import paths | |
# - Makes sure xmlrpclib is installed (fix for Pythonista 1.2) | |
# - Makes sure the minimal setuptools is installed | |
# ---------- | |
# Get pipista location as the relative base for module storage | |
lib_dir = os.path.join(__pypi_base__, 'pypi-modules') | |
tmp_dir = os.path.join(lib_dir, '.tmp') | |
if not os.path.exists(lib_dir): | |
try: | |
os.mkdir(lib_dir) | |
except Exception: | |
# Fail silently, if we can't make the directory | |
sys.exc_clear() | |
if not os.path.exists(tmp_dir): | |
try: | |
os.mkdir(tmp_dir) | |
except Exception: | |
# Fail silently, if we can't make the directory | |
sys.exc_clear() | |
# Make sure lib_dir exists before adding it to the paths | |
if os.path.exists(lib_dir): | |
if lib_dir not in sys.path: | |
sys.path += [lib_dir] | |
# Attempt to load xmlrpclib - not present in Pythonista 1.2 | |
try: | |
import xmlrpclib | |
except ImportError: | |
# Doesn't seem to be available - attempt to download it | |
sys.exc_clear() | |
_install_xmlrpclib(lib_dir) | |
import xmlrpclib | |
# Attempt to load ConfigParser - not present in Pythonista 1.2 | |
try: | |
import ConfigParser | |
except ImportError: | |
# Doesn't seem to be available - attempt to download it | |
sys.exc_clear() | |
_install_ConfigParser(lib_dir) | |
import xmlrpclib | |
try: | |
import distutils.util | |
def _fixed_get_platform(): | |
return sys.platform | |
distutils.util.get_platform = _fixed_get_platform | |
import setuptools | |
except ImportError: | |
# Install a lite version of it - just enough for what we need | |
sys.exc_clear() | |
_install_setuptools(lib_dir) | |
_prep_pipista() | |
def _reset_and_enter_tmp(alt_dir=None): | |
lib_dir = os.path.join(__pypi_base__, 'pypi-modules') | |
tmp_dir = os.path.join(lib_dir, '.tmp') | |
# Nuke the existing .tmp folder | |
_rm(tmp_dir) | |
# Prep .tmp folders: archive, unpack | |
os.makedirs(tmp_dir) | |
os.makedirs(os.path.join(tmp_dir, 'archive')) | |
os.makedirs(os.path.join(tmp_dir, 'unpack')) | |
if alt_dir is None: | |
os.chdir(tmp_dir) | |
else: | |
# Cleaning up after ourselves, time to return | |
os.chdir(alt_dir) | |
def _py_build(path=None): | |
if path is None: | |
return | |
import distutils.core | |
old_stdout = sys.stdout | |
old_stderr = sys.stderr | |
sys.stdout = tempfile.TemporaryFile() | |
sys.stderr = tempfile.TemporaryFile() | |
os.chdir(path) | |
try: | |
result = distutils.core.run_setup('setup.py', ['build_py', '--force']) | |
except Exception: | |
result = None | |
finally: | |
sys.stderr = old_stderr | |
sys.stdout = old_stdout | |
return result | |
def pypi_install(pkg_name, pkg_ver='', print_progress=True): | |
## EXPERIMENTAL - not guaranteed to work! ## | |
# Remeber where we were | |
cwd = os.getcwd() | |
# Reset build environment and enter it | |
_reset_and_enter_tmp() | |
# Download the specified package to the archive directory | |
os.chdir('archive') | |
fn = pypi_download(pkg_name, pkg_ver, print_progress) | |
if not fn: | |
_reset_and_enter_tmp(cwd) | |
return False | |
# Successfully downloaded, move back to .tmp | |
os.chdir('..') | |
res = None | |
if fn.lower().endswith('.zip'): | |
res = _unzip(os.path.abspath(fn), 'unpack') | |
elif (fn.lower().endswith('.tar.gz') or fn.lower().endswith('.tgz')): | |
res_tar = _ungzip(os.path.abspath(fn), 'archive') | |
if res_tar: | |
# Got the tar file, now to unpack it | |
res = _untar(os.path.abspath(res_tar), 'unpack') | |
elif fn.lower().endswith('.tar'): | |
res = _untar(os.path.abspath(fn), 'unpack') | |
if res: | |
# Assuming we got to this point, we now have a directory, with possible sub-dirs, with a setup.py | |
# Have to find that setup.py, search top down for first one | |
# Pick a sane default | |
setup_dir = res | |
for (path, dirs, files) in os.walk(res): | |
if 'setup.py' in set([x.lower() for x in files]): | |
setup_dir = path | |
break | |
setup_dir = os.path.abspath(setup_dir) | |
print "* setup.py found here:", setup_dir | |
# Attempt a pure python build | |
print "* Compiling pure python modules ..." | |
result = _py_build(setup_dir) | |
if result: | |
# Should be contents inside setup_dir/build/lib - merge them into pypi-modules | |
build_dir = os.path.join(setup_dir, 'build/lib') | |
if os.path.exists(build_dir): | |
# Get the files and directories in it | |
os.chdir(build_dir) | |
(path, dirs, files) = os.walk(build_dir).next() | |
lib_dir = os.path.abspath(os.path.join(__pypi_base__, 'pypi-modules')) | |
for a_file in files: | |
print "* Installing module: %s ..." % a_file | |
target = os.path.join(lib_dir, a_file) | |
if os.path.exists(target): | |
_rm(target) | |
shutil.copyfile(a_file, target) | |
for a_dir in dirs: | |
print "* Installing module: %s ..." % a_dir | |
target = os.path.join(lib_dir, a_dir) | |
if os.path.exists(target): | |
_rm(target) | |
shutil.copytree(a_dir, target) | |
_reset_and_enter_tmp(cwd) | |
return True | |
else: | |
print "* ERROR: Something went wrong" | |
_reset_and_enter_tmp(cwd) | |
return False |
Would be nice to support grabbing packages from URL's http/git
For what it's worth, I think I figured out the exec f.read() in g,l issue (where imports don't work). This should be exec f.read() in g, g. I don't claim to fully understand why, but your example the forum of just doing exec on a string with an import also fails if wrapped inside a function.
Not sure of an easy way to fix that, guess we need to patch setuptools itself, which maybe is easy given you already have a lite version.
I want to install module "wifi", but unfortunatelly, I cannot call to run file "setup.py" via "distutils.core.run_setup".
The error message is " Skipping copying files to /etc/... , no write access.
How can I resolve this issue?
Traceback (most recent call last):
File "/private/var/mobile/Containers/Shared/AppGroup/9EF748E7-E9EC-4528-ADE7-7E6D6FC841DD/Pythonista3/Documents/pipista_install.py", line 2, in
pipista.pypi_install('pandas')
File "/private/var/mobile/Containers/Shared/AppGroup/9EF748E7-E9EC-4528-ADE7-7E6D6FC841DD/Pythonista3/Documents/pipista.py", line 460, in pypi_install
fn = pypi_download(pkg_name, pkg_ver, print_progress)
File "/private/var/mobile/Containers/Shared/AppGroup/9EF748E7-E9EC-4528-ADE7-7E6D6FC841DD/Pythonista3/Documents/pipista.py", line 95, in pypi_download
hits = pypi.release_urls(pkg_name, pkg_ver)
File "/var/containers/Bundle/Application/CBE5150F-2F53-4F25-8FE0-DC5236FFA836/Pythonista3.app/Frameworks/Py2Kit.framework/pylib/xmlrpclib.py", line 1244, in call
return self.__send(self.__name, args)
File "/var/containers/Bundle/Application/CBE5150F-2F53-4F25-8FE0-DC5236FFA836/Pythonista3.app/Frameworks/Py2Kit.framework/pylib/xmlrpclib.py", line 1603, in __request
verbose=self.__verbose
File "/var/containers/Bundle/Application/CBE5150F-2F53-4F25-8FE0-DC5236FFA836/Pythonista3.app/Frameworks/Py2Kit.framework/pylib/xmlrpclib.py", line 1284, in request
return self.single_request(host, handler, request_body, verbose)
File "/var/containers/Bundle/Application/CBE5150F-2F53-4F25-8FE0-DC5236FFA836/Pythonista3.app/Frameworks/Py2Kit.framework/pylib/xmlrpclib.py", line 1317, in single_request
return self.parse_response(response)
File "/var/containers/Bundle/Application/CBE5150F-2F53-4F25-8FE0-DC5236FFA836/Pythonista3.app/Frameworks/Py2Kit.framework/pylib/xmlrpclib.py", line 1494, in parse_response
return u.close()
File "/var/containers/Bundle/Application/CBE5150F-2F53-4F25-8FE0-DC5236FFA836/Pythonista3.app/Frameworks/Py2Kit.framework/pylib/xmlrpclib.py", line 801, in close
raise Fault(**self._stack[0])
Fault: <Fault -32500: 'HTTPTooManyRequests: The action could not be performed because there were too many requests by the client. Limit may reset in 1 seconds.'>
How does this work