GithubHelp home page GithubHelp logo

pyar / fades Goto Github PK

View Code? Open in Web Editor NEW
214.0 7.0 44.0 8.95 MB

fades is a system that automatically handles the virtualenvs in the cases normally found when writing scripts and simple programs, and even helps to administer big projects.

Home Page: https://fades.readthedocs.io

License: GNU General Public License v3.0

Python 99.14% Shell 0.29% Makefile 0.11% Batchfile 0.46%
python virtualenv pip pypi packaging

fades's Introduction

Documentation Status https://coveralls.io/repos/PyAr/fades/badge.svg?branch=master&service=github Snap Status Appveyor Status

fades is a system that automatically handles the virtual environments in the cases normally found when writing scripts and simple programs, and even helps to administer big projects.

resources/logo256.png

fades will automagically create a new virtual environment (or reuse a previous created one), installing the necessary dependencies, and execute your script inside that virtual environment, with the only requirement of executing the script with fades and also marking the required dependencies.

(If you don't have a clue why this is necessary or useful, I'd recommend you to read this small text about Python and the Management of Dependencies .)

The first non-option parameter (if any) would be then the child program to execute, and any other parameters after that are passed as is to that child script.

fades can also be executed without passing a child script to execute: in this mode it will open a Python interactive interpreter inside the created/reused virtual environment (taking dependencies from --dependency or --requirement options).

Click in the following image to see a video/screencast that shows most of fades features in just 5'...

resources/video/screenshot.png

...or inspect these several small GIFs that show each a particular fades functionality, but please keep also reading for more detailed information...

When you write an script, you have to take two special measures:

  • need to execute it with fades (not python)
  • need to mark those dependencies

At the moment you execute the script, fades will search a virtual environment with the marked dependencies, if it doesn't exists fades will create it, and execute the script in that environment.

You can always call your script directly with fades:

fades myscript.py

However, for you to not forget about fades and to not execute it directly with python, it's better if you put at the beggining of the script the indication for the operating system that it should be executed with fades...

#!/usr/bin/env fades

...and also set the executable bit in the script:

chmod +x yourscript.py

You can also execute scripts directly from the web, passing directly the URL of the pastebin where the script is pasted (most common pastebines are supported, pastebin.com, gist, linkode.org, but also it's supported if the URL points to the script directly):

fades http://myserver.com/myscript.py

The procedure to mark a module imported by the script as a dependency to be installed by fades is by using a comment.

This comment will normally be in the same line of the import (recommended, less confusing and less error prone in the future), but it also can be in the previous one.

The simplest comment is like:

import somemodule   # fades
from somepackage import othermodule    # fades

The fades is mandatory, in this examples the repository is PyPI, see About different repositories below for other examples.

With that comment, fades will install automatically in the virtual environment the somemodule or somepackage from PyPI.

Also, you can indicate a particular version condition, examples:

import somemodule   # fades == 3
import somemodule   # fades >= 2.1
import somemodule   # fades >=2.1,<2.8,!=2.6.5

Sometimes, the project itself doesn't match the name of the module; in these cases you can specify the project name (optionally, before the version):

import bs4   # fades beautifulsoup4
import bs4   # fades beautifulsoup4 == 4.2

If no script or program is passed to execute, fades will provide a virtual environment with all the indicated dependencies, and then open an interactive interpreter in the context of that virtual environment.

Here is where it comes very handy the -i/--ipython option, if that REPL is preferred over the standard one.

In the case of using an interactive interpreter, it's also very useful to make fades to automatically import all the indicated dependencies, passing the --autoimport parameter.

Apart of marking the imports in the source file, there are other ways to tell fades which dependencies to install in the virtual environment.

One way is through command line, passing the --dependency parameter. This option can be specified multiple times (once per dependency), and each time the format is repository::dependency. The dependency may have versions specifications, and the repository is optional (defaults to 'pypi').

Another way is to specify the dependencies in a text file, one dependency per line, with each line having the format previously described for the --dependency parameter. This file is then indicated to fades through the --requirement parameter. This option can be specified multiple times.

In case of multiple definitions of the same dependency, command line overrides everything else, and requirements file overrides what is specified in the source code.

Finally, you can include package names in the script docstring, after a line where "fades" is written, until the end of the docstring; for example:

"""Script to do stuff.

It's a very important script.

We need some dependencies to run ok, installed by fades:
    request
    otherpackage
"""

fades supports installing the required dependencies from multiples repositories: besides PyPI, you can specify URLs that can point to projects from GitHub, Launchpad, etc. (basically, everything that is supported by pip itself).

When a dependency is specified, fades deduces the proper repository. For example, in the following examples fades will install requests from the latest revision from PyPI in the first case, and in the second case the latest revision from the project itself from GitHub:

-d requests
-d git+https://github.com/kennethreitz/requests.git#egg=requests

If you prefer, you can be explicit about which kind of repository fades should use, prefixing the dependency with the special token double colon (::):

-d pypi::requests
-d vcs::git+https://github.com/kennethreitz/requests.git#egg=requests

There are two basic repositories: pypi which will make fades to install the desired dependency from PyPI, and vcs, which will make fades to treat the dependency as a URL for a version control system site. In the first case, for PyPI, a full range of version comparators can be specified, as usual. For vcs repositories, though, the comparison is always exact: if the very same dependency is specified, a virtual environment is reused, otherwise a new one will be created and populated.

In both cases (specifying the repository explicitly or implicitly) there is no difference if the dependency is specified in the command line, in a requirements.txt file, in the script's docstring, etc. In the case of marking the import directly in the script, it slightly different.

When marking the import it normally happens that the package itself to be installed has the name of the imported module, and because of that it can only be found in PyPI. So, in the following cases the pypi repository is not only deduced, but unavoidable:

import requests  # fades
from foo import bar  # fades
import requests  # fades <= 3

But if the package is specified (normally needed because it's different than the module name), or if a version control system URL is specified, the same possibilities stated above are available: let fades to deduce the proper repository or mark it explicitly:

import bs4  # fades beautifulsoup
import bs4  # fades pypi::beautifulsoup
import requests  # fades git+https://github.com/kennethreitz/requests.git#egg=requests
import requests  # fades vcs::git+https://github.com/kennethreitz/requests.git#egg=requests

One last detail about the vcs repository: the format to write the URLs is the same (as it's passed without modifications) than what pip itself supports (see pip docs for more details).

Furthermore, you can install from local projects. It's just fine to use a dependency that starts with file:. E.g. (please note the triple slash, because we're mixing the protocol indication with the path):

fades -d file:///home/crazyuser/myproject/allstars/

You can influence several details of all the virtual environment related process.

The most important detail is which version of Python will be used in the virtual environment. Of course, the corresponding version of Python needs to be installed in your system, but you can control exactly which one to use.

No matter which way you're executing the script (see above), you can pass a -p or --python argument, indicating the Python version to be used just with the number (3.9), the whole name (python3.9) or the whole path (/usr/bin/python3.9).

Other detail is the verbosity of fades when telling what is doing. By default, fades only will use stderr to tell if a virtual environment is being created, and to let the user know that is doing an operation that requires an active network connection (e.g. installing a new dependency).

If you call fades with -v or --verbose, it will send all internal debugging lines to stderr, which may be very useful if any problem arises. On the other hand if you pass the -q or --quiet parameter, fades will not show anything (unless it has a real problem), so the original script stderr is not polluted at all.

If you want to use IPython shell you need to call fades with -i or --ipython option. This option will add IPython as a dependency to fades and it will launch this shell instead of the python one.

You can also use --system-site-packages to create a venv with access to the system libs.

Finally, no matter how the virtual environment was created, you can always get the base directory of the virtual environment in your system using the --where (or its alias --get-venv-dir) option.

The -x/--exec parameter allows you to execute any program (not just a Python one) in the context of the virtual environment.

By default the mandatory given argument is considered the executable name, relative to the environment's bin directory, so this is specially useful to execute installed scripts/program by the declared dependencies. E.g.:

fades -d flake8 -x flake8 my_script_to_be_verified_by_flake8.py

Take in consideration that you can pass an absolute path and it will be respected (but not a relative path, as it will depend of the virtual environment location).

For example, if you want to run a shell script that in turn runs a Python program that needs to be executed in the context of the virtual environment, you can do the following:

fades -r requirements.txt --exec /var/lib/foobar/special.sh

Finally, if the intended code to run is prepared to be executed as a module (what you would normally run as python3 -m some_module), you can use the same parameter with fades to run that module inside the virtual environment:

fades -r requirements.txt -m some_module

When you tell fades to create a virtual environment using one dependency and don't specify a version, it will install the latest one from PyPI.

For example, you do fades -d foobar and it installs foobar in version 7. At some point, there is a new version of foobar in PyPI, version 8, but if do fades -d foobar it will just reuse previously created virtual environment, with version 7, not downloading the new version and creating a new virtual environment with it!

You can tell fades to do otherwise, just do:

fades -d foobar --check-updates

...and fades will search updates for the package on PyPI, and as it will found version 8, will create a new virtual environment using the latest version. You can also use the -U option as an alias for --check-updates:

fades -d foobar -U

From this moment on, if you request fades -d foobar it will bring the virtual environment with the new version. If you want to get a virtual environment with not-the-latest version for any dependency, just specify the proper versions.

You can even use the --check-updates parameter when specifying the package version. Say you call fades -d foobar==7, fades will install version 7 no matter which one is the latest. But if you do:

fades -d foobar==7 --check-updates

...it will still use version 7, but will inform you that a new version is available!

One nice benefit of fades is that every time dependencies change in your project, you actually get to use a new virtual environment automatically.

If you don't pin the dependencies in your requirements file, this has another nice side effect: everytime you use them in a new environment (or if you have --check-updates set) you will get latest versions, effectively avoiding the trap of sticking in old versions forever.

However, this has a bad side. If it happens that a dependency of your project released a revision between the moment you run the tests and the moment your project is deployed to the server, it may happen that you actually put in production an untested combination. Furthermore, it may happen that even if you do pin your dependencies, the dependencies of those dependencies may not be pinned, and you get into the same situation.

For example, you may have the requests == 2.19.1 dependency, but requests declares its own dependencies, for example chardet >= 3.0.2, and when running tests locally you may get chardet in version 3.0.3, but nothing guarantees you that when deploying your project to a server (effectively building everything from scratch) you will not get a newer version of chardet, which may be totally fine but in fact it's something that you did NOT test locally.

Here is where fades comes to the rescue with the --freeze option. If this parameter is given, fades will operate exactly as it normally would, but also will dump the result of pip freeze into the specified file.

So to continue with the example above, you could run your tests like:

fades -d "requests == 2.19.1" --freeze=reqs-frozen.txt -x python3 -m unittest

...which will leave you reqs-frozen.txt with a content similar to:

certifi==2018.4.16
chardet==3.0.4
pip==18.0
requests==2.19.1
...

And then you could use that file for deployment, which has all packages pinned, so you will get exactly what you was expecting.

For particular use cases you can send specifics arguments to the venv module, pip and python itself, using the --venv-options, --pip-options and --python-options modifiers respectively. You have to use that argument for each argument sent.

Examples:

fades -d requests --venv-options="--symlinks"

fades -d requests --pip-options="--index-url='http://example.com'"

fades --python-options=-B foo.py

You can also configure fades using .ini config files. fades will search config files in /etc/fades/fades.ini, the path indicated by xdg for your system (for example ~/config/fades/fades.ini) and .fades.ini.

So you can have different settings at system, user and project level.

With fades installed you can get your config dir running:

python -c "from fades.helpers import get_confdir; print(get_confdir())"

The config files are in .ini format. (configparser) and fades will search for a [fades] section.

You have to use the same configurations that in the CLI. The only difference is with the config options with a dash, it has to be replaced with a underscore.:

[fades]
ipython=true
verbose=true
python=python3
check_updates=true
dependency=requests;django>=1.8  # separated by semicolon

There is a little difference in how fades handle these settings: "dependency", "pip-options" and "venv-options". In these cases you have to use a semicolon separated list.

The most important thing is that these options will be merged. So if you configure in /etc/fades/fades.ini "dependency=requests" you will have requests in all the virtual environments created by fades.

When using fades virtual environments are something you should not have to think about. fades will do the right thing and create a new virtual environment that matches the required dependencies. There are cases however when you'll want to do some clean up to remove unnecessary virtual environments from disk.

By running fades with the --rm argument, fades will remove the virtual environment matching the provided UUID if such environment exists (one easy way to find out the environment's UUID is calling fades with the --where option).

Another way to clean up the cache is to remove all venvs that haven't been used for some time. In order to do this you need to call fades with --clean-unused-venvs. When fades it's called with this option, it runs in mantain mode, this means that fades will exit after finished this task.

All virtual environments that haven't been used for more days than the value indicated in param will be removed.

It is recommended to have some automatically way of run this option; ie, add a cron task that perform this command:

fades --clean-unused-venvs=42

Execute foo.py under fades, passing the --bar parameter to the child program, in a virtual environment with the dependencies indicated in the source code:

fades foo.py --bar

Execute foo.py under fades, showing all the fades messages (verbose mode):

fades -v foo.py

Execute foo.py under fades (passing the --bar parameter to it), in a virtual environment with the dependencies indicated in the source code and also dependency1 and dependency2 (any version > 3.2):

fades -d dependency1 -d "dependency2>3.2" foo.py --bar

Execute the Python interactive interpreter in a virtual environment with dependency1 installed:

fades -d dependency1

Execute the Python interactive interpreter in a virtual environment after installing there all dependencies taken from the requirements.txt file:

fades -r requirements.txt

Execute the Python interactive interpreter in a virtual environment after installing there all dependencies taken from files requirements.txt and requirements_devel.txt:

fades -r requirements.txt -r requirements_devel.txt

Use the django-admin.py script to start a new project named foo, without having to have django previously installed:

fades -d django -x django-admin.py startproject foo

Remove a virtual environment matching the given uuid from disk and cache index:

fades --rm 89a2bf83-c280-4918-a78d-c35506efd69d

Download the script from the given pastebin and executes it (previously building a virtual environment for the dependencies indicated in that pastebin, of course):

fades http://linkode.org/#4QI4TrPlGf1gK2V7jPBC47

Run all the tests in a project (running pytest directly as a module, for better behaviour) and at the same time freeze dependencies for later deployment:

fades -r requirements.txt --freeze -m pytest -v

Including fades in project helper scripts makes it easy to stop worrying about the virtual environment activation/deactivation when working in that project, and also solves the problem of needing to update/change/fix an already created virtual environment if the dependencies change.

This is an example of how a script to run your project may look like:

#!/bin/sh
if (command -v fades > /dev/null)
then
    # fades FTW!
    fades -r requirements.txt bin/start
else
    echo 2
    # hope you are in the correct virtual environment
    python3 bin/start
fi

To run the tests, it's super handy to have a script that also takes care of the development dependencies:

#!/bin/sh
fades -r requirements.txt -r reqs-dev.txt -x python -m pytest -s "$@"

The virtual environments created by fades depend on the Python version used to create them, considering its major and minor version.

This means that if run fades with a Python version and then run it again with a different Python version, it may need to create a new virtual environment.

Let's see some examples. Let's say you run fades with python, which is a symlink in your /usr/bin/ to python3.6 (running it directly by hand or because fades is installed to use that Python version).

If you have Python 3.6.2 installed in your system, and it's upgraded to Python 3.6.3, fades will keep reusing the already created virtual environments, as only the micro version changed, not minor or major.

But if Python 3.7 is installed in your system, and the default python is pointed to this new one, fades will start creating all the virtual environments again, with this new version.

This is a good thing, because you want that the dependencies installed with one specific Python in the virtual environment are kept being used by the same Python version.

However, if you want to avoid this behaviour, be sure to always call fades with the specific Python version (/usr/bin/python3.6 or python3.6, for example), so it won't matter if a new version is available in the system.

Several instructions to install fades in different platforms.

In some systems you can install fades directly, no needing to install previously any dependency.

If you are in debian unstable or testing, just do:

sudo apt-get install fades

For Arch Linux, you can install it from the AUR using any AUR helper, e.g. with pikaur:

pikaur -S fades

In systems with Snaps:

snap install fades --classic

(why --classic? Because it's the only way that fades could, from inside the snap, access the rest of the system in case you want to use a different Python version, or a dependency that needs compilation, etc).

For Mac OS X (and Homebrew):

brew install fades

Else, keep reading to know how to install the dependencies first, and fades in your system next.

Besides needing Python 3.6 or greater, fades depends on the python-xdg package. This package should be installed on any GNU/Linux OS wiht a freedesktop.org GUI. However it is an optional dependency.

You can install it in Ubuntu/Debian with:

apt-get install python3-xdg

And on Arch Linux with:

pacman -S python-xdg

If you are NOT in debian unstable or testing (if you are, see above for better instructions), you can use this .deb.

Download it and install doing:

sudo dpkg -i fades_*.deb
pip3 install fades

Finally you can always get the multiplatform tarball and install it in the old fashion way:

wget http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1.orig.tar.gz
tar -xf fades_*.tar.gz
cd fades-*
sudo ./setup.py install

Yes! Branch the project and use the executable:

git clone https://github.com/PyAr/fades.git
cd fades
bin/fades your_script.py

Windows is a platform supported by fades.

However, we don't have a proper Windows installer (a .exe or .msi), but you can install it using pip, or from the tarball, or try it directly from the project. All these options are properly described above.

We do want to have a Windows installer. If you can help us in this regard, please contact us. Also we would want a Travis running in Windows so that GitHub runs all the tests in this platform too before landing any code. Thanks!

You can ask any question or send any recommendation or request to the mailing list.

Come chat with us on IRC. The #fades channel is located at the Freenode network.

Also, you can open an issue here (please do if you find any problem!).

Thanks in advance for your time.

Quick guide to get you up and running in fades development.

Clone the project:

git clone [email protected]:PyAr/fades.git

fades manages it's own dependencies, so there is nothing extra you need to install.

To try it, just do:

bin/fades -V

When starting development, at all times, and specially before wrapping up a new branch, you need to be sure that all tests pass ok.

This is very simple, actually, just run:

./test

That will not only check test cases, but also that the code complies with aesthetic recommendations, and that the README document has a proper format.

If you want to run one particular test, just specify it. Example:

./test tests.test_main:DepsMergingTestCase.test_two_different

Just pick an issue from the list.

Develop, assure ./test is happy, commit, push, create a pull request, etc.

Please, if you aim for creating a Pull Request with new code (functionality or fixes), include tests for your changes.

Thanks! Enjoy.

fades's People

Contributors

andresdelfino avatar arielrossanigo avatar c0x6a avatar chenrui333 avatar dependabot[bot] avatar diegoduncan21 avatar dlitvakb avatar dmascialino avatar eduzen avatar evandandrea avatar facundobatista avatar facundofc avatar filipeximenes avatar gerasdf avatar gilgamezh avatar humitos avatar inakimalerba avatar jairot avatar jcarizza avatar jmansilla avatar juancarlospaco avatar luciotorre avatar malderete avatar matuu avatar r-ushil avatar ricardokirkner avatar vmalloc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

fades's Issues

Anyway to catch KeyboardInterrupt ?

Hi,

I tried to catch keyboardInterrupt from my script but fades keep raising the exception, is there anyway to catch it or avoid seeing fades stack trace ? Of course i can redirect stderr, but maybe i'm missing the way to do it within python.

Thank you for fades, it's being really helpful!

Mejorar parseo de get_version

Rompe por ejemplo cuando pip tira algún mensaje extra.

*** fades ***  2015-04-30 17:21:08,885  fades.envbuilder   DEBUG    Installing dependencies for repo 'pypi': requested=[Requirement.parse('requests'),
 Requirement.parse('django')]
*** fades ***  2015-04-30 17:21:08,885  fades.pipmanager   INFO     Installing dependency: requests
*** fades ***  2015-04-30 17:21:08,886  fades.exec         DEBUG    Executing external command: ['/home/gilgamezh/.local/share/fades/ff956136-de8e-4f3
c-85e8-df7044260ee9/bin/pip', 'install', 'requests']
*** fades ***  2015-04-30 17:21:09,365  fades.exec         DEBUG    :: You are using pip version 6.0.8, however version 6.1.1 is available.
*** fades ***  2015-04-30 17:21:09,365  fades.exec         DEBUG    :: You should consider upgrading via the 'pip install --upgrade pip' command.
*** fades ***  2015-04-30 17:21:09,371  fades.exec         DEBUG    :: Collecting requests
*** fades ***  2015-04-30 17:21:09,539  fades.exec         DEBUG    ::   Using cached requests-2.6.2-py2.py3-none-any.whl
*** fades ***  2015-04-30 17:21:09,578  fades.exec         DEBUG    :: Installing collected packages: requests
*** fades ***  2015-04-30 17:21:10,031  fades.exec         DEBUG    :: 
*** fades ***  2015-04-30 17:21:10,129  fades.exec         DEBUG    :: Successfully installed requests-2.6.2
*** fades ***  2015-04-30 17:21:10,208  fades.pipmanager   DEBUG    getting installed version for requests
Traceback (most recent call last):
  File "bin/fades", line 45, in <module>
    main.go(version, sys.argv)
  File "/home/gilgamezh/code/fades/fades/main.py", line 106, in go
    venv_data, installed = envbuilder.create_venv(requested_deps)
  File "/home/gilgamezh/code/fades/fades/envbuilder.py", line 100, in create_venv
    installed[repo][project] = mgr.get_version(project)
  File "/home/gilgamezh/code/fades/fades/pipmanager.py", line 64, in get_version
    version = p.stdout.readlines()[2].decode('utf-8').strip().split()[1]
IndexError: list index out of range
 ✘  ~/code/fades   interactive  /home/gilgamezh/.local/share/fades/ff956136-de8e-4f3c-85e8-df7044260ee9/bin/pip show requests
You are using pip version 6.0.8, however version 6.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

---
Name: requests
Version: 2.6.2

Ignore commented lines

Fades is correctly logging a warning but I think that it has to be ignored.

*** fades ***  2015-02-23 11:21:08,688  fades.parsing      WARNING  Not understood import info: ['#from', 'bs4', 'import', 'BeautifulSoup']

/dev/log don't exist in some distros.

I just tried the branch xdg_optional and I got this error:

humitos@manuelkaufmann:~$ ./fades/bin/fades --verbose src/check_python_tutorial.py 
Traceback (most recent call last):
  File "/usr/lib/python3.4/logging/handlers.py", line 820, in _connect_unixsocket
    self.socket.connect(address)
FileNotFoundError: [Errno 2] No such file or directory

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./fades/bin/fades", line 45, in <module>
    main.go(version, sys.argv)
  File "/home/humitos/fades/fades/main.py", line 97, in go
    l = logger.set_up(log_level)
  File "/home/humitos/fades/fades/logger.py", line 28, in set_up
    handler = logging.handlers.SysLogHandler(address='/dev/log')
  File "/usr/lib/python3.4/logging/handlers.py", line 803, in __init__
    self._connect_unixsocket(address)
  File "/usr/lib/python3.4/logging/handlers.py", line 831, in _connect_unixsocket
    self.socket.connect(address)
FileNotFoundError: [Errno 2] No such file or directory
humitos@manuelkaufmann:~$

Original reporte by @humitos on #20

Verbose mode

Implement -v/--verbose to show all internal messages to stderr (actually, put StreamHandler in DEBUG level)

Updating xattrs loses information saved initially

See that save_xattr() stores

xattr['deps'] = deps
xattr['env_path'] = env_path
xattr['env_bin_path'] = env_bin_path
xattr['pip_installed'] = pip_installed

but then update_xattr() only writes

xattr['deps'] = deps

BTW, these xattr management functions should be methods of a class...

Stop using xattr to store venvs metadata and centralize it

some ideas from @rbtcollins

http://linkode.org/ujwxKV00Qyal1haoQRzkg5

<facubatista> so, fades and xattr :)
<lifeless> righto
<lifeless> so, from my reading of the code
<facubatista> we're using the xattrs to store some metadata: whwere is the venv located, and which dependencies we installed
<lifeless> righto
<facubatista> so next time script is executed we use the same venv, and we quickly decide if there is a new dependency, or any old one needs to be updated/removed
<lifeless> so what about having the venv path be the hash of key inputs (like python version)
<facubatista> so, if not in xattr, where we could store this metadata?
<facubatista> so, like hash([(dependency, revno) for requested_dependencies]) ?
<lifeless> e.g., say you did path = basedir + hash.sha256(''.join([py_ver] + [str(dep) for dep in deps]).hexdigest
<lifeless> yes
<lifeless> would be a different tradeoff in venv setup times
<facubatista> the problem there is that revisions are not that constant, see this example:
<facubatista> you put in a script that you want some module with revno >= 2.0
<facubatista> so, we tell pip to install that dependency, with that revno limitation
<facubatista> pip installs the module, with revno 2.7
<facubatista> we store this 2.7
<facubatista> because that is what is currently there, and in the future we need to react to what was installed
<lifeless> pip freeze will tell you whats installed
<facubatista> so, that changing of revision complicates the hashing
<lifeless> yes
<facubatista> yes, but that involves an external execution... it's slower
<lifeless> I was thinking you'd hash the requirements, not the installed bits
<facubatista> so, question
<lifeless> we can probably get dstufft to export the freeze interface as a library call
<facubatista> if you later edit the script and add a new dependency, it will be a new venv, and we'd need to install all everything again, right?
<facubatista> (don't really think this may happen too frequently)
<lifeless> with my quick sketch, yes
<facubatista> otoh, the benefit of this approach is that if you have two scripts that wants the same dependency, they can reuse the venv
<lifeless> yeah
<lifeless> I don't know if thats really that common or not
<lifeless> but, vim wouldn't break it anymore :)
<facubatista> mmm... probably myself have several scripts that only uses beautifulsoup
<facubatista> or only uses requests
<facubatista> we'd need to put the version too in the hash
<facubatista> so a venv with mod X v1 and other with same mod X but v2 are effectively two different venvs
<lifeless> why?
<lifeless> sorry, I mean
<lifeless> which version
<lifeless> the requirement
<lifeless> or the installed?
<facubatista> the installed one
<facubatista> mmm... we'd need to use the required one
<facubatista> otherwise we won't find the venv later
<lifeless> yeah
<facubatista> any change to requirements will imply a whole venv again
<facubatista> (I don't think that changing requirements will be *that* common)
<lifeless> I can imagine a whole spin-off project
<facubatista> ?
<lifeless> to manage a cache of venvs, introspecting them and renaming rather than rebuilding or things like that
<lifeless> perhapsa double-indirection scheme
<lifeless> $crazy :)(
<facubatista> no that crazy
<facubatista> even, say
<facubatista> with what we talked so far, if we do
<facubatista> first, dependency is mod X (no version), and it installs v5
<facubatista> second, later, you change it to be with version >=3
<facubatista> with the idea discussed, we'll create a new venv
<facubatista> but, what if...
<facubatista> we have somewhere (a fades DB) where we could ask for those requirements, and it will say "I have this venv, use it"
<facubatista> other script could say, for "mod X == 5", and it will still use the same one
<facubatista> so, no more hashes to the name... the first time we just create a new venv, install what is requested, and then store somewhere "this venv has this stuff installed"
<lifeless> sounds nice
<facubatista> next time, we go to the DB and check for a venv that satisfies the requirements... if yes, use it; if not, create a new one
<facubatista> no matter if it was created for the same script, or not
<facubatista> mmm... I like this, eh!  I'm not even struggling with the feeling of "oh, we need to dump all that code" :p
<lifeless> cool, write one to throw away!
<facubatista> indeed
<facubatista> where you wanted to use fades that xattr was a problem?
<lifeless> couple places; firstly I've a windows machine now (mainly for games, but I Python on it when I'm checking upstream patches to make sure I don't break the windows buildbots)
<lifeless> secondly, I use vim :)
<lifeless> I've also had some weird experiences with xattr here and there
<lifeless> I don't really trust them, because they get lost so easily (e.g. by vim) - the standardish atomic idioms don't work
<lifeless> its not that I think the kernel is buggy or anything
<lifeless> just that their semantics don't match the semantics most unix programs use around file management
<facubatista> yes, vim has a problem with xattr (*I* opened them a bug about this :p ), there's a hack, but better if we don't use it
<facubatista> the xattrs semantics *suck*
<facubatista> they're even complicated to use in Python! and that's the main reason I didn't offer a patch for vim :p
<facubatista> and thanks for all the ideas!!

Basic functionality

Code the basic (but still inefficient) behaviour, that is:

  • create always a new fresh virtualenv
  • install the dependencies
  • execute the child process inside the virtualenv

Quiet mode

Implement -q/--quiet to never show anything in stderr (actually, it will put StreamHandler in ERROR level).

Store metadata for venv reusing

Store in the file itself the needed information to be more efficient in the next runs:

  • the venv id, to reuse it
  • the installed dependencies, to know if current-execution-dependencies should be removed, updated, or installed

Package names

How does fades the name of the package that it should install?

I think it's taking the name from the import line but sometimes this is not correct: for example: beautifulsoup.

I have this in my .py file:

from bs4 import BeautifulSoup  # fades.pypi

and fades tries to install the package named bs4 which is incorrect because the proper name is: beautifulsoup4

I suggest to use something like this:

from bs4 import BeautifulSoup  # fades.pypi beautifulsoup4

Supress instalers stdout when not in -v mode

Being in quiet mode, or just normal one, avoid installers/pip/etc prints to go to user's stdout.

I'm talking about avoiding these, for example:

Downloading/unpacking jinja2 (from futen)
Downloading Jinja2-2.7.3.tar.gz (378kB): 378kB downloaded
Running setup.py (path:/home/facundo/.local/share/fades/5bfd80e1-6b6e-46aa-84d5-1444b54a2c50/build/jinja2/setup.py) egg_info for package jinja2
i686-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-Bsymbolic-functions -Wl,-z,relro -g -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 build/temp.linux-i686-3.4/src/_fastmath.o -lgmp -o build/lib.linux-i686-3.4/Crypto/PublicKey/_fastmath.cpython-34m.so
building 'Crypto.Hash._MD2' extension

Incorrect data saved when version condition is >=

When vesion condition is >= alwas saves the given version instead the installed.

Example:

 ~/code/fades  ± master ●  cat sarasa.py 
import requests # fades.pypi
import django # fades.pypi >= 1.4

print("hola mundo")
bin/fades -v sarasa.py
*** fades ***  2014-12-30 15:56:32,452  fades              DEBUG    Running Python sys.version_info(major=3, minor=4, micro=2, releaselevel='final', serial=0) on 'linux'
*** fades ***  2014-12-30 15:56:32,452  fades              DEBUG    Starting fades v. 0.1
*** fades ***  2014-12-30 15:56:32,453  fades.helpers      DEBUG    Getting fades info from xattr for sarasa.py
*** fades ***  2014-12-30 15:56:32,453  fades.helpers      DEBUG    Xattr obtained from sarasa.py: {'env_path': '/home/ndemarchi/.local/share/fades/9d2db669-9ff8-426b-817b-d267
f3fa4ae5', 'pip_installed': True, 'env_bin_path': '/home/ndemarchi/.local/share/fades/9d2db669-9ff8-426b-817b-d267f3fa4ae5/bin', 'deps': {<Repo.pypi: 1>: {'requests': {'version
': '2.5.1'}, 'flask': {'version': '>=2.1'}}}}
*** fades ***  2014-12-30 15:56:32,454  fades              DEBUG    compare installed versión with last release not implemented yet see #XXX
*** fades ***  2014-12-30 15:56:32,454  fades.pipmanager   DEBUG    getting installed versión for requests
*** fades ***  2014-12-30 15:56:32,754  fades.pipmanager   DEBUG    Installed versión of requests is: 2.5.1
*** fades ***  2014-12-30 15:56:32,755  fades.pipmanager   INFO     Installing dependency: django>= 1.4
*** fades ***  2014-12-30 15:56:33,017  fades.helpers      DEBUG    Downloading/unpacking django>=1.4
*** fades ***  2014-12-30 15:57:47,728  fades.helpers      DEBUG    Installing collected packages: django
*** fades ***  2014-12-30 15:57:51,130  fades.helpers      DEBUG    Successfully installed django
*** fades ***  2014-12-30 15:57:51,130  fades.helpers      DEBUG    Cleaning up...
*** fades ***  2014-12-30 15:57:51,487  fades              DEBUG    updating xattr
*** fades ***  2014-12-30 15:57:51,487  fades.helpers      INFO     Updating xattr fades info for sarasa.py
*** fades ***  2014-12-30 15:57:51,487  fades.helpers      DEBUG    Updating xattr with: {<Repo.pypi: 1>: {'requests': {'version': '2.5.1'}, 'django': {'version': '>= 1.4'}}}
*** fades ***  2014-12-30 15:57:51,487  fades.helpers      DEBUG    Getting fades info from xattr for sarasa.py
*** fades ***  2014-12-30 15:57:51,488  fades.helpers      DEBUG    Xattr obtained from sarasa.py: {'env_path': '/home/ndemarchi/.local/share/fades/9d2db669-9ff8-426b-817b-d267
f3fa4ae5', 'pip_installed': True, 'env_bin_path': '/home/ndemarchi/.local/share/fades/9d2db669-9ff8-426b-817b-d267f3fa4ae5/bin', 'deps': {<Repo.pypi: 1>: {'requests': {'version
': '2.5.1'}, 'flask': {'version': '>=2.1'}}}}
*** fades ***  2014-12-30 15:57:51,488  fades              DEBUG    Calling the child Python program 'sarasa.py' with options []
hola mundo
 ~/code/fades  ± master ●  /home/ndemarchi/.local/share/fades/9d2db669-9ff8-426b-817b-d267f3fa4ae5/bin/python -c "import django; print(django.get_version())"
1.7.1

fades return with error if is called without a file

 ~/code/fades  ± master   bin/fades 
Traceback (most recent call last):
  File "bin/fades", line 45, in <module>
    main.go(version, sys.argv)
  File "/home/ndemarchi/code/fades/fades/main.py", line 105, in go
    requested_deps = parsing.parse_file(child_program)
  File "/home/ndemarchi/code/fades/fades/parsing.py", line 81, in parse_file
    with open(filepath, 'rt', encoding='utf8') as fh:
FileNotFoundError: [Errno 2] No such file or directory: ''

It should show a usage example.

🚀 Bonus: Show the same usage help with -h and fades versión with -V

Support requirements in docstring

It would be nice if, in addition to comments on imports, you could support a requirements.txt-like thing in the script's docstring, like this:

#!/usr/bin/python

"""
This script to foobar the bazbats has the following requirements:

foo >=1.0
bar <2
bat
baz
"""

Syntax, parsing, etc are left as exercise for the authors ;-)

Duplicated Venvs

With this .py file:

import requests # fades.pypi == 2.5
import django # fades.pypi

print("Hola Fades :D ")

I'm getting a new venv on each run.

venv.idx:

{"metadata": {"pip_installed": true, "env_path": "/home/ndemarchi/.local/share/fades/1cc74821-945a-4b55-96c5-c106d372291f", "env_bin_path": "/home/ndemarchi/.local/share/fades/1cc74821-945a-4b55-96c5-c106d372291f/bin"}, "timestamp": 1424784668, "installed": {"pypi": {"django": "1.7.4", "requests": "2.5.0"}}}
{"installed": {"pypi": {"django": "1.7.4", "requests": "2.5.0"}}, "metadata": {"env_bin_path": "/home/ndemarchi/.local/share/fades/790bb3e3-5afd-443f-a6ed-9707b59c8bd4/bin", "pip_installed": true, "env_path": "/home/ndemarchi/.local/share/fades/790bb3e3-5afd-443f-a6ed-9707b59c8bd4"}, "timestamp": 1424784827}
{"installed": {"pypi": {"requests": "2.5.0", "django": "1.7.4"}}, "timestamp": 1424785203, "metadata": {"env_bin_path": "/home/ndemarchi/.local/share/fades/70f2fef6-7115-4501-9bc0-3be6c802781f/bin", "env_path": "/home/ndemarchi/.local/share/fades/70f2fef6-7115-4501-9bc0-3be6c802781f", "pip_installed": true}}

Would it be happening because of the order of the dicts?

pip errors are not captured

pip errors are not captured and when pip fails fades is saving the dependency info as if it had been properly installed 👎

Support an interactive form

So, if you do:

$ fades "requests == 2.3"

it will open a python3 interpreter in a virtualenv that has that requirement

Assure proper documentation

When the rest is ready, assure all functionalities are properly described and explained in the README.md and in the man page.

Add xdg as a dependency to the README file

 /tmp  pyvenv testfade
 /tmp  source testfade/bin/activate
(testfade)  /tmp  pip install git+https://github.com/PyAr/fades.git
Downloading/unpacking git+https://github.com/PyAr/fades.git
  Cloning https://github.com/PyAr/fades.git to ./pip-yupq5skl-build
  Running setup.py (path:/tmp/pip-yupq5skl-build/setup.py) egg_info for package from git+https://github.com/PyAr/fades.git

    package init file 'fades/__init__.py' not found (or not a regular file)
Installing collected packages: fades
  Running setup.py install for fades
    package init file 'fades/__init__.py' not found (or not a regular file)
    changing mode of build/scripts-3.4/fades from 644 to 755

    changing mode of /tmp/testfade/bin/fades to 755
Successfully installed fades
Cleaning up...
(testfade)  /tmp  fades 
Traceback (most recent call last):
  File "/tmp/testfade/bin/fades", line 44, in <module>
    from fades import main
  File "/tmp/testfade/share/fades/fades/main.py", line 26, in <module>
    from fades.pipmanager import PipManager
  File "/tmp/testfade/share/fades/fades/pipmanager.py", line 27, in <module>
    from xdg import BaseDirectory
ImportError: No module named 'xdg'

Add execution examples

Put in the usage, in the readme, and in the man several examples of fades execution, like:

    fades -v foo.py 
         executes foo.py showing all debug messages

etc...

Running fades without freedesktop.org system

I'm running fades in a system without a GUI and I'm getting this error:

$ ./fades/bin/fades src/check_python_tutorial.py --email
Traceback (most recent call last):
  File "./fades/bin/fades", line 44, in <module>
    from fades import main
  File "/home/humitos/fades/fades/main.py", line 23, in <module>
    from fades.pipmanager import PipManager
  File "/home/humitos/fades/fades/pipmanager.py", line 22, in <module>
    from xdg import BaseDirectory
ImportError: No module named 'xdg'
$ 

Support requirements from a file

Like, with -r or --requirements

So, you can do (without commenting anything in foo.py):

    fades --requirements=req.txt foo.py

Or also to open in interactive mode with those dependencies:

    fades --interactive --requirements=req.txt 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.