Commit c0fcaf71 by Sébastien Eustace

Add basic support for virtualenvs autogeneration

parent 241033c1
# Change Log # Change Log
## [Unreleased]
### Added
- Added support for virtualenv autogeneration (Python 3.6+ only).
## [0.5.0] - 2018-03-14 ## [0.5.0] - 2018-03-14
......
from pathlib import Path
from typing import Any
from .locations import CONFIG_DIR
from .utils.toml_file import TomlFile
class Config:
def __init__(self, file: TomlFile):
self._file = file
self._raw_content = file.read(raw=True)
self._content = file.read()
@property
def name(self):
return str(self._file.path)
@property
def file(self):
return self._file
@property
def raw_content(self):
return self._raw_content
@property
def content(self):
return self._content
def setting(self, setting_name: str) -> Any:
"""
Retrieve a setting value.
"""
keys = setting_name.split('.')
config = self._raw_content
for key in keys:
if key not in config:
return None
config = config[key]
return config
def add_property(self, key, value):
keys = key.split('.')
config = self._content
for i, key in enumerate(keys):
if key not in config and i < len(keys) - 1:
config[key] = {}
if i == len(keys) - 1:
config[key] = value
break
config = config[key]
self.dump()
def remove_property(self, key):
keys = key.split('.')
config = self._content
for i, key in enumerate(keys):
if key not in config:
return
if i == len(keys) - 1:
del config[key]
break
config = config[key]
self.dump()
def dump(self):
self._file.write(self._content)
@classmethod
def create(cls, file, base_dir=None) -> 'Config':
if base_dir is None:
base_dir = CONFIG_DIR
file = TomlFile(Path(base_dir) / file)
return cls(file)
...@@ -24,7 +24,6 @@ class Application(BaseApplication): ...@@ -24,7 +24,6 @@ class Application(BaseApplication):
super().__init__('Poetry', Poetry.VERSION) super().__init__('Poetry', Poetry.VERSION)
self._poetry = None self._poetry = None
self._venv = Venv.create()
@property @property
def poetry(self) -> Poetry: def poetry(self) -> Poetry:
...@@ -38,10 +37,6 @@ class Application(BaseApplication): ...@@ -38,10 +37,6 @@ class Application(BaseApplication):
def reset_poetry(self) -> None: def reset_poetry(self) -> None:
self._poetry = None self._poetry = None
@property
def venv(self) -> Venv:
return self._venv
def get_default_commands(self) -> list: def get_default_commands(self) -> list:
commands = super(Application, self).get_default_commands() commands = super(Application, self).get_default_commands()
...@@ -58,9 +53,3 @@ class Application(BaseApplication): ...@@ -58,9 +53,3 @@ class Application(BaseApplication):
ShowCommand(), ShowCommand(),
UpdateCommand(), UpdateCommand(),
] ]
def do_run(self, i, o) -> int:
if self._venv.is_venv() and o.is_verbose():
o.writeln(f'Using virtualenv: <comment>{self._venv.venv}</>')
return super().do_run(i, o)
...@@ -7,10 +7,10 @@ from poetry.installation import Installer ...@@ -7,10 +7,10 @@ from poetry.installation import Installer
from poetry.semver.version_parser import VersionParser from poetry.semver.version_parser import VersionParser
from poetry.version.version_selector import VersionSelector from poetry.version.version_selector import VersionSelector
from .command import Command from .venv_command import VenvCommand
class AddCommand(Command): class AddCommand(VenvCommand):
""" """
Add a new depdency to <comment>poetry.toml</>. Add a new depdency to <comment>poetry.toml</>.
...@@ -66,6 +66,7 @@ If you do not specify a version constraint, poetry will choose a suitable one ba ...@@ -66,6 +66,7 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
installer = Installer( installer = Installer(
self.output, self.output,
self.venv,
self.poetry.package, self.poetry.package,
self.poetry.locker, self.poetry.locker,
self.poetry.pool self.poetry.pool
......
from .command import Command from .venv_command import VenvCommand
from poetry.masonry import Builder from poetry.masonry import Builder
class BuildCommand(Command): class BuildCommand(VenvCommand):
""" """
Builds a package, as a tarball and a wheel by default. Builds a package, as a tarball and a wheel by default.
......
...@@ -35,6 +35,6 @@ class Command(BaseCommand): ...@@ -35,6 +35,6 @@ class Command(BaseCommand):
Initialize command. Initialize command.
""" """
self.input = i self.input = i
self.output = PoetryStyle(i, o, self.get_application().venv) self.output = PoetryStyle(i, o)
return super(BaseCommand, self).run(i, o) return super(BaseCommand, self).run(i, o)
import json import json
import re import re
from pathlib import Path from poetry.config import Config
from poetry.locations import CONFIG_DIR
from poetry.toml import loads
from .command import Command from .command import Command
TEMPLATE = """[repositories] TEMPLATE = """[settings]
[repositories]
""" """
AUTH_TEMPLATE = """[http-basic] AUTH_TEMPLATE = """[http-basic]
...@@ -41,36 +40,24 @@ To remove a repository (repo is a short alias for repositories): ...@@ -41,36 +40,24 @@ To remove a repository (repo is a short alias for repositories):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._config_file = None self._config = Config.create('config.toml')
self._config = {} self._auth_config = Config.create('auth.toml')
self._auth_config_file = None
self._auth_config = {}
def initialize(self, i, o): def initialize(self, i, o):
super().initialize(i, o) super().initialize(i, o)
# Create config file if it does not exist # Create config file if it does not exist
self._config_file = Path(CONFIG_DIR) / 'config.toml' if not self._config.file.exists():
self._auth_config_file = Path(CONFIG_DIR) / 'auth.toml' self._config.file.parent.mkdir(parents=True, exist_ok=True)
self._config.file.write_text(TEMPLATE)
if not self._config_file.exists():
self._config_file.parent.mkdir(parents=True, exist_ok=True)
self._config_file.write_text(TEMPLATE)
if not self._auth_config_file.exists():
self._auth_config_file.parent.mkdir(parents=True, exist_ok=True) if not self._auth_config.file.exists():
self._auth_config_file.write_text(AUTH_TEMPLATE) self._auth_config.file.parent.mkdir(parents=True, exist_ok=True)
self._auth_config.file.write_text(AUTH_TEMPLATE)
with self._config_file.open() as f:
self._config = loads(f.read())
with self._auth_config_file.open() as f:
self._auth_config = loads(f.read())
def handle(self): def handle(self):
if self.option('list'): if self.option('list'):
self._list_configuration(self._config) self._list_configuration(self._config.raw_content)
return 0 return 0
...@@ -87,15 +74,16 @@ To remove a repository (repo is a short alias for repositories): ...@@ -87,15 +74,16 @@ To remove a repository (repo is a short alias for repositories):
if m: if m:
if not m.group(1): if not m.group(1):
value = {} value = {}
if 'repositories' in self._config: if self._config.setting('repositories') is not None:
value = self._config['repositories'] value = self._config.setting('repositories')
else: else:
if m.group(1) not in self._config['repositories']: repo = self._config.setting(f'repositories.{m.group(1)}')
if repo is None:
raise ValueError( raise ValueError(
f'There is not {m.group(1)} repository defined' f'There is no {m.group(1)} repository defined'
) )
value = self._config['repositories'][m.group(1)] value = repo
self.line(str(value)) self.line(str(value))
...@@ -103,6 +91,23 @@ To remove a repository (repo is a short alias for repositories): ...@@ -103,6 +91,23 @@ To remove a repository (repo is a short alias for repositories):
values = self.argument('value') values = self.argument('value')
boolean_validator = lambda val: val in {'true', 'false', '1', '0'}
boolean_normalizer = lambda val: True if val in ['true', '1'] else False
unique_config_values = {
'settings.virtualenvs.create': (boolean_validator, boolean_normalizer)
}
if setting_key in unique_config_values:
if self.option('unset'):
return self._remove_single_value(setting_key)
return self._handle_single_value(
setting_key,
unique_config_values[setting_key],
values
)
# handle repositories # handle repositories
m = re.match('^repos?(?:itories)?(?:\.(.+))?', self.argument('key')) m = re.match('^repos?(?:itories)?(?:\.(.+))?', self.argument('key'))
if m: if m:
...@@ -110,26 +115,18 @@ To remove a repository (repo is a short alias for repositories): ...@@ -110,26 +115,18 @@ To remove a repository (repo is a short alias for repositories):
raise ValueError('You cannot remove the [repositories] section') raise ValueError('You cannot remove the [repositories] section')
if self.option('unset'): if self.option('unset'):
if m.group(1) not in self._config['repositories']: repo = self._config.setting(f'repositories.{m.group(1)}')
raise ValueError(f'There is not {m.group(1)} repository defined') if repo is None:
raise ValueError(f'There is no {m.group(1)} repository defined')
del self._config[m.group(1)]
self._config_file.write_text(self._config.dumps()) self._config.remove_property(f'repositories.{m.group(1)}')
return 0 return 0
if len(values) == 1: if len(values) == 1:
url = values[0] url = values[0]
if m.group(1) in self._config['repositories']: self._config.add_property(f'repositories.{m.group(1)}.url', url)
self._config['repositories'][m.group(1)]['url'] = url
else:
self._config['repositories'][m.group(1)] = {
'url': url
}
self._config_file.write_text(self._config.dumps())
return 0 return 0
...@@ -142,14 +139,12 @@ To remove a repository (repo is a short alias for repositories): ...@@ -142,14 +139,12 @@ To remove a repository (repo is a short alias for repositories):
m = re.match('^(http-basic)\.(.+)', self.argument('key')) m = re.match('^(http-basic)\.(.+)', self.argument('key'))
if m: if m:
if self.option('unset'): if self.option('unset'):
if m.group(2) not in self._auth_config[m.group(1)]: if not self._auth_config.setting(f'{m.group(1)}.{m.group(2)}'):
raise ValueError( raise ValueError(
f'There is no {m.group(2)} {m.group(1)} defined' f'There is no {m.group(2)} {m.group(1)} defined'
) )
del self._auth_config[m.group(1)][m.group(2)] self._auth_config.remove_property(f'{m.group(1)}.{m.group(2)}')
self._auth_config_file.write_text(self._auth_config.dumps())
return 0 return 0
...@@ -165,17 +160,38 @@ To remove a repository (repo is a short alias for repositories): ...@@ -165,17 +160,38 @@ To remove a repository (repo is a short alias for repositories):
username = values[0] username = values[0]
password = values[1] password = values[1]
self._auth_config[m.group(1)][m.group(2)] = { self._auth_config.add_property(
'username': username, f'{m.group(1)}.{m.group(2)}', {
'password': password 'username': username,
} 'password': password
}
self._auth_config_file.write_text(self._auth_config.dumps()) )
return 0 return 0
raise ValueError(f'Setting {self.argument("key")} does not exist') raise ValueError(f'Setting {self.argument("key")} does not exist')
def _handle_single_value(self, key, callbacks, values):
validator, normalizer = callbacks
if len(values) > 1:
raise RuntimeError('You can only pass one value.')
value = values[0]
if not validator(value):
raise RuntimeError(
f'"{value}" is an invalid value for {key}'
)
self._config.add_property(key, normalizer(value))
return 0
def _remove_single_value(self, key):
self._config.remove_property(key)
return 0
def _list_configuration(self, contents, k=None): def _list_configuration(self, contents, k=None):
orig_k = k orig_k = k
......
from poetry.installation import Installer from poetry.installation import Installer
from poetry.repositories.pypi_repository import PyPiRepository
from .command import Command from .venv_command import VenvCommand
class InstallCommand(Command): class InstallCommand(VenvCommand):
""" """
Installs the project dependencies. Installs the project dependencies.
...@@ -27,6 +26,7 @@ exist it will look for <comment>poetry.toml</> and do the same. ...@@ -27,6 +26,7 @@ exist it will look for <comment>poetry.toml</> and do the same.
def handle(self): def handle(self):
installer = Installer( installer = Installer(
self.output, self.output,
self.venv,
self.poetry.package, self.poetry.package,
self.poetry.locker, self.poetry.locker,
self.poetry.pool self.poetry.pool
......
from poetry.installation import Installer from poetry.installation import Installer
from .command import Command from .venv_command import VenvCommand
class LockCommand(Command): class LockCommand(VenvCommand):
""" """
Locks the project dependencies. Locks the project dependencies.
...@@ -19,6 +19,7 @@ the current directory, processes it, and locks the depdencies in the <comment>po ...@@ -19,6 +19,7 @@ the current directory, processes it, and locks the depdencies in the <comment>po
def handle(self): def handle(self):
installer = Installer( installer = Installer(
self.output, self.output,
self.venv,
self.poetry.package, self.poetry.package,
self.poetry.locker, self.poetry.locker,
self.poetry.pool self.poetry.pool
......
from poetry.installation import Installer from poetry.installation import Installer
from .command import Command from .venv_command import VenvCommand
class RemoveCommand(Command): class RemoveCommand(VenvCommand):
""" """
Removes a package from the project dependencies. Removes a package from the project dependencies.
...@@ -54,6 +54,7 @@ list of installed packages ...@@ -54,6 +54,7 @@ list of installed packages
installer = Installer( installer = Installer(
self.output, self.output,
self.venv,
self.poetry.package, self.poetry.package,
self.poetry.locker, self.poetry.locker,
self.poetry.pool self.poetry.pool
......
from poetry.semver import statisfies from poetry.semver import statisfies
from poetry.version.version_selector import VersionSelector from poetry.version.version_selector import VersionSelector
from .command import Command from .venv_command import VenvCommand
class ShowCommand(Command): class ShowCommand(VenvCommand):
""" """
Shows information about packages. Shows information about packages.
......
from poetry.installation import Installer from poetry.installation import Installer
from poetry.repositories.pypi_repository import PyPiRepository
from .command import Command from .venv_command import VenvCommand
class UpdateCommand(Command): class UpdateCommand(VenvCommand):
""" """
Update dependencies as according to the <comment>poetry.toml</> file. Update dependencies as according to the <comment>poetry.toml</> file.
...@@ -20,6 +19,7 @@ class UpdateCommand(Command): ...@@ -20,6 +19,7 @@ class UpdateCommand(Command):
installer = Installer( installer = Installer(
self.output, self.output,
self.venv,
self.poetry.package, self.poetry.package,
self.poetry.locker, self.poetry.locker,
self.poetry.pool self.poetry.pool
......
from poetry.utils.venv import Venv
from .command import Command
class VenvCommand(Command):
def __init__(self, name=None):
self._venv = None
super().__init__(name)
def initialize(self, i, o):
super().initialize(i, o)
self._venv = Venv.create(o, self.poetry.package.name)
if self._venv.is_venv() and o.is_verbose():
o.writeln(f'Using virtualenv: <comment>{self._venv.venv}</>')
@property
def venv(self):
return self._venv
...@@ -4,18 +4,12 @@ from cleo.styles import OutputStyle ...@@ -4,18 +4,12 @@ from cleo.styles import OutputStyle
class PoetryStyle(CleoStyle): class PoetryStyle(CleoStyle):
def __init__(self, i, o, venv): def __init__(self, i, o):
self._venv = venv
super().__init__(i, o) super().__init__(i, o)
self.output.get_formatter().add_style('warning', 'black', 'yellow') self.output.get_formatter().add_style('warning', 'black', 'yellow')
self.output.get_formatter().add_style('question', 'blue') self.output.get_formatter().add_style('question', 'blue')
@property
def venv(self):
return self._venv
def writeln(self, messages, def writeln(self, messages,
type=OutputStyle.OUTPUT_NORMAL, type=OutputStyle.OUTPUT_NORMAL,
verbosity=OutputStyle.VERBOSITY_NORMAL): verbosity=OutputStyle.VERBOSITY_NORMAL):
......
...@@ -25,10 +25,12 @@ class Installer: ...@@ -25,10 +25,12 @@ class Installer:
def __init__(self, def __init__(self,
io, io,
venv,
package: Package, package: Package,
locker: Locker, locker: Locker,
pool: Pool): pool: Pool):
self._io = io self._io = io
self._venv = venv
self._package = package self._package = package
self._locker = locker self._locker = locker
self._pool = pool self._pool = pool
...@@ -345,7 +347,7 @@ class Installer: ...@@ -345,7 +347,7 @@ class Installer:
def _get_operations_from_lock(self, def _get_operations_from_lock(self,
locked_repository: Repository locked_repository: Repository
) -> List[Operation]: ) -> List[Operation]:
installed_repo = InstalledRepository.load(self._io.venv) installed_repo = InstalledRepository.load(self._venv)
ops = [] ops = []
extra_packages = [ extra_packages = [
...@@ -390,7 +392,7 @@ class Installer: ...@@ -390,7 +392,7 @@ class Installer:
continue continue
parser = VersionParser() parser = VersionParser()
python = '.'.join([str(i) for i in self._io.venv.version_info[:3]]) python = '.'.join([str(i) for i in self._venv.version_info[:3]])
if 'python' in package.requirements: if 'python' in package.requirements:
python_constraint = parser.parse_constraints( python_constraint = parser.parse_constraints(
package.requirements['python'] package.requirements['python']
...@@ -462,4 +464,4 @@ class Installer: ...@@ -462,4 +464,4 @@ class Installer:
return _extra_packages(extra_packages) return _extra_packages(extra_packages)
def _get_installer(self) -> BaseInstaller: def _get_installer(self) -> BaseInstaller:
return PipInstaller(self._io.venv, self._io) return PipInstaller(self._venv, self._io)
from poetry.console.styles.poetry import PoetryStyle from cleo.inputs import ListInput
from poetry.utils.venv import Venv from cleo.outputs import NullOutput
class NullVenv(Venv):
def __init__(self, execute=False):
super().__init__()
self.executed = []
self._execute = execute
def run(self, bin: str, *args): from poetry.console.styles.poetry import PoetryStyle
self.executed.append([bin] + list(args))
if self._execute:
return super().run(bin, *args)
def _bin(self, bin):
return bin
class NullIO(PoetryStyle): class NullIO(PoetryStyle):
def __init__(self, execute=False): def __init__(self):
self._venv = NullVenv(execute=execute) super().__init__(ListInput([]), NullOutput())
@property
def venv(self) -> NullVenv:
return self._venv
def is_quiet(self) -> bool: def is_quiet(self) -> bool:
return False return False
......
from poetry.semver.constraints import MultiConstraint
from .builders import CompleteBuilder from .builders import CompleteBuilder
from .builders import SdistBuilder from .builders import SdistBuilder
from .builders import WheelBuilder from .builders import WheelBuilder
...@@ -13,14 +11,15 @@ class Builder: ...@@ -13,14 +11,15 @@ class Builder:
'all': CompleteBuilder 'all': CompleteBuilder
} }
def __init__(self, poetry, io): def __init__(self, poetry, venv, io):
self._poetry = poetry self._poetry = poetry
self._venv = venv
self._io = io self._io = io
def build(self, fmt: str): def build(self, fmt: str):
if fmt not in self._FORMATS: if fmt not in self._FORMATS:
raise ValueError(f'Invalid format: {fmt}') raise ValueError(f'Invalid format: {fmt}')
builder = self._FORMATS[fmt](self._poetry, self._io) builder = self._FORMATS[fmt](self._poetry, self._venv, self._io)
return builder.build() return builder.build()
...@@ -24,8 +24,9 @@ class Builder: ...@@ -24,8 +24,9 @@ class Builder:
'3.4', '3.5', '3.6', '3.7' '3.4', '3.5', '3.6', '3.7'
} }
def __init__(self, poetry, io): def __init__(self, poetry, venv, io):
self._poetry = poetry self._poetry = poetry
self._venv = venv
self._io = io self._io = io
self._package = poetry.package self._package = poetry.package
self._path = poetry.file.parent self._path = poetry.file.parent
......
...@@ -17,7 +17,7 @@ class CompleteBuilder(Builder): ...@@ -17,7 +17,7 @@ class CompleteBuilder(Builder):
def build(self): def build(self):
# We start by building the tarball # We start by building the tarball
# We will use it to build the wheel # We will use it to build the wheel
sdist_builder = SdistBuilder(self._poetry, self._io) sdist_builder = SdistBuilder(self._poetry, self._venv, self._io)
sdist_file = sdist_builder.build() sdist_file = sdist_builder.build()
sdist_info = SimpleNamespace(builder=sdist_builder, file=sdist_file) sdist_info = SimpleNamespace(builder=sdist_builder, file=sdist_file)
...@@ -26,7 +26,7 @@ class CompleteBuilder(Builder): ...@@ -26,7 +26,7 @@ class CompleteBuilder(Builder):
dist_dir = self._path / 'dist' dist_dir = self._path / 'dist'
with self.unpacked_tarball(sdist_file) as tmpdir: with self.unpacked_tarball(sdist_file) as tmpdir:
wheel_info = WheelBuilder.make_in( wheel_info = WheelBuilder.make_in(
poetry.Poetry.create(tmpdir), self._io, dist_dir, poetry.Poetry.create(tmpdir), self._venv, self._io, dist_dir,
original=self._poetry original=self._poetry
) )
......
...@@ -51,9 +51,6 @@ Author-email: {author_email} ...@@ -51,9 +51,6 @@ Author-email: {author_email}
class SdistBuilder(Builder): class SdistBuilder(Builder):
def __init__(self, poetry, io):
super().__init__(poetry, io)
def build(self, target_dir: Path = None) -> Path: def build(self, target_dir: Path = None) -> Path:
self._io.writeln(' - Building <info>sdist</info>') self._io.writeln(' - Building <info>sdist</info>')
if target_dir is None: if target_dir is None:
......
...@@ -35,8 +35,8 @@ Root-Is-Purelib: true ...@@ -35,8 +35,8 @@ Root-Is-Purelib: true
class WheelBuilder(Builder): class WheelBuilder(Builder):
def __init__(self, poetry, io, target_fp, original=None): def __init__(self, poetry, venv, io, target_fp, original=None):
super().__init__(poetry, io) super().__init__(poetry, venv, io)
self._records = [] self._records = []
self._original_path = self._path self._original_path = self._path
...@@ -48,14 +48,14 @@ class WheelBuilder(Builder): ...@@ -48,14 +48,14 @@ class WheelBuilder(Builder):
compression=zipfile.ZIP_DEFLATED) compression=zipfile.ZIP_DEFLATED)
@classmethod @classmethod
def make_in(cls, poetry, io, directory, original=None) -> SimpleNamespace: def make_in(cls, poetry, venv, io, directory, original=None) -> SimpleNamespace:
# We don't know the final filename until metadata is loaded, so write to # We don't know the final filename until metadata is loaded, so write to
# a temporary_file, and rename it afterwards. # a temporary_file, and rename it afterwards.
(fd, temp_path) = tempfile.mkstemp(suffix='.whl', (fd, temp_path) = tempfile.mkstemp(suffix='.whl',
dir=str(directory)) dir=str(directory))
try: try:
with open(fd, 'w+b') as fp: with open(fd, 'w+b') as fp:
wb = WheelBuilder(poetry, io, fp, original=original) wb = WheelBuilder(poetry, venv, io, fp, original=original)
wb.build() wb.build()
wheel_path = directory / wb.wheel_filename wheel_path = directory / wb.wheel_filename
...@@ -67,7 +67,7 @@ class WheelBuilder(Builder): ...@@ -67,7 +67,7 @@ class WheelBuilder(Builder):
return SimpleNamespace(builder=wb, file=wheel_path) return SimpleNamespace(builder=wb, file=wheel_path)
@classmethod @classmethod
def make(cls, poetry, io) -> SimpleNamespace: def make(cls, poetry, venv, io) -> SimpleNamespace:
"""Build a wheel in the dist/ directory, and optionally upload it. """Build a wheel in the dist/ directory, and optionally upload it.
""" """
dist_dir = poetry.file.parent / 'dist' dist_dir = poetry.file.parent / 'dist'
...@@ -76,7 +76,7 @@ class WheelBuilder(Builder): ...@@ -76,7 +76,7 @@ class WheelBuilder(Builder):
except FileExistsError: except FileExistsError:
pass pass
return cls.make_in(poetry, io, dist_dir) return cls.make_in(poetry, venv, io, dist_dir)
def build(self) -> None: def build(self) -> None:
self._io.writeln(' - Building <info>wheel</info>') self._io.writeln(' - Building <info>wheel</info>')
...@@ -99,7 +99,7 @@ class WheelBuilder(Builder): ...@@ -99,7 +99,7 @@ class WheelBuilder(Builder):
current_path = os.getcwd() current_path = os.getcwd()
try: try:
os.chdir(str(self._path)) os.chdir(str(self._path))
self._io.venv.run( self._venv.run(
'python', 'python',
str(setup), str(setup),
'build', 'build',
......
...@@ -3,8 +3,14 @@ import os ...@@ -3,8 +3,14 @@ import os
import subprocess import subprocess
import sys import sys
from pathlib import Path
from subprocess import CalledProcessError from subprocess import CalledProcessError
from venv import EnvBuilder
from poetry.config import Config
from poetry.locations import CACHE_DIR
class VenvError(Exception): class VenvError(Exception):
...@@ -27,10 +33,46 @@ class Venv: ...@@ -27,10 +33,46 @@ class Venv:
self._version_info = None self._version_info = None
@classmethod @classmethod
def create(cls) -> 'Venv': def create(cls, io, name=None) -> 'Venv':
if 'VIRTUAL_ENV' not in os.environ: if 'VIRTUAL_ENV' not in os.environ:
# Not in a virtualenv # Not in a virtualenv
return cls() # Checking if we need to create one
config = Config.create('config.toml')
create_venv = config.setting('settings.virtualenvs.create')
if create_venv is False:
io.writeln(
'<fg=black;bg=yellow>'
'Skipping virtualenv creation, '
'as specified in config file.'
'</>'
)
return cls()
venv_path = config.setting('settings.virtualenvs.path')
if venv_path is None:
venv_path = Path(CACHE_DIR) / 'virtualenvs'
else:
venv_path = Path(venv_path)
if not name:
name = Path.cwd().name
name = f'{name}-py{".".join([str(v) for v in sys.version_info[:2]])}'
venv = venv_path / name
if not venv.exists():
io.writeln(
f'Creating virtualenv <info>{name}</> in {str(venv_path)}'
)
builder = EnvBuilder(with_pip=True)
builder.create(str(venv))
else:
if io.is_very_verbose():
io.writeln(f'Virtualenv <info>{name}</> already exists.')
os.environ['VIRTUAL_ENV'] = str(venv)
# venv detection: # venv detection:
# stdlib venv may symlink sys.executable, so we can't use realpath. # stdlib venv may symlink sys.executable, so we can't use realpath.
...@@ -134,3 +176,21 @@ class Venv: ...@@ -134,3 +176,21 @@ class Venv:
def is_venv(self) -> bool: def is_venv(self) -> bool:
return self._venv is not None return self._venv is not None
class NullVenv(Venv):
def __init__(self, execute=False):
super().__init__()
self.executed = []
self._execute = execute
def run(self, bin: str, *args):
self.executed.append([bin] + list(args))
if self._execute:
return super().run(bin, *args)
def _bin(self, bin):
return bin
...@@ -11,6 +11,7 @@ from poetry.packages import Locker as BaseLocker ...@@ -11,6 +11,7 @@ from poetry.packages import Locker as BaseLocker
from poetry.repositories import Pool from poetry.repositories import Pool
from poetry.repositories import Repository from poetry.repositories import Repository
from poetry.repositories.installed_repository import InstalledRepository from poetry.repositories.installed_repository import InstalledRepository
from poetry.utils.venv import NullVenv
from tests.helpers import get_dependency from tests.helpers import get_dependency
from tests.helpers import get_package from tests.helpers import get_package
...@@ -108,17 +109,22 @@ def locker(): ...@@ -108,17 +109,22 @@ def locker():
return Locker() return Locker()
@pytest.fixture()
def venv():
return NullVenv()
@pytest.fixture()
def installer(package, pool, locker, venv):
return Installer(NullIO(), venv, package, locker, pool)
def fixture(name): def fixture(name):
file = Path(__file__).parent / 'fixtures' / f'{name}.test' file = Path(__file__).parent / 'fixtures' / f'{name}.test'
return toml.loads(file.read_text()) return toml.loads(file.read_text())
@pytest.fixture()
def installer(package, pool, locker):
return Installer(NullIO(), package, locker, pool)
def test_run_no_dependencies(installer, locker): def test_run_no_dependencies(installer, locker):
installer.run() installer.run()
expected = fixture('no-dependencies') expected = fixture('no-dependencies')
......
...@@ -8,6 +8,7 @@ from pathlib import Path ...@@ -8,6 +8,7 @@ from pathlib import Path
from poetry import Poetry from poetry import Poetry
from poetry.io import NullIO from poetry.io import NullIO
from poetry.masonry.builders import CompleteBuilder from poetry.masonry.builders import CompleteBuilder
from poetry.utils.venv import NullVenv
fixtures_dir = Path(__file__).parent / 'fixtures' fixtures_dir = Path(__file__).parent / 'fixtures'
...@@ -29,7 +30,7 @@ def clear_samples_dist(): ...@@ -29,7 +30,7 @@ def clear_samples_dist():
def test_wheel_c_extension(): def test_wheel_c_extension():
module_path = fixtures_dir / 'extended' module_path = fixtures_dir / 'extended'
builder = CompleteBuilder(Poetry.create(module_path), NullIO(True)) builder = CompleteBuilder(Poetry.create(module_path), NullVenv(True), NullIO())
builder.build() builder.build()
sdist = fixtures_dir / 'extended' / 'dist' / 'extended-0.1.tar.gz' sdist = fixtures_dir / 'extended' / 'dist' / 'extended-0.1.tar.gz'
......
...@@ -8,6 +8,7 @@ from pathlib import Path ...@@ -8,6 +8,7 @@ from pathlib import Path
from poetry import Poetry from poetry import Poetry
from poetry.io import NullIO from poetry.io import NullIO
from poetry.masonry.builders.sdist import SdistBuilder from poetry.masonry.builders.sdist import SdistBuilder
from poetry.utils.venv import NullVenv
from tests.helpers import get_dependency from tests.helpers import get_dependency
...@@ -71,7 +72,7 @@ def test_convert_dependencies(): ...@@ -71,7 +72,7 @@ def test_convert_dependencies():
def test_make_setup(): def test_make_setup():
poetry = Poetry.create(project('complete')) poetry = Poetry.create(project('complete'))
builder = SdistBuilder(poetry, NullIO()) builder = SdistBuilder(poetry, NullVenv(), NullIO())
setup = builder.build_setup() setup = builder.build_setup()
setup_ast = ast.parse(setup) setup_ast = ast.parse(setup)
...@@ -94,7 +95,7 @@ def test_make_setup(): ...@@ -94,7 +95,7 @@ def test_make_setup():
def test_find_files_to_add(): def test_find_files_to_add():
poetry = Poetry.create(project('complete')) poetry = Poetry.create(project('complete'))
builder = SdistBuilder(poetry, NullIO()) builder = SdistBuilder(poetry, NullVenv(), NullIO())
result = builder.find_files_to_add() result = builder.find_files_to_add()
assert result == [ assert result == [
...@@ -111,7 +112,7 @@ def test_find_files_to_add(): ...@@ -111,7 +112,7 @@ def test_find_files_to_add():
def test_package(): def test_package():
poetry = Poetry.create(project('complete')) poetry = Poetry.create(project('complete'))
builder = SdistBuilder(poetry, NullIO()) builder = SdistBuilder(poetry, NullVenv(), NullIO())
builder.build() builder.build()
sdist = fixtures_dir / 'complete' / 'dist' / 'my-package-1.2.3.tar.gz' sdist = fixtures_dir / 'complete' / 'dist' / 'my-package-1.2.3.tar.gz'
...@@ -122,7 +123,7 @@ def test_package(): ...@@ -122,7 +123,7 @@ def test_package():
def test_prelease(): def test_prelease():
poetry = Poetry.create(project('prerelease')) poetry = Poetry.create(project('prerelease'))
builder = SdistBuilder(poetry, NullIO()) builder = SdistBuilder(poetry, NullVenv(), NullIO())
builder.build() builder.build()
sdist = fixtures_dir / 'prerelease' / 'dist' / 'prerelease-0.1b1.tar.gz' sdist = fixtures_dir / 'prerelease' / 'dist' / 'prerelease-0.1b1.tar.gz'
...@@ -133,7 +134,7 @@ def test_prelease(): ...@@ -133,7 +134,7 @@ def test_prelease():
def test_with_c_extensions(): def test_with_c_extensions():
poetry = Poetry.create(project('extended')) poetry = Poetry.create(project('extended'))
builder = SdistBuilder(poetry, NullIO()) builder = SdistBuilder(poetry, NullVenv(), NullIO())
builder.build() builder.build()
sdist = fixtures_dir / 'extended' / 'dist' / 'extended-0.1.tar.gz' sdist = fixtures_dir / 'extended' / 'dist' / 'extended-0.1.tar.gz'
......
...@@ -6,6 +6,7 @@ from pathlib import Path ...@@ -6,6 +6,7 @@ from pathlib import Path
from poetry import Poetry from poetry import Poetry
from poetry.io import NullIO from poetry.io import NullIO
from poetry.masonry.builders import WheelBuilder from poetry.masonry.builders import WheelBuilder
from poetry.utils.venv import NullVenv
fixtures_dir = Path(__file__).parent / 'fixtures' fixtures_dir = Path(__file__).parent / 'fixtures'
...@@ -28,7 +29,7 @@ def clear_samples_dist(): ...@@ -28,7 +29,7 @@ def clear_samples_dist():
def test_wheel_module(): def test_wheel_module():
module_path = fixtures_dir / 'module1' module_path = fixtures_dir / 'module1'
WheelBuilder.make(Poetry.create(str(module_path)), NullIO()) WheelBuilder.make(Poetry.create(str(module_path)), NullVenv(), NullIO())
whl = module_path / 'dist' / 'module1-0.1-py2.py3-none-any.whl' whl = module_path / 'dist' / 'module1-0.1-py2.py3-none-any.whl'
...@@ -37,7 +38,7 @@ def test_wheel_module(): ...@@ -37,7 +38,7 @@ def test_wheel_module():
def test_wheel_package(): def test_wheel_package():
module_path = fixtures_dir / 'complete' module_path = fixtures_dir / 'complete'
WheelBuilder.make(Poetry.create(str(module_path)), NullIO()) WheelBuilder.make(Poetry.create(str(module_path)), NullVenv(), NullIO())
whl = module_path / 'dist' / 'my_package-1.2.3-py3-none-any.whl' whl = module_path / 'dist' / 'my_package-1.2.3-py3-none-any.whl'
...@@ -46,7 +47,7 @@ def test_wheel_package(): ...@@ -46,7 +47,7 @@ def test_wheel_package():
def test_wheel_prerelease(): def test_wheel_prerelease():
module_path = fixtures_dir / 'prerelease' module_path = fixtures_dir / 'prerelease'
WheelBuilder.make(Poetry.create(str(module_path)), NullIO()) WheelBuilder.make(Poetry.create(str(module_path)), NullVenv(), NullIO())
whl = module_path / 'dist' / 'prerelease-0.1b1-py2.py3-none-any.whl' whl = module_path / 'dist' / 'prerelease-0.1b1-py2.py3-none-any.whl'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment