Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pip module #39

Closed
briarfox opened this issue Dec 31, 2014 · 51 comments · Fixed by #314
Closed

pip module #39

briarfox opened this issue Dec 31, 2014 · 51 comments · Fixed by #314

Comments

@briarfox
Copy link
Contributor

The pip module is almost done. Does anyone know if pypi modules are only tar.gz or are .zip used as well? I have not been able to find a .zip example.

Currently pip is using a .sh script to install the pypi package since setup.py will not work in pythonista. The .sh uses common stash modules. Is this exceptable or should pip handle ungziping, mkdir and rm by itself? Currently the .sh uses: tar, mkdir, mv and rm stash modules.

@ywangd
Copy link
Owner

ywangd commented Dec 31, 2014

@briarfox My opinion is to use stash modules as much as possible to avoid repetition. I do realise that the current shell script is quite limited due to lack of program control constructs. So I am not sure how much functionality the pip module would have. I am still thinking about the possibility to interleave python code in a shell script so the shell script can be more useful.

@ywangd ywangd added this to the v0.3.0 milestone Jan 1, 2015
@briarfox
Copy link
Contributor Author

briarfox commented Jan 2, 2015

Ok currenly pyhonisa sie-packages does no load .pth files ( it's not in the site path list) I am wondering what the best way to handle packges is. When downloading a pypi package there is the root folder which usually contains documentation, examples and the acual package. The acual package needs to be in site-packages direcly. So the question is do we keep he documentation/examples and if so where?

Options:

  1. Move the package folder into site-packages, delete root folder.
  2. Keep the root folder in site-packages and move the package folder into site-packages as well.
  3. Create a pypi_packages folder inside of site-packages. Store root package folder there. Move package folder into site-packages.
  4. Same as 3 but put the pypi_packages folder in documents and add the package folder to site-packages.
  5. currently wont work Put the root package folder inside site-packages. Create a pypi_path.pth file and append each package location.

@ywangd @dgelessus

@jsbain
Copy link
Contributor

jsbain commented Jan 2, 2015

Another option: for each top level package (can a pypi contain more than one?), create a .py file in site-packages with that name. That py would then redirect any imports to the proper location, under the root wherever you place it. See below for an example..

In this example, typing

import myModule

first imports site-packages/myModule.py. But that script below tries to import myModule, starting at the root, in this example '~/Documents/myModule-1.0/`. If successful, it points sys.modules to whatever it imported. Importing hierarchical packages work as expected, because of how the import system works. To make things truly transparent probably requires mucking with the traceback if an ImportError occurs, to make it seem as if the script in site-packages was not the caller.

# site-packages/myModule.py
#  import myModule      will import this script first, but then will end up importing 
def _do_import(p):
    import sys, os, imp
    m=imp.find_module(__name__)
    path=os.path.expanduser(p)
    module_path = imp.find_module(__name__,[path])
    if module_path:
        try:
            module = imp.load_module(__name__,*module_path)             
        except ImportError:
            raise    #maybe fake traceback?
        sys.modules[__name__]=module
    else:
        pass 
        #. What to do if package not found anymore?  Haven't tested...
         # maybe    del sys.modules[__name__]    so it doesn't point back here?  
         # maybe raise an ImportError?, which maybe does the same thing
_do_import('~/Documents/myModule-1.0/')

pip could write out this script, just substituting the proper path.
Alternatively, you could be a little more generic, and always write the same file. That file would be a slight variation of the above, except instead of providing the path explicitly, it would scan site-packages for .pth files, then provide that path list to find_module

@ywangd
Copy link
Owner

ywangd commented Jan 2, 2015

@briarfox I would prefer to simply use Option 1, i.e. move the actual package folder directly under site-packages. As I understand it, this behaviour is more in line with what the real pip does:

  • Copy the actual package folder directly under site-packages
  • Creates some metadata information under an egg-info directory. We don't really need this, but if you really want it, a possible solution is to parse the setup.py file (mainly to get the description, author, contact etc.)
  • The original doc and examples are not kept anyway.
    An additional useful feature is make pip keep track of what packages were installed by it.

Stash can manage its own python path. In fact, I am going to add a PYTHONPATH environment variable so that additional path can be added on top of Pythonista default path. The downside of this approach is that the packages will only be available when Stash is running. So I only plan to use it for helper libraries, e.g. a lib folder under stash folder which contains some helper function such as unexpanduser (i.e. these functions are only meant to be used from within stash).

@jsbain your approach is indeed quite clever. But for this issue, I kinda think a simple approach is viable.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 2, 2015

@ywangd Going with Option 1. It turns out that pypi does use .zip as well. I should have that added in tonight and will get a working pull request up. I agree that it would be useful to have pip keep track of packages installed through pip. Having the ability to update, list and remove packages would be handy. I'll work on that next.

@jsbain Clever script, thanks.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 5, 2015

Current pull request of pip uses the common package naming conventions but I'm finding packages that do not use the standard. It looks like I'll need to parse setup.py to find the package folders.

@ywangd
Copy link
Owner

ywangd commented Jan 5, 2015

@briarfox I noticed the same issue when trying to install github3.py. So it is probably necessary to somehow parse the setup function (mainly to get the packages argument).

@jsbain Regarding to your idea about letting pip handle github package installation, I personally think the github package handling deserves its own command as pypi and github behaves quite differently. It would be ideal to have a github command that also does the search, (release) versions, download etc. through formal GitHub APIs ( the one I am thinking to use right now is github3.py).

With that being said, I do agree that since both pypi and github manage packages, they should be somehow unified to simply the overall user-experience. So maybe add a wrapper function , e.g. require(), as an entry point to call either pypi or github. This function will be located in the lib folder (#5) of stash. It seems to me that this approach is more modular and easier to maintain down the path.

Thanks!

@ywangd ywangd mentioned this issue Jan 6, 2015
@briarfox
Copy link
Contributor Author

briarfox commented Jan 6, 2015

@ywangd @dgelessus I'm running into issues with parsing setup.py. The average setup.py is easy to parse:

setup(name='somepackage',
          packages =  [somepackage],
          etc

However there are may ways to pass params to setup.py. example:

NAME = 'somepackage'
PACKAGES = [somepackage']
params = dict(name=NAME,packages=PACKAGES)
setup(**params)

How can we handle all the different syntax? If I could just mock setup to return a dict of the params I'd be set. However I cant import or execfile setup.py becuase of the setuptools/distutils imports.

@dgelessus
Copy link
Contributor

How about execing the code in an environment with a modified __import__ function that returns simple dummy modules for setuptools and distutils? The fake setup function could assign its **kwargs to some other attribute accessible from outside, which could then be read afterwards.

@jsbain
Copy link
Contributor

jsbain commented Jan 6, 2015

@ywangd I was thinking specifically of pypi packages that are hosed on github, so have a setup.py, etc, and look identical to the real pypi modules except their source ( and in many cases I think pypi points to a github archive for the actual download!). The use case would be forked versions needed to run on pythonista. I agree a separate mechanism might be needed for true github modules that don't have setup.py.

@dgelessus That is similar to the approach used by @pudquick for pipista v2, though he served an actual slimmed down version of setuptools with mods for the pythonista environment. Note that this script would need a lot of updates for pythonista 1.5+, since xmlrpclib is already installed, etc. also, as I recall there were issues in using exec script in g inside of a function, breaking some setup.py that had imports, though exec script in g,g did work as expected.

Another question.... What prevents running setup.py in general? 1) missing _osx_support.py 2) some errors in getting some sysconfig parameters, because pythonista lies about some paths. 3) And maybe some other issues.
I think I was recently able to get a package to install by patching a few distutils functions which were raising exceptions, modifying the offending sysconfig parameters, and providing a minimal _osx_support.py. At the time I didn't see much value in that approach, but I forget why...

@pudquick
Copy link

pudquick commented Jan 6, 2015

Neat. Didn't know this project existed. Thanks for the mention.

I did a custom setuptools with shortcircuited code in it because it expected an environment where some kind of alternate process spawning was possible.

Not all python modules are pure python and setuptools didn't have native support for detecting the (jailed/sandboxed) Pythonista environment, thereby knowing to fail if compilation was required.

I tried to wanted to just "let it fail", but the exec bug didn't let a core piece of the logic work.

So I resorted to basic heuristics where most modules contained a single modulename.py or modulename/ folder and just extract -> installed them. It was on the user to know if it would work or not.

I may have to look into this again :)

@ywangd
Copy link
Owner

ywangd commented Jan 6, 2015

@jsbain I get your point now. I agree this specific use case of GitHub repo shares a lot similarity with pypi. This is indeed the situation of the external libraries that the git command depends on.

@ywangd
Copy link
Owner

ywangd commented Jan 6, 2015

@briarfox
Here is a solution I came up with based on all previous discussions. The key idea is to fake the setuptools module and execute the pakcage's setup.py file. Compared to @pudquick 's approach, one difference is that the fake is more light weighted as the faked module is created programmatically instead of from files. Another difference is that the setup() function is now provided by pip. So once the setup.py is executed. the installation is mostly completed.

A few things to note are:

  • The sample code is not a complete pip. It focus only on how setup.py can be executed with the faked modules, not actually install it. The setup() function needs to be expanded to contain actual installation code.
  • github3.py is used as the test case for the code.

The sample code is as follows:

import sys
import os
from types import ModuleType

def make_module(new_module, doc="", scope=locals()):
    """
    modified from 
    http://dietbuddha.blogspot.com.au/2012/11/python-metaprogramming-dynamic-module.html
    make_module('a.b.c.d', doc="", scope=locals()]) -> <module built-in="built-in" d="d">

    * creates module (and submodules as needed)
    * adds module (and submodules) to sys.modules
    * correctly nests submodules as needed
    * not overwritting existing modules (my modification to the original function)
    """
    module_name = []

    for name in new_module.split('.'):
        m = ModuleType(name, doc)
        parent_module_name = '.'.join(module_name)

        if parent_module_name:
            if parent_module_name not in sys.modules.keys():  # do not overwrite existing modules
                print 'creating parent', parent_module_name
                setattr(sys.modules[parent_module_name], name, m)
        else:
            if m not in scope.keys(): # do not overwrite existing modules
                scope[name] = m

        module_name.append(name)

        name_path = '.'.join(module_name)
        if name_path not in sys.modules.keys(): # do not overwrite existing modules
            sys.modules[name_path] = m

    return sys.modules['.'.join(module_name)]

def setup(*args, **kwargs):
    print kwargs['packages']
    # NOTE: pip installation code here
    # e.g. _stash('mv package_folder ~/Documents/site-packages/package_folder') etc.
    pass

saved_modules = sys.modules
saved_cwd = os.getcwd()

try:
    # pip code here (download and unzip)

    # Fake the setuptools modules so setup.py can run
    # fake   from setuptools import setup
    st = make_module('setuptools')
    st.__dict__['setup'] = setup  # the custom setup function will perform the installation

    # more sub-modules to fake
    # fake    from setuptools.command.test import test
    test = make_module('setuptools.command.test')
    test.__dict__['test'] = type('test', (), {})  # dummy class so setup can run

    os.chdir('TOP_LEVEL_FOLDER_of_the_UNZIPPED_PACKAGE')
    execfile('setup.py')

finally:  # remove the fake modules
    sys.modules = saved_modules
    os.chdir(saved_cwd)

@ywangd
Copy link
Owner

ywangd commented Jan 6, 2015

I do realise above code has some limitations:

  • The faked modules, sub modules and functions have to be manually added. For now it only deals with from setuptools import setup and from setuptools.command.test import test. These two imports seem to cover a great number of packages.
  • This approach as well as the entire pip command work only for pure Python packages. I think this is fine as packages require compilations are out of scope imo.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 6, 2015

@ywangd looks good, thanks. I should have an update today.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 6, 2015

@pudquick Did you want to handle pip for stash? I used pipista for reference. I've been sick and have a new born so my time coding has been greatly reduced. If you'd like to take it over let me know, no issues if you scrap my pip version.

@ywangd
Copy link
Owner

ywangd commented Jan 6, 2015

@briarfox Please take time to rest and look after the baby. No pressure here at all.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 7, 2015

@yawngd Thanks. I'll keep working at it, but its slow going. Figured if @pudquick wanted to work on it, that be fine.

Do we want pip handleing setup.py or would that be more appropriate for the python module? Since users might like to install a script without pip by python setup.py install.

@ywangd
Copy link
Owner

ywangd commented Jan 7, 2015

@briarfox I would prefer to let pip handle setup.py. Because what we do here really is to fake the install process by hijacking the normal execution of setup.py. It is not really a true equivalent to python setup.py install in my opinion.

I just realised @pudquick is the original author of shellista. Thanks for the inspiration on getting me started on StaSh.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 7, 2015

@ywangd I'm finding a wierd issue with python cwd. it seems that using the cd module is different then using os.chdir(). I'm finding that I need to use _stash('cd') to manipulate paths when jusing stash modules and os.chdir() for any paths needed outside of a stash module.

example:

#example 1 cwd ~/Documents
#Change directory with cd module
_stash('cd ~/Documents/site-packages')
_stash('tar -xzf ~/Documents/site-packages/some_file.tar.gz) #tar extracts to current dir
#This results in the unarchived file in Documents/site-packages
#example 2 cwd ~/Documents
os.chdir(os.path.expanduser('~/Documents/site-packages'))
_stash('tar -xzf ~/Documents/site-packages/some_file.tar.gz) 
#This results in tar extracting into Documents/

@briarfox
Copy link
Contributor Author

briarfox commented Jan 7, 2015

@ywangd The setup.py is running with mixed results. Simple setup.py files work great but more complicated ones seem to run into a lot of import errors, even when the package works in pythonista. This is my current thought, let me know what you think.

First we look for common module naming. Such as mymodule, mymodule.py. If found, simpley move folder/script. If not found, then try to run setup.py

@ywangd
Copy link
Owner

ywangd commented Jan 8, 2015

@briarfox The answer to the cd issue quite involved. A general rule is that you cannot mix the use of _stash('cd XXX') and os.chdir('XXX').

In your two examples, they actually both correctly extract archives to ~/Documents/site-packages. But I guess in your real code there may be a _stash('cd XXX') comes before the os.chdir in the 2nd example. This would cause the extraction goes in to the wrong place.

The reason is stash keeps track of its own cwd for each sub-shell. If no _stash('cd xxx') has to been called, the cwd is equal to the system's cwd. But once a _stash('cd xxx') command is called, stash records it and use xxx as the cwd onwards (subsequent os.chdir will then have no effect on other stash command).

This behavior is to simulate the sub-shell environment where cwd changes in sub-shell does not affect its parent shell. I have to admit this may not be the best design as I sometimes get confused by my own code. But if you stick to use _stash() command, things should be more consistent.

@ywangd
Copy link
Owner

ywangd commented Jan 8, 2015

@briarfox I agree with you that we can try simple module names first before resorting to execute setup.py.

Could you please given me an example of the more complicated setup.py. I'd like to see if there is chance to remedy failed imports on the fly.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 8, 2015

@ywangd Would I be better off sticking to using os.chdir() instead of _stash('cd ...')?

Two of the modules that I've tried are scapy and Twisted. The current Twisted does not work in pythonista but I believe it will after next pythonista update. We could try to stub in other modules that are missing, but it seems that most cases can be solved by looking for the package directory.

@ywangd
Copy link
Owner

ywangd commented Jan 8, 2015

@briarfox As long as you use the same function to manipulate directory, it should be fine. I personally would prefer to use _stash('cd ...'). Because other commands needs to be executed by stash anyway, I kinda think it is better to allow stash keep track of all the information.

I had a look at the two modules. Yes the import processes really vary quite a bit. It is probably too much bandage work to cater all of them.

@ywangd
Copy link
Owner

ywangd commented Jan 8, 2015

@briarfox Another reason to use _stash('cd') over os.chdir() is that a command, e.g. pip, usually does not want affect parent shell's cwd once it is finished, i.e. Users normally do not expect their cwd gets changed after calling pip. If os.chdir() is used, pip has to keep track of it and revert back to the original cwd once it is finished. These tasks will be handled automatically if _stash('cd') is used.

@dgelessus
Copy link
Contributor

Does this version of pip attempt to handle dependencies in any way? If it is possible to read the required dependencies from the setup.py file, it might be useful to at least check if the dependency is part of default Pythonista or found in site-packages, and if not print a warning.

@briarfox
Copy link
Contributor Author

briarfox commented Jan 8, 2015

@dgelessus It currently does not install dependencies. It will attempt to load the module and print out a warning on possible dependencies if failed. I'd really like to grab dependencies but there are many ways to use setup.py so simplely parsing it is not a guarantee. If we could just find a way to handle majority of setup.py imports it would be easy to rely on setup.py working. I'll look into at least displaying the needed dependencies, if i can't auto install them.

I'm still working on adding 3rd party github packages.

@pudquick
Copy link

@ywangd re: shellista inspiration - you're welcome :)

@pudquick
Copy link

So. Diving back into this (whee).

pip is an exceedingly complex tool. But honestly, I only ever use a fraction of the functionality of it. Really what I want is the ability to: find, download, unpack, and install a package from PyPI

When I was attempting to do this before, I came to the realization that pip (or at least the pieces I was using of it) only was really handling the search/download/unpack part. The install was still being handled by setuptools.

To see what it's doing in the handoff, do the following:

  • run python (cpython, not Pythonista) on a system: python -c 'import pip; print pip'
  • Check the directory that pip is installed into
  • Search the file contents of that module directory for: def call_subprocess
  • Depending on which version of pip you have, it could be defined in one of several files
  • Right after the function definition, you'll probably see this as the first line of code:
    if command_desc is None:
  • At the same level of indentation, right before it, add one more line of code, so it looks like this:
    print cmd.__repr__()
    if command_desc is None:
  • To trigger what I'm talking about, then install a package with pip. Here's an example:sudo pip install beautifulsoup4(If it's already installed, you may need to uninstall first - or use "install -I" to ignore its presence)

You will get the commands that pip (after finding, downloading, unpacking, and analyzing the package) sends to python to kick off the install process.

Here's example output from beautifulsoup4:

Collecting beautifulsoup4
  Downloading beautifulsoup4-4.3.2.tar.gz (143kB)
    100% |################################| 143kB 1.6MB/s 
['/usr/bin/python2.7', '-c', "\n__file__ = '/tmp/pip-build-YtDF6D/beautifulsoup4/setup.py'\nfrom setuptools.command import egg_info\nimport pkg_resources\nimport os\nimport tokenize\ndef replacement_run(self):\n    self.mkpath(self.egg_info)\n    installer = self.distribution.fetch_build_egg\n    for ep in pkg_resources.iter_entry_points('egg_info.writers'):\n        # require=False is the change we're making:\n        writer = ep.load(require=False)\n        if writer:\n            writer(self, ep.name, os.path.join(self.egg_info,ep.name))\n    self.find_sources()\negg_info.egg_info.run = replacement_run\nexec(compile(\n    getattr(tokenize, 'open', open)(__file__).read().replace('\\r\\n', '\\n'),\n    __file__,\n    'exec'\n))\n", 'egg_info', '--egg-base', 'pip-egg-info']
Installing collected packages: beautifulsoup4
  Running setup.py install for beautifulsoup4
['/usr/bin/python2.7', '-c', "import setuptools, tokenize;__file__='/tmp/pip-build-YtDF6D/beautifulsoup4/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))", 'install', '--record', '/tmp/pip-rHy2y4-record/install-record.txt', '--single-version-externally-managed', '--compile']
Successfully installed beautifulsoup4-4.3.2

As you can see, pip essentially "bows out" of the process. It's generated a temporary script that's passed to python as the -c argument which relies on only setuptools from that point onwards.

Basically, if you're building a pip equivalent - or at least trying to cover 80%+ of the functional code involving downloading from PyPI, unpacking, and installing - then really you only need:

  • Searching PyPI
  • Unpacking the archived file
  • Triggering install using setuptools

The hard part I was running into before and I'm seeing again (now that I'm revisiting it) is controlling the installation so that it doesn't muck up your globals() and locals(), since we only have one running python interpreter instance. pip on a normal system (aka not Pythonista) solves this by running a new separate instance of python.

If we can keep that segmented (if we care about that, even), we can probably patch around the rest. For instance: I already know how to control / redirect the directory that setuptools will attempt to install the package into :)

@briarfox
Copy link
Contributor Author

@pudquick I do not believe we have access to setuptools. We had tried to mock distutils, setuptools but the list is long. It works great on simple packages but it was getting rather cumbersome on more complet setup.py files. The current pip handling:

  1. Check for a folder inside the archive of the package. This is pretty common, so we simply move that folder into site-packages.
  2. Look for the package name in a src folder, if found move that folder into site-packages.
  3. When the above two fail, we try to run setup.py to grab the packages, py_modules and scripts variables.

It would be great to get a working setuptools and distutils.

@ywangd
Copy link
Owner

ywangd commented Jan 23, 2015

@pudquick Thanks for your explanation.

I haven't spent much time on this topic. So I don't quite understand what exactly you mean by "muck up globals() and locals()". Is it possible to run the script with a copy of globals() and locals()? e.g. execfile('filename', dcit(globals()), dict(locals()) so that the original namespace will not be changed?

I did notice that pip is a wrapper of setuptools, which in turn is a wrapper of distutils. So only implementing pip is not going to solve the whole problem as Pythonista does not have full functional distutils module. The most used function in pip is setup(), which is a function from distutils and not available in Pythonista.

What I think is that if we can intercept the call to setup(), we can get most if not all information required to install the module. For pure python modules, we will then be able to find the actual files/directories need to be copied over.

@ywangd ywangd removed this from the v0.3.0 milestone Jan 24, 2015
@pudquick
Copy link

@briarfox I like your pip module so far :)

Here's a different take on it: https://gist.github.com/pudquick/cb7b6413a54162fd1c00

Try it out as 'pip2.py' if you want to see it in action. It's not a complete implementation, but vanilla 'install' is functional. Mine's not stash-specific, so it carries its own support for handling gzip / tar / zip files (makes up about 50% of the code in itself).

But it's got progress indicators for download (with current speed), its own method for parsing setup.py (I split out the 'setup(...)' call and then dummy out up to a max of 10 undefined object names), plus it safely contains modifications to global/local variables by setup.py.

I've got additional ideas for handling setup.py that can probably increase its parsing resilience, but as it's written right now, it's confirmed as able to install simple packages.

If anything in there is helpful, grab what you like.

@ywangd
Copy link
Owner

ywangd commented Jan 26, 2015

@pudquick
I had some quick tries using the script from within stash. Here are the findings:

  • The script throw an error "operation not permitted". I think it is caused by the tmp_dir creation which is not allowed in Pythonista. I made a minor change and it now works in Pythonista (or any iOS, OS X): tmp_dir = os.environ['TMPDIR']
  • I do like how it shows the progress bar and speed. I may grab them for wget.py if that's OK.
  • I have yet to read through the code and see how it works and understand your explanation. I tried to install a package called uritemplate, but it gave an error as "unable to simply parse setup.py"
  • To make the script easily accessible in Stash, I have added its link to the "Pythonista Command Script Index". So it can be easily installed and managed in stash using pcsm install pip-mini (can also try pcsm list and pcsm info pip-mini).

@pudquick
Copy link

@ywangd Sorry about that (the first error)! Just updated the code, should be fine now.

It intentionally makes temp files in __pip_tmp because the intent, later, is to provide an option to preserve the temporary directory when module download succeeds but parsing fails (allowing for a manual option / seeing where it failed).

By all means grab what code you like.

As for the other error you ran into - it indicates there's code outside of the setup(...) block in setup.py necessary for building the 'packages' dict key. I want to try some other tricks before resorting to naively executing setup.py. Pretty sure I can tweak better performance out of it.

@briarfox
Copy link
Contributor Author

@pudquick Great! I'll have a look at it when I get to work.

@pudquick
Copy link

@ywangd @briarfox Updated mocking/dummy capabilities.

There is now a '_dummy' global object it creates that can act like an object with attributes or even like a function. You can do any of the following with a _dummy object:

  • something = _dummy
  • something = _dummy.arbitrary_attribute
  • something = _dummy['arbitrary_key']
  • something = _dummy(...) (returns the _dummy object)
  • something = _dummy.arbitrary_function(...) (returns the _dummy object)

This stubs out most "key = " actions in setup(...).

Also replaced the built-in open(...) with a version that, if unable to open a particular file it returns a _dummy object instead.

@ywangd: This makes it successfully install 'uritemplate' now, for example, which had setup(...) lines like:

  • version = uritemplate.__version__,
  • url = base_url,
  • download_url = '%starball/uritemplate-py-%s' % (base_url, uritemplate.__version__),
  • long_description=open("README.rst").read(),

@GeorgViehoever
Copy link

Looking at the pip module lines 281 ff, I wondered why you chose to prefer manual unpacking of modules (for instance _stash('mv ~/Documents/site-packages/{basename}/{name} ~/Documents/site-packages/{name}'.format(basename=dir_name,name=package_name)) to calling the fake setup module. The setup module at least honors some of the settings that are encoded in the setup.py file, while the other methods just make educated guesses.

I would like to suggest to prefer the setup method to the others if a setup.py file can be found.
Georg

@jsbain
Copy link
Contributor

jsbain commented Aug 16, 2015

if you look through the history, pip has gone through multiple iterations. at one time the fake setup was used.... the problem is that pythonista has a funny folder system, and some of the places setup would try to install things are not writable.

that said, pip does routinely fail on some modules... if you can figure out ways to make it more reliable, i think pull requests are welcome!

@GeorgViehoever
Copy link

@jsbain Thanks, I think I'll have a look at how to improve pip. No promises though...

One more questions: pip has code snippets such as _stash('echo Removing setup files.') Any particular reason why this does not use print() ?

Georg

@ywangd
Copy link
Owner

ywangd commented Aug 17, 2015

You can use print instead and it should be faster.

@ywangd
Copy link
Owner

ywangd commented Mar 2, 2016

Finally come back to this topic ...

I have just largely rewrite the pip command. It nows handles dependencies and also install from github.

The major change is that pip now always try executing the setup.py file first (instead of guessing the source directory). It works in most cases for me. It even works for when the setup call is something like

setup(**kwargs)

or

setup(**get_my_kwargs())

or

setup(packages=find_packages())

This is made possible by parsing the setup file into AST and modify AST to hijack the setup call. So the setup file pretty much runs normally until it reaches the setup() call, where the program flow is then redirected to a stub function. The stub function then records the passed arguments and acts accordingly. This approach seems to work well for all pure Python packages. Only one exception I found is crypto-enigma which has a broken setup file (messed up dependencies). So it is not really the pip's fault. The new code still falls back to package directory guessing when execution of setup file fails. So it does not lose any functionalities compared to the old version.

Try pip install boto3 and you'll see how it works.
Also try pip install selectel/pyte to install pyte from its github repo.

This change is now available in both dev and py3 branch and you can selfupdate to get it.

@ssnkhan
Copy link

ssnkhan commented Mar 30, 2016

Thank you for your efforts with pip. I installed oletools (installed within /site-packages/oletools/), but when executing a python script (such as mraptor.py), I am presented with the following error:

stash: <type 'exceptions.ImportError'>: No module named thirdparty.xglob

The thirdparty directory exists within the oletools folder, suggesting that the toolkit was correctly installed. I suspect this is more likely to be a path issue. Can anyone offer any guidance please?

@ywangd
Copy link
Owner

ywangd commented Mar 31, 2016

@ssnkhan stash pip does not handle path configuration for sub-packages. It is however easy to fix by moving the thirdparty folder to directly under site-packages.

@ssnkhan
Copy link

ssnkhan commented Mar 31, 2016

Thank you @ywangd. This makes some scripts work (such as olevba.py), however, some tools, existing at the same location as /olevba.py (such as /mraptor.py) throw up the following error:

stash: <type 'exceptions.ImportError'>: No module named thirdparty.xglob

I also find that the auto-complete for scripts only works within the active directory in which the tool is stored - is there anyway I can link these somehow, so that I can accept the script from any directory?

I am taking a foundation in Python at present, so I apologise for what would otherwise be considered a very newbie question.

EDIT: Call script from any location -- solved, I used alias.py and linked the script from there. However, the alias does not persist after Pythonista is closed. How can the alias be made more permanent?

@ywangd
Copy link
Owner

ywangd commented Apr 1, 2016

The errors can be fixed by add a few settings in .stashrc file located inside stash's installation root. You can create a new file if it does not exist.

Add following contents to the file:

PYTHONPATH=~/Documents/site-packages/oletools:$PYTHONPATH
BIN_PATH=~/Documents/site-packages/oletools:$BIN_PATH

The first line should solve the import issue and the second line allows you to call the script anywhere with auto-completion. The aliases can also be persistent by add them to this file.

To create this file, you can issue following commands:

cd $STASH_ROOT
touch .stashrc
edit .stashrc

Now you should have this file opened in an editor tab. You'll need to restart StaSh or even Pythonista after you finish typing.

@ssnkhan
Copy link

ssnkhan commented Apr 2, 2016

Thank you, that works perfectly! Supposing I have other scripts in other directories which I also wish to call from anywhere, how is that best achieved? For instance, I installed balbuzard via pip, and adding the following lines to .stashrc didn't work:

PYTHONPATH=~/Documents/site-packages/oletools:$PYTHONPATH
BIN_PATH=~/Documents/site-packages/oletools:$BIN_PATH

PYTHONPATH=~/Documents/site-packages/balbuzard:$PYTHONPATH
BIN_PATH=~/Documents/site-packages/balbuzard:$BIN_PATH

oletools scripts still auto-complete, but balbuzard ones don't. I have googled this too, but struggling to find the answer, so I appreciate your continued patience with me!

@ywangd
Copy link
Owner

ywangd commented Apr 4, 2016

It worked for me. Did you try restart Pythonista (i.e. double click Home button and swipe up to remove Pythonista from the task list)?

Also it is possible to add multiple entries in one line as:

PYTHONPATH=~/Documents/site-packages/balbuzard:~/Documents/site-packages/oletools:$PYTHONPATH
BIN_PATH=~/Documents/site-packages/balbuzard:~/Documents/site-packages/oletools:$BIN_PATH

@ssnkhan
Copy link

ssnkhan commented Apr 4, 2016

@ywangd oletools works, balbuzard refuses to autocomplete. I've tried placing the instructions on seperate lines as well in their short hard form as you've suggested, but while oletools continues to work, balbuzard does not autocomplete. I have killed the app from the dock and checked via cat and the file has saved the commands.

@ssnkhan
Copy link

ssnkhan commented Apr 4, 2016

Resolved — pardon my stupidity, I was editing the file in ~Documents, rather than $STASH_ROOT! All working now, many thanks :)

@SynAck3
Copy link
Contributor

SynAck3 commented Jan 1, 2018

What are the outstanding tasks to close this issue out?

bennr01 added a commit to bennr01/stash that referenced this issue May 4, 2018
#About this commit/PR
This is a commit/PR to close a large number of old/fixed issues at once using the github `fix #<issuenumber>` syntax.
Below is a list of all issues which will automatically be closed by merging this commit/PR into `ywangd/master`.
Behind each issue number is a short summary about the issue or why it can be closed.

#Issues which can be closed

**Py3 related:**
- fix ywangd#281 (py3 had not yet been supported)
- fix ywangd#261 (python3 pip installation)
- fix ywangd#246 (python3 code in python2)
- fix ywangd#222 (python3 command in stash)
- fix ywangd#200 (pip now works (at least partially) with python3)
- close ywangd#197 (general pythonista3 compatibility thread)


**Questions which have been answered / unfixable bugs:**
- close ywangd#310 (pip installation with c code)
- close ywangd#292 (StaSh is not dead; also a question regarding py3)
- close ywangd#290 (pip installation, problem solved)
- close ywangd#289 (answer provided, no further activity by reporter)
- close ywangd#279 (pip install protects standard distribution)
- close ywangd#245 (answered)
- close ywangd#244 (not possible (unless someone finds a workaround)
- close ywangd#243 (c-code in pip installation)
- close ywangd#208 (question answered)


**implemented suggestions/changes:**
- fix ywangd#273 (partially implemented as part of py2and3)
- fix ywangd#193 (implemented by now)
- fix ywangd#63  (subcommand completion; implemented long ago)
- fix ywangd#39  (pip; implemented long ago)

**fixed bugs:**
- fix ywangd#308 (https error in pip; fixed)
- fix ywangd#223 (old beta-related bug)
- fix ywangd#211 (old beta-related bug)
- fix ywangd#209 (seems to be fixed, can not reproduce)
- fix ywangd#206 (git did not clone into subdirectory; fixed)
- fix ywangd#98  (telnet did not send \r\n; fixed)


**other stuff:**
- close ywangd#260 (no useful bug description)
- close ywangd#258 (no useful bug description)
- close ywangd#251 (not a bug but an announcement, no longer relevant)
- close ywangd#241 (works now)
- close ywangd#219 (no further acivity by reporter, likely user error)


#Issues which can be closed, but where the underlying issue has not been fixed
- close ywangd#285 (pip command too complex)
- close ywangd#291 (pip installation question, but not c-code related. But seems package-related)
- close ywangd#274 (pip install problem, at least partial related to c-code, no further activity by reporter)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants