Changeset 11694:d38d2b1084b7 in orange for Orange/utils/addons.py


Ignore:
Timestamp:
09/11/13 13:26:28 (7 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Keep all release download urls. Select the source distribution to install.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/utils/addons.py

    r11656 r11694  
    1919 
    2020import shelve 
     21import anydbm 
    2122import xmlrpclib 
    2223import warnings 
     
    3132import platform 
    3233import subprocess 
     34import errno 
    3335import urllib2 
    34 import urlparse 
    35 import posixpath 
    3636import site 
    3737import itertools 
     
    4242import Orange.utils.environ 
    4343 
    44 ADDONS_ENTRY_POINT="orange.addons" 
    45  
    46  
    47 OrangeAddOn = namedtuple('OrangeAddOn', ['name', 'available_version', 'installed_version', 'summary', 'description', 
    48                                          'author', 'docs_url', 'keywords', 'homepage', 'package_url', 
    49                                          'release_url', 'release_size', 'python_version']) 
    50 #It'd be great if we could somehow read a list and descriptions of widgets, show them in the dialog and enable 
    51 #search of add-ons based on keywords in widget names and descriptions. 
     44ADDONS_ENTRY_POINT = "orange.addons" 
     45 
     46 
     47OrangeAddOn = namedtuple( 
     48    'OrangeAddOn', 
     49    ['name', 'available_version', 'installed_version', 'summary', 
     50     'description', 'author', 'docs_url', 'keywords', 'homepage', 
     51     'package_url', 'release_urls'] 
     52) 
     53 
     54ReleaseUrl = namedtuple( 
     55    "ReleaseUrl", 
     56    ["filename", "url", "size", "python_version", "packagetype"] 
     57) 
     58 
     59# It'd be great if we could somehow read a list and descriptions of 
     60# widgets, show them in the dialog and enable search of add-ons 
     61# based on keywords in widget names and descriptions. 
    5262 
    5363INDEX_RE = "[^a-z0-9-']"  # RE for splitting entries in the search index 
    5464 
    55 AOLIST_FILE = os.path.join(Orange.utils.environ.orange_settings_dir, "addons.shelve") 
    56  
    57 def open_addons(): 
     65AOLIST_FILE = os.path.join(Orange.utils.environ.orange_settings_dir, 
     66                           "addons_v2.shelve") 
     67 
     68 
     69def open_addons(flag="r"): 
    5870    try: 
    59         addons = shelve.open(AOLIST_FILE, 'c') 
    60         if any(name != name.lower() for name, record in addons.items()):  # Try to read the whole list and check for sanity. 
    61             raise Exception("Corrupted add-on list.") 
    62     except: 
    63         if os.path.isfile(AOLIST_FILE): 
    64             os.remove(AOLIST_FILE) 
    65         addons = shelve.open(AOLIST_FILE, 'n') 
     71        addons = shelve.open(AOLIST_FILE, flag) 
     72    except anydbm.error as ex: 
     73        if flag in ["r", "w"] and ex.message.startswith("need 'c'"): 
     74            # Need to create it it first. 
     75            s = shelve.open(AOLIST_FILE, "c") 
     76            s.close() 
     77            addons = shelve.open(AOLIST_FILE, flag) 
     78        else: 
     79            if os.path.isfile(AOLIST_FILE): 
     80                os.remove(AOLIST_FILE) 
     81            addons = shelve.open(AOLIST_FILE, 'n') 
     82    else: 
     83        # Try to read the whole list and check for sanity. 
     84        if any(name != name.lower() for name, _ in addons.items()): 
     85            addons.close() 
     86            if os.path.isfile(AOLIST_FILE): 
     87                os.remove(AOLIST_FILE) 
     88            addons = shelve.open(AOLIST_FILE, 'n') 
     89 
    6690    return addons 
    6791 
    6892 
    6993def addons_corrupted(): 
    70     with closing(open_addons()) as addons: 
     94    with closing(open_addons(flag="r")) as addons: 
    7195        return len(addons) == 0 
    7296 
     
    79103 
    80104    index = defaultdict(list) 
    81     with closing(open_addons()) as addons: 
     105    with closing(open_addons(flag="r")) as addons: 
    82106        for name, ao in addons.items(): 
    83107            for s in [name, ao.summary, ao.description, ao.author] + (ao.keywords if ao.keywords else []): 
     
    90114                        index[word[:i+1]].append(name) 
    91115 
     116 
    92117def search_index(query): 
    93118    global index 
     
    95120    words = [word for word in re.split(INDEX_RE, query.lower()) if len(word)>1] 
    96121    if not words: 
    97         with closing(open_addons()) as addons: 
     122        with closing(open_addons(flag="r")) as addons: 
    98123            return addons.keys() 
    99124    for word in words: 
    100125        result.update(index[word]) 
    101126    return result 
     127 
    102128 
    103129def refresh_available_addons(force=False, progress_callback=None): 
     
    121147    docs = {} 
    122148    if progress_callback: 
    123         progress_callback(len(pkg_dict)+1, 1) 
    124     with closing(open_addons()) as addons: 
     149        progress_callback(len(pkg_dict) + 1, 1) 
     150 
     151    with closing(open_addons(flag="c")) as addons: 
    125152        for i, (name, (_, version)) in enumerate(pkg_dict.items()): 
    126             if force or name not in addons or addons[name.lower()].available_version != version: 
     153            installed = addons[name.lower()] if name.lower() in addons else None 
     154            if force or not installed or installed.available_version != version: 
    127155                try: 
    128156                    data = pypi.release_data(name, version) 
    129                     rel = pypi.release_urls(name, version)[0] 
    130  
     157                    urls = pypi.release_urls(name, version) 
     158                    release_urls = \ 
     159                        [ReleaseUrl(url["filename"], url["url"], 
     160                                    url["size"], url["python_version"], 
     161                                    url["packagetype"]) 
     162                         for url in urls] 
    131163                    if readthedocs: 
    132164                        try: 
    133                             docs = readthedocs.project.get(slug=name.lower())['objects'][0] 
     165                            docs = readthedocs.project.get( 
     166                                slug=name.lower())['objects'][0] 
    134167                        except: 
    135168                            docs = {} 
    136                     addons[name.lower()] = OrangeAddOn(name = name, 
    137                                                available_version = data['version'], 
    138                                                installed_version = addons[name.lower()].installed_version if name.lower() in addons else None, 
    139                                                summary = data['summary'], 
    140                                                description = data.get('description', ''), 
    141                                                author = str((data.get('author', '') or '') + ' ' + (data.get('author_email', '') or '')).strip(), 
    142                                                docs_url = data.get('docs_url', docs.get('subdomain', '')), 
    143                                                keywords = data.get('keywords', "").split(","), 
    144                                                homepage = data.get('home_page', ''), 
    145                                                package_url = data.get('package_url', ''), 
    146                                                release_url = rel.get('url', None), 
    147                                                release_size = rel.get('size', -1), 
    148                                                python_version = rel.get('python_version', None)) 
    149                 except Exception, e: 
     169                    addons[name.lower()] = OrangeAddOn( 
     170                        name=name, 
     171                        available_version=data['version'], 
     172                        installed_version=installed.installed_version if installed else None, 
     173                        summary=data['summary'], 
     174                        description=data.get('description', ''), 
     175                        author=str((data.get('author', '') or '') + ' ' + 
     176                                   (data.get('author_email', '') or '')).strip(), 
     177                        docs_url=data.get('docs_url', docs.get('subdomain', '')), 
     178                        keywords=data.get('keywords', "").split(","), 
     179                        homepage=data.get('home_page', ''), 
     180                        package_url=data.get('package_url', ''), 
     181                        release_urls=release_urls 
     182                    ) 
     183                except Exception: 
    150184                    import traceback 
    151185                    traceback.print_exc() 
    152                     warnings.warn('Could not load data for the following add-on: %s'%name) 
     186                    warnings.warn( 
     187                        'Could not load data for the add-on: %s' % name) 
     188 
    153189            if progress_callback: 
    154                 progress_callback(len(pkg_dict)+1, i+2) 
     190                progress_callback(len(pkg_dict) + 1, i + 2) 
    155191 
    156192    rebuild_index() 
     193 
    157194 
    158195def load_installed_addons(): 
    159196    found = set() 
    160     with closing(open_addons()) as addons: 
     197    with closing(open_addons(flag="c")) as addons: 
    161198        for entry_point in pkg_resources.iter_entry_points(ADDONS_ENTRY_POINT): 
    162             name, version = entry_point.dist.project_name, entry_point.dist.version 
    163             #TODO We could import setup.py from entry_point.location and load descriptions and such ... 
     199            name = entry_point.dist.project_name 
     200            version = entry_point.dist.version 
     201 
    164202            if name.lower() in addons: 
    165                 addons[name.lower()] = addons[name.lower()]._replace(installed_version = version) 
     203                addons[name.lower()] = addons[name.lower()]._replace(installed_version=version) 
    166204            else: 
    167                 addons[name.lower()] = OrangeAddOn(name = name, 
    168                     available_version = None, 
    169                     installed_version = version, 
    170                     summary = "", 
    171                     description = "", 
    172                     author = "", 
    173                     docs_url = "", 
    174                     keywords = "", 
    175                     homepage = "", 
    176                     package_url = "", 
    177                     release_url = "", 
    178                     release_size = None, 
    179                     python_version = None) 
     205                addons[name.lower()] = OrangeAddOn( 
     206                    name=name, 
     207                    available_version=None, 
     208                    installed_version=version, 
     209                    summary="", 
     210                    description="", 
     211                    author="", 
     212                    docs_url="", 
     213                    keywords="", 
     214                    homepage="", 
     215                    package_url="", 
     216                    release_urls=[]) 
    180217            found.add(name.lower()) 
     218 
    181219        for name in set(addons).difference(found): 
    182             addons[name.lower()] = addons[name.lower()]._replace(installed_version = None) 
     220            addons[name.lower()] = addons[name.lower()]._replace(installed_version=None) 
     221 
    183222    rebuild_index() 
    184223 
     
    318357        extra_kwargs["startupinfo"] = startupinfo 
    319358 
    320     subprocess.check_output([executable, setup_script] + args, 
    321                              cwd=source_root, 
    322                              stderr=subprocess.STDOUT, 
    323                              **extra_kwargs) 
     359    process = subprocess.Popen([executable, setup_script] + args, 
     360                               cwd=source_root, 
     361                               stdout=subprocess.PIPE, 
     362                               stderr=subprocess.STDOUT, 
     363                               bufsize=1,  # line buffered 
     364                               **extra_kwargs) 
     365    output = [] 
     366    while process.poll() is None: 
     367        try: 
     368            line = process.stdout.readline() 
     369        except (OSError, IOError) as ex: 
     370            if ex.errno != errno.EINTR: 
     371                raise 
     372        else: 
     373            output.append(line) 
     374            print line, 
     375 
     376    if process.returncode: 
     377        raise subprocess.CalledProcessError( 
     378                  process.returncode, 
     379                  setup_script, 
     380                  "".join(output) 
     381              ) 
    324382 
    325383 
    326384def install(name, progress_callback=None): 
    327     if progress_callback: 
    328         progress_callback(1, 0) 
    329  
    330     with closing(open_addons()) as addons: 
     385    with closing(open_addons(flag="r")) as addons: 
    331386        addon = addons[name.lower()] 
    332     release_url = addon.release_url 
     387 
     388    source_urls = [url for url in addon.release_urls 
     389                   if url.packagetype == "sdist"] 
     390    release_url = source_urls[0] 
    333391 
    334392    try: 
    335393        tmpdir = tempfile.mkdtemp() 
    336394 
    337         stream = urllib2.urlopen(release_url, timeout=120) 
    338  
    339         parsed_url = urlparse.urlparse(release_url) 
    340         package_name = posixpath.basename(parsed_url.path) 
    341         package_path = os.path.join(tmpdir, package_name) 
     395        stream = urllib2.urlopen(release_url.url, timeout=120) 
     396 
     397        package_path = os.path.join(tmpdir, release_url.filename) 
    342398 
    343399        progress_cb = (lambda value: progress_callback(value, 0)) \ 
     
    380436        ao.uninstall(True) 
    381437    except ImportError: 
    382         raise Exception("Pip is required for add-on uninstallation. Install pip and try again.") 
     438        raise Exception("Pip is required for add-on uninstallation. " 
     439                        "Install pip and try again.") 
     440 
    383441 
    384442def upgrade(name, progress_callback=None): 
    385443    install(name, progress_callback) 
    386444 
    387 load_installed_addons() 
    388  
     445 
     446try: 
     447    load_installed_addons() 
     448except Exception as ex: 
     449    # Do not let an exception propagate during the import phase, 
     450    # It should not even be called at import only if/when add-ons 
     451    # db is actually requested. 
     452    warnings.warn("'load_installed_addons' failed with %s" % ex, 
     453                  UserWarning) 
    389454 
    390455 
     
    399464registered = __read_addons_list(os.path.join(Orange.utils.environ.orange_settings_dir, "add-ons.txt"), False) + \ 
    400465             __read_addons_list(os.path.join(Orange.utils.environ.install_dir, "add-ons.txt"), True) 
     466 
     467if registered: 
     468    warnings.warn("'add-ons.txt' is deprecated. " + 
     469                  "Please use setuptools/entry points.", 
     470                  UserWarning) 
    401471 
    402472for name, path in registered: 
Note: See TracChangeset for help on using the changeset viewer.