Skip to content

Commit

Permalink
Merge 9a0a91c into c096c14
Browse files Browse the repository at this point in the history
  • Loading branch information
juancarlospaco committed Apr 24, 2018
2 parents c096c14 + 9a0a91c commit b36f232
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 4 deletions.
8 changes: 8 additions & 0 deletions README.rst
Expand Up @@ -371,6 +371,14 @@ Uses the ``django-admin.py`` script to start a new project named ``foo``, withou

Removes a virtualenv matching the given uuid from disk and cache index.

``fades --list-venvs``

List all virtualenvs, showing the information (UUID, timestamp, dependencies, interpreter, etc).

``fades --list-venvs ipython,prospector``

Optionally filter the list using FILTER, FILTER can be a word string or comma separated words.


What if Python is updated in my system?
---------------------------------------
Expand Down
47 changes: 45 additions & 2 deletions fades/helpers.py
Expand Up @@ -16,12 +16,15 @@

"""A collection of utilities for fades."""

import os
import sys

import json
import logging
import os
import re
import subprocess
import sys

from datetime import datetime
from urllib import request
from urllib.error import HTTPError

Expand All @@ -39,6 +42,24 @@
print(json.dumps(d))
"""

LIST_VENVS_TEMPLATE = """
Virtualenv UUID: {uid}
timestamp: {dat}
full path: {pat}
dependencies: {pac}
interpreter: {pyv}
options: {opt}
"""

# UUID Regex, for v1 to v5.
UUID_REGEX = re.compile((
'[a-f0-9]{8}-'
'[a-f0-9]{4}-'
'[1-5]' # Versions.
'[a-f0-9]{3}-'
'[89ab][a-f0-9]{3}-'
'[a-f0-9]{12}$'
), re.IGNORECASE)

# the url to query PyPI for project versions
BASE_PYPI_URL = 'https://pypi.python.org/pypi/{name}/json'
Expand Down Expand Up @@ -256,3 +277,25 @@ def check_pypi_exists(dependencies):
logger.error("%s doesn't exists in PyPI.", dependency)
return False
return True


def list_venvs(index_path, query=None, template=LIST_VENVS_TEMPLATE):
"""List all venvs from an index file path and print info to stdout."""
if os.path.isfile(index_path):
if query:
query = tuple(set(query.lower().strip().split(",")))
venv_info = ""
with open(index_path) as jotason:
for jotason_line in jotason:
if query and not any([qry in jotason_line.lower() for qry in query]):
continue
v_dct_get = json.loads(jotason_line).get
venv_info += template.format(
uid=UUID_REGEX.search(v_dct_get("metadata")["env_path"]).group(0),
pat=v_dct_get("metadata")["env_path"],
pac=v_dct_get("installed"),
pyv=v_dct_get("interpreter"),
opt=v_dct_get("options"),
dat=datetime.fromtimestamp(v_dct_get("timestamp")))
print(venv_info)
return venv_info
9 changes: 8 additions & 1 deletion fades/main.py
Expand Up @@ -125,13 +125,15 @@ def go():
help=("Extra options to be supplied to python. (this option can be "
"used multiple times)"))
parser.add_argument('--rm', dest='remove', metavar='UUID',
help=("Remove a virtualenv by UUID."))
help=("Remove a virtualenv by UUID (see --list-venvs)."))
parser.add_argument('--clean-unused-venvs', action='store',
help=("This option remove venvs that haven't been used for more than "
"CLEAN_UNUSED_VENVS days. Appart from that, will compact usage "
"stats file.\n"
"When this option is present, the cleaning takes place at the "
"beginning of the execution."))
parser.add_argument('-l', '--list-venvs', metavar='FILTER', nargs='?', const=' ',
help=("List all venvs. Optionally filter the list by FILTER."))
parser.add_argument('child_program', nargs='?', default=None)
parser.add_argument('child_options', nargs=argparse.REMAINDER)

Expand Down Expand Up @@ -161,6 +163,11 @@ def go():
logger.debug("Starting fades v. %s", fades.__version__)
logger.debug("Arguments: %s", args)

if args.list_venvs:
print(f"args.list_venvs:{args.list_venvs}.")
helpers.list_venvs(os.path.join(helpers.get_basedir(), 'venvs.idx'), args.list_venvs)
sys.exit(0)

# verify that the module is NOT being used from a virtualenv
if detect_inside_virtualenv(sys.prefix, getattr(sys, 'real_prefix', None),
getattr(sys, 'base_prefix', None)):
Expand Down
7 changes: 6 additions & 1 deletion man/fades.1
Expand Up @@ -20,6 +20,7 @@ fades - A system that automatically handles the virtualenvs in the cases normall
[\fB--pip-options\fR=\fIoptions\fR]
[\fB--python-options\fR=\fIoptions\fR]
[\fB--check-updates\fR]
[\fB--list-venvs\fR]
[\fB--clean-unused-venvs\fR=\fImax_days_to_keep\fR]
[child_program [child_options]]

Expand Down Expand Up @@ -104,14 +105,18 @@ Extra options to be supplied to pip. (this option can be used multiple times)
.BR --python-options=\fIPYTHON_OPTION\fR
Extra options to be supplied to python. (this option can be used multiple times)

.TP
.TP
.BR --check-updates
Will check for updates in PyPI to verify if there are new versions for the requested dependencies. If a new version is available for a dependency, it will use it (if the dependency was requested without version) or just inform which new version is available (if the dependency was requested with a specific version).

.TP
.BR --clean-unused-venvs=\fIMAX_DAYS_TO_KEEP\fR
Will remove all virtualenvs that haven't been used for more than MAX_DAYS_TO_KEEP days.

.TP
.BR --list-venvs\fIFILTER\fR
List all virtualenvs, showing information (UUID, timestamp, path, dependencies, interpreter, etc). Optionally filter the list using FILTER, FILTER can be a word string or comma separated words.

.SH EXAMPLES

.TP
Expand Down
56 changes: 56 additions & 0 deletions tests/examples/output.txt
@@ -0,0 +1,56 @@

Virtualenv UUID: d1cf2e61-4ac5-467d-9b65-1a3c0e913bf3
timestamp: 2011-06-16 00:00:00
full path: /home/juan/.fades/d1cf2e61-4ac5-467d-9b65-1a3c0e913bf3
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 032e6e2c-bc7e-4a7f-ad1d-66ecb9106884
timestamp: 2009-02-09 00:00:00
full path: /home/juan/.fades/032e6e2c-bc7e-4a7f-ad1d-66ecb9106884
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 53c8f169-b105-4ed1-a7fc-25792b18fc4c
timestamp: 2001-06-17 00:00:00
full path: /home/juan/.fades/53c8f169-b105-4ed1-a7fc-25792b18fc4c
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 8cc6666c-fb5e-474b-a8a7-217c9558d5e2
timestamp: 2017-08-05 00:00:00
full path: /home/juan/.fades/8cc6666c-fb5e-474b-a8a7-217c9558d5e2
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 7a4a4eb8-b0d2-4d88-a890-4830278e0eb9
timestamp: 2015-09-06 00:00:00
full path: /home/juan/.fades/7a4a4eb8-b0d2-4d88-a890-4830278e0eb9
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 7c10fa5f-037e-49b1-a9dd-3462c739e239
timestamp: 2007-01-15 00:00:00
full path: /home/juan/.fades/7c10fa5f-037e-49b1-a9dd-3462c739e239
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 37de0f88-3db7-434a-a692-048aa35fbfe7
timestamp: 2002-07-08 00:00:00
full path: /home/juan/.fades/37de0f88-3db7-434a-a692-048aa35fbfe7
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}

Virtualenv UUID: 000236c6-5ddb-4422-b210-93e09fab603f
timestamp: 2017-12-06 00:00:00
full path: /home/juan/.fades/000236c6-5ddb-4422-b210-93e09fab603f
dependencies: {}
interpreter: /usr/bin/python3
options: {'pyvenv_options': [], 'virtualenv_options': []}
8 changes: 8 additions & 0 deletions tests/examples/venvs.idx
@@ -0,0 +1,8 @@
{"timestamp": 1308193200, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/d1cf2e61-4ac5-467d-9b65-1a3c0e913bf3", "env_bin_path": "/home/juan/.fades/d1cf2e61-4ac5-467d-9b65-1a3c0e913bf3/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 1234144800, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/032e6e2c-bc7e-4a7f-ad1d-66ecb9106884", "env_bin_path": "/home/juan/.fades/032e6e2c-bc7e-4a7f-ad1d-66ecb9106884/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 992746800, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/53c8f169-b105-4ed1-a7fc-25792b18fc4c", "env_bin_path": "/home/juan/.fades/53c8f169-b105-4ed1-a7fc-25792b18fc4c/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 1501902000, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/8cc6666c-fb5e-474b-a8a7-217c9558d5e2", "env_bin_path": "/home/juan/.fades/8cc6666c-fb5e-474b-a8a7-217c9558d5e2/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 1441508400, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/7a4a4eb8-b0d2-4d88-a890-4830278e0eb9", "env_bin_path": "/home/juan/.fades/7a4a4eb8-b0d2-4d88-a890-4830278e0eb9/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 1168830000, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/7c10fa5f-037e-49b1-a9dd-3462c739e239", "env_bin_path": "/home/juan/.fades/7c10fa5f-037e-49b1-a9dd-3462c739e239/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 1026097200, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/37de0f88-3db7-434a-a692-048aa35fbfe7", "env_bin_path": "/home/juan/.fades/37de0f88-3db7-434a-a692-048aa35fbfe7/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
{"timestamp": 1512529200, "installed": {}, "metadata": {"env_path": "/home/juan/.fades/000236c6-5ddb-4422-b210-93e09fab603f", "env_bin_path": "/home/juan/.fades/000236c6-5ddb-4422-b210-93e09fab603f/bin", "pip_installed": true}, "interpreter": "/usr/bin/python3", "options": {"pyvenv_options": [], "virtualenv_options": []}}
42 changes: 42 additions & 0 deletions tests/test_files/autogenerate_random_venvsidx.py
@@ -0,0 +1,42 @@


"""Tiny tool to autogenerate random 'venvs.idx' content for testing purposes."""


import os
from random import randint
from uuid import uuid4
from shutil import which
import datetime
import json


def main():
"""Build a valid random-ish Fades venvs.idx content."""
y3ar, h0me = datetime.date.today().year, os.path.join(os.path.expanduser("~"), ".fades")
venvs_idx = ""
for _ in range(randint(2, 9)):
random_envpath = os.path.join(h0me, str(uuid4()))
random_timestamp = int(datetime.datetime(year=randint(2000, y3ar),
month=randint(1, 12),
day=randint(1, 28)).timestamp())
venvs_idx += json.dumps({
"timestamp": random_timestamp,
"installed": {},
"metadata": {
"env_path": random_envpath,
"env_bin_path": os.path.join(random_envpath, "bin"),
"pip_installed": True
},
"interpreter": which("python3") or "/usr/bin/python3.6",
"options": {
"pyvenv_options": [],
"virtualenv_options": []
}
}) + "\n"
print(venvs_idx.strip())
return venvs_idx.strip()


if __name__ in "__main__":
main()
23 changes: 23 additions & 0 deletions tests/test_helpers.py
Expand Up @@ -375,3 +375,26 @@ def test_redirect_response(self):
exists = helpers.check_pypi_exists(deps)
self.assertTrue(exists)
self.assertLoggedWarning("Got a (unexpected) HTTP_STATUS")


class ListVenvsTestCase(unittest.TestCase):

"""Utilities to list venvs."""

maxDiff, __slots__ = None, ()

@unittest.skipIf('TRAVIS' in os.environ or 'APPVEYOR' in os.environ,
"Travis/AppVeyor weird scaping travis-ci.org/PyAr/fades/jobs/366371756#L764")
def test_list_venvs(self):
venvs_idx = os.path.join(PATH_TO_EXAMPLES, 'venvs.idx')
venvs_info = os.path.join(PATH_TO_EXAMPLES, 'output.txt')
str_list_venv = helpers.list_venvs(venvs_idx)
self.assertIsInstance(str_list_venv, str)
with open(venvs_info) as venvs_info_file:
self.assertEqual(str_list_venv, venvs_info_file.read())

def test_index_path_empty(self):
self.assertEqual(helpers.list_venvs(""), None)

def test_index_path_not_found(self):
self.assertEqual(helpers.list_venvs("directory_does_not_exist"), None)

0 comments on commit b36f232

Please sign in to comment.