Commit 4b2ffcaf by Sébastien Eustace

Improve debugging of dependency resolution

parent 5d61ab7c
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
- Dependency resolution caches now use sha256 hashes. - Dependency resolution caches now use sha256 hashes.
- Changed CLI error style. - Changed CLI error style.
- Improved debugging of dependency resolution.
## [0.8.3] - 2018-04-16 ## [0.8.3] - 2018-04-16
......
...@@ -24,6 +24,10 @@ class AddCommand(VenvCommand): ...@@ -24,6 +24,10 @@ class AddCommand(VenvCommand):
If you do not specify a version constraint, poetry will choose a suitable one based on the available package versions. If you do not specify a version constraint, poetry will choose a suitable one based on the available package versions.
""" """
_loggers = [
'poetry.repositories.pypi_repository'
]
def handle(self): def handle(self):
from poetry.installation import Installer from poetry.installation import Installer
from poetry.semver.version_parser import VersionParser from poetry.semver.version_parser import VersionParser
......
import logging
from cleo import Command as BaseCommand from cleo import Command as BaseCommand
from ..styles.poetry import PoetryStyle from ..styles.poetry import PoetryStyle
class CommandFormatter(logging.Formatter):
_colors = {
'error': 'fg=red',
'warning': 'fg=yellow',
'debug': 'fg=blue',
}
def format(self, record):
if not record.exc_info:
level = record.levelname.lower()
msg = record.msg
if level in self._colors:
msg = '<{}>{}</>'.format(self._colors[level], msg)
return msg
return super(CommandFormatter, self).format(record)
class CommandHandler(logging.Handler):
def __init__(self, command):
self._command = command
output = self._command.output
level = logging.WARNING
if output.is_debug():
level = logging.DEBUG
elif output.is_very_verbose() or output.is_verbose():
level = logging.INFO
super(CommandHandler, self).__init__(level)
def emit(self, record):
try:
msg = self.format(record)
level = record.levelname.lower()
err = level in ('warning', 'error', 'exception', 'critical')
if err:
self._command.output.write_error(msg, newline=True)
else:
self._command.line(msg)
except Exception:
self.handleError(record)
class Command(BaseCommand): class Command(BaseCommand):
_loggers = []
@property @property
def poetry(self): def poetry(self):
return self.get_application().poetry return self.get_application().poetry
...@@ -19,4 +71,25 @@ class Command(BaseCommand): ...@@ -19,4 +71,25 @@ class Command(BaseCommand):
self.input = i self.input = i
self.output = PoetryStyle(i, o) self.output = PoetryStyle(i, o)
for logger in self._loggers:
self.register_logger(logging.getLogger(logger))
return super(BaseCommand, self).run(i, o) return super(BaseCommand, self).run(i, o)
def register_logger(self, logger):
"""
Register a new logger.
"""
handler = CommandHandler(self)
handler.setFormatter(CommandFormatter())
logger.handlers = [handler]
logger.propagate = False
output = self.output
level = logging.WARNING
if output.is_debug():
level = logging.DEBUG
elif output.is_very_verbose() or output.is_verbose():
level = logging.INFO
logger.setLevel(level)
...@@ -13,6 +13,10 @@ class DebugResolveCommand(Command): ...@@ -13,6 +13,10 @@ class DebugResolveCommand(Command):
{ package?* : packages to resolve. } { package?* : packages to resolve. }
""" """
_loggers = [
'poetry.repositories.pypi_repository'
]
def handle(self): def handle(self):
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.puzzle import Solver from poetry.puzzle import Solver
......
...@@ -21,6 +21,10 @@ exist it will look for <comment>pyproject.toml</> and do the same. ...@@ -21,6 +21,10 @@ exist it will look for <comment>pyproject.toml</> and do the same.
<info>poetry install</info> <info>poetry install</info>
""" """
_loggers = [
'poetry.repositories.pypi_repository'
]
def handle(self): def handle(self):
from poetry.installation import Installer from poetry.installation import Installer
......
...@@ -14,6 +14,10 @@ the current directory, processes it, and locks the depdencies in the <comment>py ...@@ -14,6 +14,10 @@ the current directory, processes it, and locks the depdencies in the <comment>py
<info>poetry lock</info> <info>poetry lock</info>
""" """
_loggers = [
'poetry.repositories.pypi_repository'
]
def handle(self): def handle(self):
from poetry.installation import Installer from poetry.installation import Installer
......
...@@ -17,6 +17,10 @@ list of installed packages ...@@ -17,6 +17,10 @@ list of installed packages
<info>poetry remove</info>""" <info>poetry remove</info>"""
_loggers = [
'poetry.repositories.pypi_repository'
]
def handle(self): def handle(self):
from poetry.installation import Installer from poetry.installation import Installer
......
...@@ -12,6 +12,10 @@ class UpdateCommand(VenvCommand): ...@@ -12,6 +12,10 @@ class UpdateCommand(VenvCommand):
(implicitly enables --verbose). } (implicitly enables --verbose). }
""" """
_loggers = [
'poetry.repositories.pypi_repository'
]
def handle(self): def handle(self):
from poetry.installation import Installer from poetry.installation import Installer
......
...@@ -3,10 +3,10 @@ from .command import Command ...@@ -3,10 +3,10 @@ from .command import Command
class VenvCommand(Command): class VenvCommand(Command):
def __init__(self, name=None): def __init__(self):
self._venv = None self._venv = None
super(VenvCommand, self).__init__(name) super(VenvCommand, self).__init__()
def initialize(self, i, o): def initialize(self, i, o):
from poetry.utils.venv import Venv from poetry.utils.venv import Venv
......
...@@ -13,7 +13,6 @@ from .operations import Update ...@@ -13,7 +13,6 @@ from .operations import Update
from .operations.operation import Operation from .operations.operation import Operation
from .provider import Provider from .provider import Provider
from .ui import UI
class Solver: class Solver:
......
from cleo.styles import CleoStyle
from poetry.mixology.contracts import UI as BaseUI
class UI(BaseUI):
def __init__(self, io): # type: (CleoStyle) -> None
self._io = io
self._progress = None
super(UI, self).__init__(self._io.is_debug())
@property
def output(self):
return self._io
def before_resolution(self):
self._io.write('<info>Resolving dependencies</>')
if self.is_debugging():
self._io.new_line()
def indicate_progress(self):
if not self.is_debugging():
self._io.write('.')
def after_resolution(self):
self._io.new_line()
def debug(self, message, depth):
if self.is_debugging():
debug_info = str(message)
debug_info = '\n'.join([
'<comment>:{}:</> {}'.format(str(depth).rjust(4), s)
for s in debug_info.split('\n')
]) + '\n'
self.output.write(debug_info)
import logging
import os import os
import tarfile import tarfile
import zipfile import zipfile
...@@ -38,12 +39,16 @@ from poetry.version.markers import InvalidMarker ...@@ -38,12 +39,16 @@ from poetry.version.markers import InvalidMarker
from .repository import Repository from .repository import Repository
logger = logging.getLogger(__name__)
class PyPiRepository(Repository): class PyPiRepository(Repository):
def __init__(self, def __init__(self,
url='https://pypi.org/', url='https://pypi.org/',
disable_cache=False, disable_cache=False,
fallback=True): fallback=True):
self._name = 'PyPI'
self._url = url self._url = url
self._disable_cache = disable_cache self._disable_cache = disable_cache
self._fallback = fallback self._fallback = fallback
...@@ -91,6 +96,12 @@ class PyPiRepository(Repository): ...@@ -91,6 +96,12 @@ class PyPiRepository(Repository):
for version, release in info['releases'].items(): for version, release in info['releases'].items():
if not release: if not release:
# Bad release # Bad release
self._log(
'No release information found for {}-{}, skipping'.format(
name, version
),
level='debug'
)
continue continue
if ( if (
...@@ -102,6 +113,13 @@ class PyPiRepository(Repository): ...@@ -102,6 +113,13 @@ class PyPiRepository(Repository):
for version in versions: for version in versions:
packages.append(Package(name, version)) packages.append(Package(name, version))
self._log(
'{} packages found for {} {}'.format(
len(packages), name, str(constraint)
),
level='debug'
)
return packages return packages
def package(self, def package(self,
...@@ -125,6 +143,10 @@ class PyPiRepository(Repository): ...@@ -125,6 +143,10 @@ class PyPiRepository(Repository):
and '_fallback' not in release_info and '_fallback' not in release_info
): ):
# Force cache update # Force cache update
self._log(
'No dependencies found, downloading archives',
level='debug'
)
self._cache.forget('{}:{}'.format(name, version)) self._cache.forget('{}:{}'.format(name, version))
release_info = self.get_release_info(name, version) release_info = self.get_release_info(name, version)
...@@ -141,6 +163,13 @@ class PyPiRepository(Repository): ...@@ -141,6 +163,13 @@ class PyPiRepository(Repository):
dependency = dependency_from_pep_508(req) dependency = dependency_from_pep_508(req)
except ValueError: except ValueError:
# Likely unable to parse constraint so we skip it # Likely unable to parse constraint so we skip it
self._log(
'Invalid constraint ({}) found in {}-{} dependencies, '
'skipping'.format(
req, package.name, package.version
),
level='debug'
)
continue continue
if dependency.extras: if dependency.extras:
...@@ -438,3 +467,6 @@ class PyPiRepository(Repository): ...@@ -438,3 +467,6 @@ class PyPiRepository(Repository):
if requires_dist: if requires_dist:
return requires_dist return requires_dist
def _log(self, msg, level='info'):
getattr(logger, level)('{}: {}'.format(self._name, msg))
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