Skip to content

Commit

Permalink
Log fixes and Fedora availability
Browse files Browse the repository at this point in the history
  • Loading branch information
dvershinin committed Mar 17, 2022
1 parent 4fdb67f commit fa5294f
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 26 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

`pip-safe` is the *safe* and easy pip package manager for command-line apps from PyPi.

### Synopsys

pip-safe install lastversion
lastversion linux

Expand Down Expand Up @@ -29,18 +31,21 @@ Configure your `PATH` to execute stuff from `~/.local/bin` and `/usr/local/bin`.
Place `export PATH=$PATH:$HOME/.local/bin:/usr/local/bin` in your `~/.bashrc`
then run `source ~/.bashrc` to apply to current shell.

### CentOS/RHEL 7, 8
### CentOS/RHEL 7, 8 and Fedora Linux

sudo yum -y install https://extras.getpagespeed.com/release-latest.rpm
sudo yum -y install pip-safe

Using `pip-safe` command installs stuff using Python 3.
Using `pip-safe` command installs a program using Python 3, by default.

You can additionally `yum install pip2-safe` to have `pip2-safe`
(would be required for installing apps which support only Python 2).
If you require to run a legacy app using Python 2, run `yum install pip2-safe` to install Python 2
support. Then to install a Python 2 app, use `pip2-safe install <app>`. You can still use
`pip-safe` as usual, for Python 3 apps.

### Other systems

Please do not use these methods if packages for `pip-safe` are available!

#### Install `pip-safe` for current user

If you install `pip-safe` using this method, you can only install packages for current user,
Expand Down Expand Up @@ -127,7 +132,7 @@ It is that easy and I don't know why nobody did this before.

## Caveats

* Only pure Python apps will work absolutely reliably, because others might require *system* libraries
* Only pure Python apps will work absolutely reliably, because others might require *system* libraries,
and we can't decipher what are those
* Tested only with Python 3.6

Expand Down
2 changes: 1 addition & 1 deletion pip_safe/__about__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.0.9"
__version__ = "0.0.10"
65 changes: 47 additions & 18 deletions pip_safe/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import argparse
import json
import logging as log # for verbose output
import logging
import os
import shutil

Expand All @@ -11,6 +11,8 @@
from .__about__ import __version__
from .utils import symlink, make_sure_path_exists, ensure_file_is_absent, call_subprocess

log = logging.getLogger('pip-safe')


def confirm_smth(question):
reply = str(six.moves.input(question + ' (y/Nn): ')).lower().strip()
Expand Down Expand Up @@ -61,6 +63,8 @@ def get_venv_executable_names(name, system_wide=False):
log.debug("Checking what was installed to virtualenv's bin")
bin_names = []
venv_pip = get_venv_pip(name, system_wide)
if not venv_pip:
return []
# sanitize version specifier if user installs, e.g. lastversion==1.2.4
main_package_name = name.split('==')[0]
# if the passed name was a URL, we have to figure out the name of the "main"
Expand All @@ -82,25 +86,31 @@ def get_venv_executable_names(name, system_wide=False):

file_cmd = [venv_pip, 'show', '-f', main_package_name]
log.debug('Running {}'.format(file_cmd))
for line in call_subprocess(
file_cmd,
show_stdout=False,
raise_on_return_code=False,
):
line = line.strip()
if line.startswith('../../../bin/'):
basename = os.path.basename(line)
bin_names.append(basename)
try:
for line in call_subprocess(
file_cmd,
show_stdout=False,
raise_on_return_code=True,
):
line = line.strip()
if line.startswith('../../../bin/'):
basename = os.path.basename(line)
bin_names.append(basename)
except OSError:
return []
return bin_names


def get_current_version(name, system_wide=False):
venv_pip = get_venv_pip(name, system_wide=system_wide)
if venv_pip is None:
return 'damaged (no inner pip)'
p = call_subprocess([venv_pip, 'show', name],
show_stdout=False,
raise_on_return_code=False)
try:
p = call_subprocess([venv_pip, 'show', name],
show_stdout=False,
raise_on_return_code=False)
except FileNotFoundError:
return 'damaged (Python interpreter is not found)'
v = 'n/a'
package_not_found = False
for line in p:
Expand Down Expand Up @@ -149,6 +159,7 @@ def install_package(name, system_wide=False, upgrade=False):
# the env var is supposed to hide the "old version" warning emitted
# in the very first run
log.info('Ensuring latest pip in the virtualenv')
log.debug("PIP_DISABLE_PIP_VERSION_CHECK=1 " + " ".join(args))
call_subprocess(args, extra_env={'PIP_DISABLE_PIP_VERSION_CHECK': '1'})

log.debug("Running pip install in the virtualenv {}".format(name))
Expand All @@ -159,7 +170,12 @@ def install_package(name, system_wide=False, upgrade=False):
args.append('-U')
args.append(name)
args.append('--quiet')
call_subprocess(args)
try:
call_subprocess(args)
except OSError:
# clean up virtualenv on pip error
return remove_package(name, system_wide, confirmation_needed=False,
silent=True)

pkg_bin_names = get_venv_executable_names(name, system_wide)
for bin_name in pkg_bin_names:
Expand Down Expand Up @@ -231,7 +247,7 @@ def is_bin_in_path(system_wide=False):
return bin_dir in path_dirs


def remove_package(name, system_wide=False, confirmation_needed=True):
def remove_package(name, system_wide=False, confirmation_needed=True, silent=False):
venv_dir = get_venv_dir(name, system_wide)
if not os.path.exists(venv_dir):
log.warning('Looks like {} already does not exist. Nothing to do'.format(
Expand All @@ -252,7 +268,10 @@ def remove_package(name, system_wide=False, confirmation_needed=True):
log.debug('Going to remove: {}'.format(venv_dir))
if os.path.exists(venv_dir):
shutil.rmtree(venv_dir)
log.info('{} was removed.'.format(name))
if silent:
log.debug('Cleaned up {}'.format(name))
else:
log.info('{} was removed.'.format(name))


def main():
Expand All @@ -275,11 +294,21 @@ def main():
parser.set_defaults(verbose=False, verbose_more=False, system=False)
args = parser.parse_args()

# create console handler and set level to debug
ch = logging.StreamHandler()
# create formatter
fmt = '%(name)s - %(levelname)s - %(message)s' if args.verbose else '%(levelname)s: %(message)s'
formatter = logging.Formatter(fmt)
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
log.addHandler(ch)

if args.verbose:
log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
log.setLevel(logging.DEBUG)
log.info("Verbose output.")
else:
log.basicConfig(format="%(levelname)s: %(message)s", level=log.INFO)
log.setLevel(logging.INFO)

if args.command == 'install':
install_package(name=args.package,
Expand Down
5 changes: 3 additions & 2 deletions pip_safe/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ def ensure_file_is_absent(file_path):
def symlink(target, link_name, overwrite=False):
'''
Create a symbolic link named link_name pointing to target.
If link_name exists then FileExistsError is raised, unless overwrite=True.
When trying to overwrite a directory, IsADirectoryError is raised.
The whole point of this is being able to overwrite
Whereas default Python os.symlink will fail on existing file
See https://stackoverflow.com/questions/8299386/modifying-a-symlink-in-python
'''

if not overwrite:
Expand Down

0 comments on commit fa5294f

Please sign in to comment.