Skip to content
This repository has been archived by the owner on Jan 12, 2021. It is now read-only.

Commit

Permalink
Merge pull request #28 from dephell/try-command
Browse files Browse the repository at this point in the history
Try command
  • Loading branch information
orsinium committed Apr 18, 2019
2 parents d4286c3 + cde9081 commit 6362344
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 30 deletions.
4 changes: 3 additions & 1 deletion dephell/actions/_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ def get_package(req: str) -> Dependency:


def get_resolver(reqs: Iterable[str] = None) -> Resolver:
return PIPConverter(lock=False).loads_resolver('\n'.join(reqs))
root = PIPConverter(lock=False).loads_resolver('\n'.join(reqs))
root.name = 'root'
return root
4 changes: 3 additions & 1 deletion dephell/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .jail_install import JailInstallCommand
from .jail_list import JailListCommand
from .jail_remove import JailRemoveCommand
from .jail_try import JailTryCommand
from .package_downloads import PackageDownloadsCommand
from .package_install import PackageInstallCommand
from .package_list import PackageListCommand
Expand Down Expand Up @@ -58,6 +59,7 @@
'JailInstallCommand',
'JailListCommand',
'JailRemoveCommand',
'JailTryCommand',
'PackageDownloadsCommand',
'PackageInstallCommand',
'PackageListCommand',
Expand Down Expand Up @@ -86,7 +88,6 @@
'deps sync': DepsSyncCommand,
'deps tree': DepsTreeCommand,
# 'deps remove': ...,
# 'deps sync': ...,
'generate authors': GenerateAuthorsCommand,
'generate config': GenerateConfigCommand,
'generate editorconfig': GenerateEditorconfigCommand,
Expand All @@ -98,6 +99,7 @@
'jail install': JailInstallCommand,
'jail list': JailListCommand,
'jail remove': JailRemoveCommand,
'jail try': JailTryCommand,
# 'jail update': ...,
'package downloads': PackageDownloadsCommand,
'package install': PackageInstallCommand,
Expand Down
9 changes: 2 additions & 7 deletions dephell/commands/deps_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,8 @@ def get_parser(cls) -> ArgumentParser:
builders.build_api(parser)
builders.build_output(parser)
builders.build_other(parser)
parser.add_argument(
'--type',
nargs='?',
choices=('pretty', 'json', 'graph'),
default='pretty',
help='format for tree output.',
)
parser.add_argument('--type', choices=('pretty', 'json', 'graph'), default='pretty',
help='format for tree output.')
parser.add_argument('name', nargs=REMAINDER, help='package to get dependencies from')
return parser

Expand Down
2 changes: 1 addition & 1 deletion dephell/commands/jail_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def get_parser(cls) -> ArgumentParser:
builders.build_venv(parser)
builders.build_output(parser)
builders.build_other(parser)
parser.add_argument('name', nargs=REMAINDER, help='package to install')
parser.add_argument('name', nargs=REMAINDER, help='packages to install')
return parser

def __call__(self) -> bool:
Expand Down
144 changes: 144 additions & 0 deletions dephell/commands/jail_try.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# built-in
import subprocess
import shlex
from argparse import REMAINDER, ArgumentParser
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Set


# project
from dephell_venvs import VEnv

# app
from ..context_tools import env_var
from ..actions import get_python, get_resolver
from ..config import builders
from ..controllers import analize_conflict
from ..models import Requirement
from ..package_manager import PackageManager
from .base import BaseCommand


class JailTryCommand(BaseCommand):
"""Try packages into temporary isolated environment.
https://dephell.readthedocs.io/en/latest/cmd-jail-try.html
"""
@classmethod
def get_parser(cls) -> ArgumentParser:
parser = ArgumentParser(
prog='dephell jail try',
description=cls.__doc__,
)
builders.build_config(parser)
builders.build_venv(parser)
builders.build_output(parser)
builders.build_other(parser)
parser.add_argument('--command', help='command to execute.')
parser.add_argument('name', nargs=REMAINDER, help='packages to install')
return parser

def __call__(self) -> bool:
resolver = get_resolver(reqs=self.args.name)
name = next(iter(resolver.graph.get_layer(0))).dependencies[0].name

command = self.config.get('command')
if not command:
command = 'python'
if isinstance(command, str):
command = shlex.split(command)

with TemporaryDirectory() as base_path:
base_path = Path(base_path)

# make venv
venv = VEnv(path=base_path)
if venv.exists():
self.logger.error('already installed', extra=dict(package=name))
return False
python = get_python(self.config)
self.logger.info('creating venv...', extra=dict(
venv=str(venv.path),
python=str(python.path),
))
venv.create(python_path=python.path)

# install
ok = self._install(resolver=resolver, python_path=venv.python_path)
if not ok:
return False

# install executable
executable = venv.bin_path / command[0]
if not executable.exists():
self.logger.warning('executable is not found in venv, trying to install...', extra=dict(
executable=command[0],
))
ok = self._install(
resolver=get_resolver(reqs=command[:1]),
python_path=venv.python_path,
)
if not ok:
return False
if not executable.exists():
self.logger.error('package installed, but executable is not found')
return False

# make startup script to import installed packages
startup_path = base_path / '_startup.py'
packages = self._get_startup_packages(lib_path=venv.lib_path, packages=self.args.name)
if not packages:
self.logger.error('cannot find any packages')
return False
startup_path.write_text('import ' + ', '.join(sorted(packages)))

# run
self.logger.info('running...')
with env_var(key='PYTHONSTARTUP', value=str(startup_path)):
result = subprocess.run([str(executable)] + command[1:])
if result.returncode != 0:
self.logger.error('command failed', extra=dict(code=result.returncode))
return False

return True

def _install(self, resolver, python_path: Path) -> bool:
self.logger.info('build dependencies graph...')
resolved = resolver.resolve(silent=self.config['silent'])
if not resolved:
conflict = analize_conflict(resolver=resolver)
self.logger.warning('conflict was found')
print(conflict)
return False

# install
reqs = Requirement.from_graph(graph=resolver.graph, lock=True)
self.logger.info('installation...', extra=dict(
executable=python_path,
packages=len(reqs),
))
code = PackageManager(executable=python_path).install(reqs=reqs)
if code != 0:
return False
self.logger.info('installed')
return True

@staticmethod
def _get_startup_packages(lib_path: Path, packages) -> Set[str]:
names = set()
for path in lib_path.iterdir():
name = path.name
if name == '__pycache__':
continue
if name.endswith('.py'):
names.add(name.split('.')[0])
elif path.is_dir() and '.' not in name:
names.add(name)

if packages:
packages = {package.lower().replace('-', '_') for package in packages}
if len(names & packages) == len(packages):
return packages

return names
4 changes: 2 additions & 2 deletions dephell/commands/venv_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ def __call__(self) -> bool:

executable = venv.bin_path / command[0]
if not executable.exists():
self.logger.warning('executable does not found in venv, trying to install...', extra=dict(
self.logger.warning('executable is not found in venv, trying to install...', extra=dict(
executable=command[0],
))
result = self._install(name=command[0], python_path=venv.python_path)
if not result:
return False

if not executable.exists():
self.logge.error('package installed, but executable is not found')
self.logger.error('package installed, but executable is not found')
return False

self.logger.info('running...')
Expand Down
6 changes: 1 addition & 5 deletions dephell/converters/egginfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,7 @@ def parse_info(cls, content: str, root=None) -> RootDependency:

# dependencies
deps = []
reqs = chain(
cls._get_list(info, 'Requires'),
cls._get_list(info, 'Requires-Dist'),
)
for req in reqs:
for req in cls._get_list(info, 'Requires-Dist'):
req = PackagingRequirement(req)
deps.extend(DependencyMaker.from_requirement(source=root, req=req))
root.attach_dependencies(deps)
Expand Down
7 changes: 1 addition & 6 deletions dephell/converters/setuppy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from collections import defaultdict
from distutils.core import run_setup
from io import StringIO
from itertools import chain
from logging import getLogger
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -114,12 +113,8 @@ def load(cls, path) -> RootDependency:
root.entrypoints = tuple(entrypoints)

# dependencies
reqs = chain(
cls._get_list(info, 'requires'),
cls._get_list(info, 'install_requires'),
)
deps = []
for req in reqs:
for req in cls._get_list(info, 'install_requires'):
req = Requirement(req)
deps.extend(DependencyMaker.from_requirement(source=root, req=req))
root.attach_dependencies(deps)
Expand Down
19 changes: 12 additions & 7 deletions dephell/repositories/warehouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,11 @@ async def get_dependencies(self, name: str, version: str,
msg = 'cannot parse requirement: "{}" from {} {}'
logger.warning(msg.format(dep, name, version))

dep_extra = req.marker and Markers(req.marker).extra
try:
dep_extra = req.marker and Markers(req.marker).extra
except ValueError: # unsupported operation for version marker python_version: in
dep_extra = None

# it's not extra and we want not extra too
if dep_extra is None and extra is None:
result.append(req)
Expand Down Expand Up @@ -269,12 +273,13 @@ async def _get_from_files(self, files_info: List[dict]) -> Tuple[str, ...]:
if not files_info:
return ()

# Dirty hack to make DepHell much faster.
# If releases contains wheel then PyPI can parse requirements from it,
# but hasn't found iany requirements. So, release has no requirements.
for file_info in files_info:
if file_info['packagetype'] == 'bdist_wheel':
return ()
# # Dirty hack to make DepHell much faster.
# # If releases contains wheel then PyPI can parse requirements from it,
# # but hasn't found iany requirements. So, release has no requirements.
# # UPD: it doesn't work for prompt_toolkit
# for file_info in files_info:
# if file_info['packagetype'] == 'bdist_wheel':
# return ()

from ..converters import SDistConverter, WheelConverter

Expand Down
54 changes: 54 additions & 0 deletions docs/cmd-jail-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# dephell jail try

Try python packages in an isolated environment.

Try [textdistance](https://github.com/orsinium/textdistance):

```bash
$ dephell jail try textdistance
INFO creating venv... (python=/usr/local/bin/python3.7, venv=/tmp/tmpgixqt4_q)
INFO build dependencies graph...
INFO installation... (executable=/tmp/tmpgixqt4_q/bin/python3.7, packages=1)
Collecting textdistance==4.1.3 (from -r /tmp/tmpduyecsir/requiements.txt (line 2))
Installing collected packages: textdistance
Successfully installed textdistance-4.1.3
INFO installed
INFO running...
Python 3.7.0 (default, Dec 24 2018, 12:47:36)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
```

In this example DepHell installs latest `texdistance` release in a temporary virtual environment and runs python interpreter with already imported `textdistance` inside.

Use [ipython](https://ipython.org/) instead of standart python interpreter:

```bash
$ dephell jail try --command=ipython textdistance
```

Set python version:

```bash
$ dephell jail try --python=3.5 textdistance
...
Python 3.5.3 (928a4f70d3de, Feb 08 2019, 10:42:58)
[PyPy 7.0.0 with GCC 6.2.0 20160901] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>>
```

Install flake8 and plugin for it and run checks on given path:

```bash
$ dephell jail try --command="flake8 ./dephell" flake8 flake8-commas
```

## See also

1. [How DepHell choose Python interpreter](python-lookup).
1. [dephell jail install](cmd-jail-install) to install CLI tool in permanent jail.
1. [dephell venv create](cmd-venv-create) for information about virtual environments management in DepHell.
1. [dephell package install](cmd-package-install) to install package into project virtual environment.
1. [dephell deps install](cmd-deps-install) to install all project dependencies.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
cmd-jail-install
cmd-jail-list
cmd-jail-remove
cmd-jail-try
.. toctree::
:maxdepth: 1
Expand Down

0 comments on commit 6362344

Please sign in to comment.