Hello community,

here is the log from the commit of package python-dephell for openSUSE:Factory 
checked in at 2020-07-06 16:30:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-dephell (Old)
 and      /work/SRC/openSUSE:Factory/.python-dephell.new.3060 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-dephell"

Mon Jul  6 16:30:02 2020 rev:13 rq:818851 version:0.8.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-dephell/python-dephell.changes    
2020-05-28 09:18:09.209080694 +0200
+++ /work/SRC/openSUSE:Factory/.python-dephell.new.3060/python-dephell.changes  
2020-07-06 16:32:23.199732494 +0200
@@ -1,0 +2,15 @@
+Sun Jul  5 10:18:13 UTC 2020 - Sebastian Wagner <sebix+novell....@sebix.at>
+
+- update to version 0.8.3:
+ - The only noticeable change is an ability to provide a custom CA bundle via 
--ca flag.
+ - Fewer network requests
+ - Fix envs filtering in `deps convert`
+ - test and fix `deps add`
+ - Fixes #425: `vendor download` doesn't do anything with private pypi
+ - fix path resolving 4 register
+ - Fix Converter.lock default value for subclasses
+ - Small fixes in requesting warehouse (simple and API)
+ - fix typo in examples
+- check: disable a failing test which depends on the (not packaged) docs.
+
+-------------------------------------------------------------------

Old:
----
  dephell-0.8.2.tar.gz

New:
----
  dephell-0.8.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-dephell.spec ++++++
--- /var/tmp/diff_new_pack.X3sToa/_old  2020-07-06 16:32:24.755737274 +0200
+++ /var/tmp/diff_new_pack.X3sToa/_new  2020-07-06 16:32:24.759737287 +0200
@@ -28,7 +28,7 @@
 %bcond_with test
 %endif
 Name:           python-dephell%{psuffix}
-Version:        0.8.2
+Version:        0.8.3
 Release:        0
 Summary:        Dependency resolution for Python
 License:        MIT
@@ -172,7 +172,8 @@
 %if %{with test}
 # Emulate Travis, which disables tests which expect a git repository
 export TRAVIS_OS_NAME=1
-%pytest --no-network
+# test_params_all_described requires the docs to be packaged 
https://github.com/dephell/dephell/pull/448
+%pytest --no-network -k 'not test_params_all_described'
 %endif
 
 %if ! %{with test}

++++++ dephell-0.8.2.tar.gz -> dephell-0.8.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/PKG-INFO new/dephell-0.8.3/PKG-INFO
--- old/dephell-0.8.2/PKG-INFO  1970-01-01 01:00:00.000000000 +0100
+++ new/dephell-0.8.3/PKG-INFO  1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dephell
-Version: 0.8.2
+Version: 0.8.3
 Summary: Dependency resolution for Python
 Project-URL: Homepage, https://dephell.org/
 Project-URL: Repository, https://github.com/dephell/dephell
@@ -317,6 +317,7 @@
 
 .. code-block:: bash
 
+   dephell self auth upload.pypi.org my-username my-password
    dephell project upload
 
 These are some of the most useful commands. See `documentation 
<https://dephell.org/docs/>`_ for more details.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/README.md new/dephell-0.8.3/README.md
--- old/dephell-0.8.2/README.md 2020-03-19 16:37:32.000000000 +0100
+++ new/dephell-0.8.3/README.md 2020-03-19 16:41:14.000000000 +0100
@@ -248,6 +248,7 @@
 Now, we can upload these packages to [PyPI](https://pypi.org/):
 
 ```bash
+dephell self auth upload.pypi.org my-username my-password
 dephell project upload
 ```
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/README.rst new/dephell-0.8.3/README.rst
--- old/dephell-0.8.2/README.rst        2020-03-19 16:38:17.000000000 +0100
+++ new/dephell-0.8.3/README.rst        2020-04-28 08:29:15.000000000 +0200
@@ -288,6 +288,7 @@
 
 .. code-block:: bash
 
+   dephell self auth upload.pypi.org my-username my-password
    dephell project upload
 
 These are some of the most useful commands. See `documentation 
<https://dephell.org/docs/>`_ for more details.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/__init__.py 
new/dephell-0.8.3/dephell/__init__.py
--- old/dephell-0.8.2/dephell/__init__.py       2020-03-19 16:37:51.000000000 
+0100
+++ new/dephell-0.8.3/dephell/__init__.py       2020-04-28 08:29:08.000000000 
+0200
@@ -13,6 +13,6 @@
 """
 
 
-__version__ = '0.8.2'
+__version__ = '0.8.3'
 __author__ = 'Gram (@orsinium)'
 __license__ = 'MIT'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/actions/_package.py 
new/dephell-0.8.3/dephell/actions/_package.py
--- old/dephell-0.8.2/dephell/actions/_package.py       2019-12-19 
12:08:27.000000000 +0100
+++ new/dephell-0.8.3/dephell/actions/_package.py       2020-04-27 
12:28:33.000000000 +0200
@@ -22,6 +22,5 @@
 
 
 def get_resolver(reqs: Iterable[str] = None) -> Resolver:
-    root = PIPConverter(lock=False).loads_resolver('\n'.join(reqs))
-    root.name = 'root'
-    return root
+    resolver = PIPConverter(lock=False).loads_resolver('\n'.join(reqs))
+    return resolver
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/cache.py 
new/dephell-0.8.3/dephell/cache.py
--- old/dephell-0.8.2/dephell/cache.py  2019-11-19 13:50:34.000000000 +0100
+++ new/dephell-0.8.3/dephell/cache.py  2020-04-22 14:17:27.000000000 +0200
@@ -16,7 +16,7 @@
     def __init__(self, *keys, ttl: int = -1):
         self.path = Path(config['cache']['path'], *keys)
         if self.ext:
-            self.path = self.path.with_suffix(self.ext)
+            self.path = self.path.with_name(self.path.name + self.ext)
         self.ttl = ttl
         self._check_ttl()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/commands/deps_add.py 
new/dephell-0.8.3/dephell/commands/deps_add.py
--- old/dephell-0.8.2/dephell/commands/deps_add.py      2019-12-19 
18:13:43.000000000 +0100
+++ new/dephell-0.8.3/dephell/commands/deps_add.py      2020-04-27 
12:28:33.000000000 +0200
@@ -38,6 +38,7 @@
         # get new deps
         new_resolver = get_resolver(reqs=self.args.name)
         new_root = new_resolver.graph._roots[0]
+        new_root.name = 'new-root'
 
         # set envs
         for dep in new_root.dependencies:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/commands/deps_convert.py 
new/dephell-0.8.3/dephell/commands/deps_convert.py
--- old/dephell-0.8.2/dephell/commands/deps_convert.py  2019-12-19 
18:13:43.000000000 +0100
+++ new/dephell-0.8.3/dephell/commands/deps_convert.py  2020-04-28 
08:23:30.000000000 +0200
@@ -66,15 +66,14 @@
 
         # filter out deps by `--envs`
         if self.config.get('envs'):
-            if len(resolver.graph._layers) == 1:
-                for root in resolver.graph._roots:
-                    for dep in root.dependencies:
-                        dep.applied = True
-                        resolver.graph.add(dep)
-                for root in resolver.graph._roots:
-                    root.applied = True
-            resolver.apply_envs(set(self.config['envs']))
-            resolver.graph._layers = resolver.graph._layers[:1]
+            if not should_be_resolved:
+                resolver.graph.fast_apply()
+            resolver.apply_envs(envs=set(self.config['envs']), 
deep=should_be_resolved)
+            # If it's not a lockfile, we should drop all dependencies except 
direct ones.
+            # While `fast_apply` doesn't produce such dependencies, we want to 
be sure
+            # that we can't ever have them in the output. Trust no one.
+            if not should_be_resolved:
+                resolver.graph._layers = resolver.graph._layers[:2]
 
         # dump
         self.logger.debug('dump dependencies...', extra=dict(
@@ -82,10 +81,10 @@
             path=self.config['to']['path'],
         ))
 
-        dumper_kwargs = {
-            'reqs': Requirement.from_graph(resolver.graph, lock=dumper.lock),
-            'project': resolver.graph.metainfo,
-        }
+        dumper_kwargs = dict(
+            reqs=Requirement.from_graph(resolver.graph, lock=dumper.lock),
+            project=resolver.graph.metainfo,
+        )
         if self.config['to']['path'] == 'stdout':
             print(dumper.dumps(**dumper_kwargs))
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/commands/deps_tree.py 
new/dephell-0.8.3/dephell/commands/deps_tree.py
--- old/dephell-0.8.2/dephell/commands/deps_tree.py     2019-12-19 
18:13:43.000000000 +0100
+++ new/dephell-0.8.3/dephell/commands/deps_tree.py     2020-04-27 
19:06:45.000000000 +0200
@@ -35,7 +35,10 @@
 
         if self.args.type == 'pretty':
             for dep in sorted(resolver.graph.get_layer(1)):
-                print('\n'.join(self._make_tree(dep)))
+                if not dep.applied:
+                    continue
+                content = '\n'.join(self._make_tree(dep))
+                print(self._colorize(content))
             return True
 
         if self.args.type == 'json':
@@ -63,14 +66,35 @@
 
     @classmethod
     def _make_tree(cls, dep, *, level: int = 0) -> List[str]:
-        lines = ['{level}- {name} [required: {constraint}, locked: {best}, 
latest: {latest}]'.format(
+        best = dep.group.best_release.version
+        latest = dep.group.best_release.version
+        if best == latest:
+            template = '{level}- {name} [required: `{constraint}`, latest: 
`{latest}`]'
+        else:
+            template = '{level}- {name} [required: `{constraint}`, locked: 
`{best}`, latest: `{latest}`]'
+        lines = [template.format(
             level='  ' * level,
             name=dep.name,
             constraint=str(dep.constraint) or '*',
-            best=str(dep.group.best_release.version),
-            latest=str(dep.groups.releases[0].version),
+            best=str(best),
+            latest=str(latest),
         )]
         deps = {dep.name: dep for dep in dep.dependencies}.values()  # drop 
duplicates
         for subdep in sorted(deps):
             lines.extend(cls._make_tree(subdep, level=level + 1))
         return lines
+
+    @staticmethod
+    def _colorize(content: str) -> str:
+        try:
+            import pygments
+            import pygments.lexers
+            import pygments.formatters
+        except ImportError:
+            return content
+        content = pygments.highlight(
+            code=content,
+            lexer=pygments.lexers.MarkdownLexer(),
+            formatter=pygments.formatters.TerminalFormatter(),
+        )
+        return content.strip()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/commands/project_register.py 
new/dephell-0.8.3/dephell/commands/project_register.py
--- old/dephell-0.8.2/dephell/commands/project_register.py      2020-03-16 
17:51:40.000000000 +0100
+++ new/dephell-0.8.3/dephell/commands/project_register.py      2020-04-27 
11:43:00.000000000 +0200
@@ -69,6 +69,7 @@
         return True
 
     def _register(self, project_path: Path, lib_path: Path = None) -> bool:
+        project_path = project_path.resolve()
         self._make_egg_info(
             project_path=project_path,
             from_format=self.config['from']['format'],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/config/builders.py 
new/dephell-0.8.3/dephell/config/builders.py
--- old/dephell-0.8.2/dephell/config/builders.py        2020-03-18 
10:58:30.000000000 +0100
+++ new/dephell-0.8.3/dephell/config/builders.py        2020-04-27 
19:06:45.000000000 +0200
@@ -88,6 +88,7 @@
 
     other_group.add_argument('--project', help='path to the current project')
     other_group.add_argument('--bin', help='path to the dir for installing 
scripts')
+    other_group.add_argument('--ca', help='path to CA_BUNDLE file for SSL 
verification.')
 
     other_group.add_argument('--envs', nargs='*', help='environments (main, 
dev) or extras to install')
     other_group.add_argument('--tests', nargs='*', help='paths to test files')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/config/scheme.py 
new/dephell-0.8.3/dephell/config/scheme.py
--- old/dephell-0.8.2/dephell/config/scheme.py  2020-03-18 10:58:30.000000000 
+0100
+++ new/dephell-0.8.3/dephell/config/scheme.py  2020-04-27 19:06:45.000000000 
+0200
@@ -115,6 +115,7 @@
     ),
     'project':      dict(type='string', required=True),
     'bin':          dict(type='string', required=True),
+    'ca':           dict(type='string', required=False),
     'envs':         dict(type='list', schema=dict(type='string'), 
required=False, empty=False),
     'tests':        dict(type='list', schema=dict(type='string'), 
required=True),
     'versioning':   dict(type='string', required=True, allowed=get_schemes()),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/controllers/_graph.py 
new/dephell-0.8.3/dephell/controllers/_graph.py
--- old/dephell-0.8.2/dephell/controllers/_graph.py     2019-12-30 
11:44:13.000000000 +0100
+++ new/dephell-0.8.3/dephell/controllers/_graph.py     2020-04-27 
19:06:45.000000000 +0200
@@ -118,7 +118,7 @@
         raise KeyError('cannot find any parent for dependency: ' + 
str(dep.name))
 
     def get_leafs(self, level: Optional[int] = None) -> tuple:
-        """Get deps that isn't applied yet
+        """Get deps that aren't applied yet
         """
         layers = self._layers
         if level is not None:
@@ -193,6 +193,19 @@
             ))
         return parents
 
+    def fast_apply(self) -> bool:
+        """Apply only the first layer.
+        """
+        if len(self._layers) != 1:
+            return False
+        for root in self._roots:
+            for dep in root.dependencies:
+                dep.applied = True
+                self.add(dep)
+        for root in self._roots:
+            root.applied = True
+        return True
+
     def draw(self, path: str = '.dephell_report', suffix: str = '') -> None:
         dot = graphviz.Digraph(
             name=self._roots[0].name + suffix,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/controllers/_resolver.py 
new/dephell-0.8.3/dephell/controllers/_resolver.py
--- old/dephell-0.8.2/dephell/controllers/_resolver.py  2020-01-27 
18:20:00.000000000 +0100
+++ new/dephell-0.8.3/dephell/controllers/_resolver.py  2020-04-28 
08:23:30.000000000 +0200
@@ -22,9 +22,9 @@
         self.graph = graph
         self.mutator = mutator
 
-    def apply(self, parent):
+    def apply(self, parent, recursive: bool = False):
         """
-        Returns conflicting (incompatible) dependency
+        Returns conflicting (incompatible) dependency.
         """
         for new_dep in parent.dependencies:
             other_dep = self.graph.get(new_dep.name)
@@ -45,6 +45,10 @@
                     other_dep += new_dep
                 except TypeError:   # conflict happened
                     return other_dep
+                # `recursive` used only in re-application of dependencies,
+                # when the graph already was built before.
+                if recursive:
+                    self.apply(other_dep, recursive=True)
             # check
             if not other_dep.compat:
                 return other_dep
@@ -133,7 +137,12 @@
                 self.unapply(dep)
                 dep.group = group
 
-    def apply_envs(self, envs: set) -> None:
+    def apply_envs(self, envs: set, deep: bool = True) -> None:
+        """Filter out dependencies from the graph by the given envs.
+
+        deep: Helps to avoid fetching dependencies (hence the network 
requests).
+            Set it to False for not resolved graph to make filtering faster.
+        """
         if not any(root.dependencies for root in self.graph.get_layer(0)):
             logger.debug('no dependencies, nothing to filter')
             return
@@ -152,18 +161,22 @@
             # and ignored in Requirement.from_graph.
             # It's bad behavior because deps of this dep can be required for 
other
             # deps that won't be unapplied.
-            self.unapply(dep, soft=True)
+            if deep:
+                self.unapply(dep, soft=True)
             dep.applied = False
 
         # Some child deps can be unapplied from other child deps, but we need 
them.
         # For example, if we need A, but don't need B, and A and B depends on 
C,
-        # then C will be unapplied from B. Let's return B in the graph by 
re-applying A.
+        # then C will be unapplied from B. Let's return B in the graph by 
reapplying A.
         for dep in self.graph:
             if not dep.applied:
                 continue
             if not (dep.envs | dep.inherited_envs) & envs:
                 continue
-            self.apply(dep)
+            logger.debug('reapply', extra=dict(dep=dep.name, envs=envs))
+            if deep:
+                self.apply(dep, recursive=True)
+            dep.applied = True
 
     def apply_markers(self, python) -> None:
         implementation = python.implementation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/conda.py 
new/dephell-0.8.3/dephell/converters/conda.py
--- old/dephell-0.8.2/dephell/converters/conda.py       2019-11-19 
13:50:34.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/conda.py       2020-04-27 
11:43:00.000000000 +0200
@@ -4,6 +4,7 @@
 from typing import Optional
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from dephell_specifier import RangeSpecifier
 from packaging.utils import canonicalize_name
@@ -16,6 +17,7 @@
 from .base import BaseConverter
 
 
+@attr.s()
 class CondaConverter(BaseConverter):
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if isinstance(path, str):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/egginfo.py 
new/dephell-0.8.3/dephell/converters/egginfo.py
--- old/dephell-0.8.2/dephell/converters/egginfo.py     2020-03-19 
13:33:18.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/egginfo.py     2020-04-27 
11:43:00.000000000 +0200
@@ -2,14 +2,16 @@
 from collections import defaultdict
 from email.parser import Parser
 from itertools import chain
+from logging import getLogger
 from pathlib import Path
 from typing import Dict, Optional
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from dephell_links import parse_link
 from dephell_markers import Markers
-from packaging.requirements import Requirement as PackagingRequirement
+from packaging.requirements import InvalidRequirement, Requirement as 
PackagingRequirement
 
 # app
 from ..constants import DOWNLOAD_FIELD, HOMEPAGE_FIELD
@@ -20,6 +22,7 @@
 
 
 class _Reader:
+    logger = getLogger('dephell.converters.egginfo')
 
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if isinstance(path, str):
@@ -151,7 +154,15 @@
         # dependencies
         deps = []
         for req in cls._get_list(info, 'Requires-Dist'):
-            req = PackagingRequirement(req)
+            try:
+                req = PackagingRequirement(req)
+            except InvalidRequirement:
+                cls.logger.warning('invalid requirement', extra=dict(
+                    requirement=req,
+                    package_name=root.name,
+                    package_version=root.version,
+                ))
+                continue
             deps.extend(DependencyMaker.from_requirement(
                 source=root,
                 req=req,
@@ -391,9 +402,10 @@
         return line
 
 
+@attr.s()
 class EggInfoConverter(_Reader, _Writer, BaseConverter):
     """
     PEP-314, PEP-345, PEP-566
     https://packaging.python.org/specifications/core-metadata/
     """
-    lock = False
+    lock = attr.ib(type=bool, default=False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/flit.py 
new/dephell-0.8.3/dephell/converters/flit.py
--- old/dephell-0.8.2/dephell/converters/flit.py        2020-03-19 
13:33:18.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/flit.py        2020-04-27 
11:43:00.000000000 +0200
@@ -4,6 +4,7 @@
 from typing import Optional
 
 # external
+import attr
 import tomlkit
 from dephell_discover import Root as PackageRoot
 from dephell_specifier import RangeSpecifier
@@ -17,8 +18,9 @@
 from .egginfo import EggInfoConverter
 
 
+@attr.s()
 class FlitConverter(BaseConverter):
-    lock = False
+    lock = attr.ib(type=bool, default=False)
 
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if not content:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/imports.py 
new/dephell-0.8.3/dephell/converters/imports.py
--- old/dephell-0.8.2/dephell/converters/imports.py     2019-12-17 
15:33:35.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/imports.py     2020-04-27 
11:43:00.000000000 +0200
@@ -4,6 +4,7 @@
 from typing import Dict, List, Set
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 
 # app
@@ -23,8 +24,9 @@
 CACHE_TTL = 3600 * 24 * 30  # 30 days
 
 
+@attr.s()
 class ImportsConverter(BaseConverter):
-    lock = True
+    lock = attr.ib(type=bool, default=True)
 
     def can_parse(self, path: Path, content: str = None) -> bool:
         if isinstance(path, str):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/installed.py 
new/dephell-0.8.3/dephell/converters/installed.py
--- old/dephell-0.8.2/dephell/converters/installed.py   2019-12-27 
14:56:27.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/installed.py   2020-04-27 
11:43:00.000000000 +0200
@@ -4,6 +4,7 @@
 from typing import Iterable, Union
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from packaging.utils import canonicalize_name
 
@@ -15,8 +16,9 @@
 from .wheel import WheelConverter
 
 
+@attr.s()
 class InstalledConverter(BaseConverter):
-    lock = True
+    lock = attr.ib(type=bool, default=True)
 
     _blacklist = {
         'pkg-resources',        # 
https://bugs.launchpad.net/ubuntu/+source/python-pip/+bug/1635463
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/pip.py 
new/dephell-0.8.3/dephell/converters/pip.py
--- old/dephell-0.8.2/dephell/converters/pip.py 2020-03-19 09:29:18.000000000 
+0100
+++ new/dephell-0.8.3/dephell/converters/pip.py 2020-04-27 11:43:00.000000000 
+0200
@@ -5,6 +5,7 @@
 from typing import Optional
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from dephell_links import DirLink, FileLink
 from pip._internal.req import parse_requirements
@@ -33,6 +34,7 @@
         from pip._internal.network.session import PipSession
 
 
+@attr.s()
 class PIPConverter(BaseConverter):
     sep = ' \\\n  '
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/pipfile.py 
new/dephell-0.8.3/dephell/converters/pipfile.py
--- old/dephell-0.8.2/dephell/converters/pipfile.py     2019-12-17 
15:33:35.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/pipfile.py     2020-04-27 
11:43:00.000000000 +0200
@@ -3,6 +3,7 @@
 from typing import List, Optional
 
 # external
+import attr
 import tomlkit
 from dephell_discover import Root as PackageRoot
 from dephell_pythons import Pythons
@@ -19,8 +20,10 @@
 VCS_LIST = ('git', 'svn', 'hg', 'bzr')
 
 
+@attr.s()
 class PIPFileConverter(BaseConverter):
-    lock = False
+    lock = attr.ib(type=bool, default=False)
+
     fields = (
         'version', 'editable', 'extras', 'markers',
         'ref', 'vcs', 'index', 'hashes',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/pipfilelock.py 
new/dephell-0.8.3/dephell/converters/pipfilelock.py
--- old/dephell-0.8.2/dephell/converters/pipfilelock.py 2019-12-17 
15:33:35.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/pipfilelock.py 2020-04-27 
11:43:00.000000000 +0200
@@ -6,6 +6,7 @@
 from typing import Optional
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from dephell_pythons import Pythons
 from dephell_specifier import RangeSpecifier
@@ -22,8 +23,9 @@
 # https://github.com/pypa/pipfile/blob/master/pipfile/api.py
 
 
+@attr.s()
 class PIPFileLockConverter(PIPFileConverter):
-    lock = True
+    lock = attr.ib(type=bool, default=True)
 
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if isinstance(path, str):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/poetry.py 
new/dephell-0.8.3/dephell/converters/poetry.py
--- old/dephell-0.8.2/dephell/converters/poetry.py      2020-03-18 
14:11:02.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/poetry.py      2020-04-27 
19:06:45.000000000 +0200
@@ -4,6 +4,7 @@
 from typing import List, Optional
 
 # external
+import attr
 import tomlkit
 from dephell_discover import Root as PackageRoot
 from dephell_specifier import RangeSpecifier
@@ -16,8 +17,10 @@
 from .base import BaseConverter
 
 
+@attr.s()
 class PoetryConverter(BaseConverter):
-    lock = False
+    lock = attr.ib(type=bool, default=False)
+
     fields = (
         'version', 'python', 'platform', 'allows-prereleases',
         'optional', 'extras', 'develop',
@@ -30,7 +33,7 @@
         if isinstance(path, str):
             path = Path(path)
         if content:
-            return '[tool.poetry]' in content
+            return '[tool.poetry]' in content or '[tool.poetry.' in content
         return path.name in ('poetry.toml', 'pyproject.toml')
 
     def loads(self, content) -> RootDependency:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/poetrylock.py 
new/dephell-0.8.3/dephell/converters/poetrylock.py
--- old/dephell-0.8.2/dephell/converters/poetrylock.py  2019-11-19 
13:50:34.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/poetrylock.py  2020-04-27 
11:43:00.000000000 +0200
@@ -4,6 +4,7 @@
 from typing import List, Optional
 
 # external
+import attr
 import tomlkit
 from dephell_discover import Root as PackageRoot
 from dephell_links import DirLink
@@ -16,8 +17,10 @@
 from .base import BaseConverter
 
 
+@attr.s()
 class PoetryLockConverter(BaseConverter):
-    lock = True
+    lock = attr.ib(type=bool, default=True)
+
     fields = (
         'category', 'description', 'name', 'marker', 'optional',
         'python-versions', 'version', 'dependencies',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/pyproject.py 
new/dephell-0.8.3/dephell/converters/pyproject.py
--- old/dephell-0.8.2/dephell/converters/pyproject.py   2019-12-19 
18:13:43.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/pyproject.py   2020-04-27 
11:43:00.000000000 +0200
@@ -3,6 +3,7 @@
 from typing import Optional
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from packaging.requirements import Requirement
 from tomlkit import document, dumps, parse
@@ -13,8 +14,9 @@
 from .base import BaseConverter
 
 
+@attr.s()
 class PyProjectConverter(BaseConverter):
-    lock = False
+    lock = attr.ib(type=bool, default=False)
 
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if isinstance(path, str):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/sdist.py 
new/dephell-0.8.3/dephell/converters/sdist.py
--- old/dephell-0.8.2/dephell/converters/sdist.py       2020-01-23 
19:49:00.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/sdist.py       2020-04-27 
11:43:00.000000000 +0200
@@ -25,7 +25,7 @@
     # ratio of tests and project size after which tests will be excluded from 
sdist
     ratio = attr.ib(type=Optional[float], default=None)
 
-    lock = False
+    lock = attr.ib(type=bool, default=False)
 
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if content is not None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/setuppy.py 
new/dephell-0.8.3/dephell/converters/setuppy.py
--- old/dephell-0.8.2/dephell/converters/setuppy.py     2020-03-19 
13:33:18.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/setuppy.py     2020-04-27 
11:43:00.000000000 +0200
@@ -6,6 +6,7 @@
 from typing import Optional
 
 # external
+import attr
 from dephell_discover import Root as PackageRoot
 from dephell_links import DirLink, FileLink, URLLink, VCSLink, parse_link
 from dephell_setuptools import read_setup
@@ -54,8 +55,9 @@
 """
 
 
+@attr.s()
 class SetupPyConverter(BaseConverter):
-    lock = False
+    lock = attr.ib(type=bool, default=False)
 
     def can_parse(self, path: Path, content: Optional[str] = None) -> bool:
         if isinstance(path, str):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/converters/wheel.py 
new/dephell-0.8.3/dephell/converters/wheel.py
--- old/dephell-0.8.2/dephell/converters/wheel.py       2019-12-30 
11:44:13.000000000 +0100
+++ new/dephell-0.8.3/dephell/converters/wheel.py       2020-04-27 
11:43:00.000000000 +0200
@@ -8,6 +8,7 @@
 from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
 
 # external
+import attr
 from dephell_archive import ArchivePath
 from dephell_discover import Root as PackageRoot
 
@@ -207,9 +208,10 @@
         return content + ('\n{},,'.format(path))
 
 
+@attr.s()
 class WheelConverter(_Reader, _Writer, BaseConverter):
     """
     PEP-0427
     https://www.python.org/dev/peps/pep-0427/
     """
-    lock = False
+    lock = attr.ib(type=bool, default=False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/models/dependency.py 
new/dephell-0.8.3/dephell/models/dependency.py
--- old/dephell-0.8.2/dephell/models/dependency.py      2019-12-19 
18:13:43.000000000 +0100
+++ new/dephell-0.8.3/dephell/models/dependency.py      2020-04-22 
14:17:27.000000000 +0200
@@ -193,7 +193,7 @@
 
         marker = Markers(str(self.marker))
         if self.envs - {'main'}:
-            extra_markers = {'extra == "{}"'.format(env) for env in self.envs 
- {'main'}}
+            extra_markers = {'extra == {!r}'.format(env) for env in self.envs 
- {'main'}}
             marker &= Markers(' or '.join(extra_markers))
         if marker:
             result += '; ' + str(marker)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/models/groups.py 
new/dephell-0.8.3/dephell/models/groups.py
--- old/dephell-0.8.2/dephell/models/groups.py  2019-12-17 15:33:35.000000000 
+0100
+++ new/dephell-0.8.3/dephell/models/groups.py  2020-04-28 08:23:30.000000000 
+0200
@@ -12,6 +12,9 @@
 
 
 loop = asyncio.get_event_loop()
+# How many latest releases should go in their own group.
+# Lesser value -> less network requests per package and more mutations.
+ONE_GROUP_RELEASES = 1
 
 
 def get_key(release) -> str:
@@ -164,12 +167,15 @@
             self.actualize(group=group)
             yield group
 
-        # load first group
+        # load the first two groups
         if not self._loaded_groups:
-            release = self.releases[0]
-            self._load_release_deps(release)
-            self._loaded_releases_count += 1
-            yield self._make_group([release])
+            for i in range(ONE_GROUP_RELEASES):
+                if len(self.releases) <= i:
+                    return
+                release = self.releases[i]
+                self._load_release_deps(release)
+                self._loaded_releases_count += 1
+                yield self._make_group([release])
 
         # load new groups
         prev_key = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/models/requirement.py 
new/dephell-0.8.3/dephell/models/requirement.py
--- old/dephell-0.8.2/dephell/models/requirement.py     2020-01-27 
18:20:00.000000000 +0100
+++ new/dephell-0.8.3/dephell/models/requirement.py     2020-04-27 
19:06:45.000000000 +0200
@@ -29,16 +29,15 @@
         extras = defaultdict(list)
         roots = [root.name for root in graph.get_layer(0)]
 
-        # if roots wasn't applied then apply them
-        if len(graph._layers) == 1:
-            for root in graph._roots:
-                for dep in root.dependencies:
-                    graph.add(dep)
+        # if roots weren't applied, apply them
+        graph.fast_apply()
 
         # get all nodes
         for layer in reversed(graph._layers[1:]):  # skip roots
             for dep in sorted(layer):
-                if dep.constraint.empty:
+                if not dep.used:
+                    continue
+                if not dep.applied:
                     continue
                 if dep.extra is None:
                     req = cls(dep=dep, lock=lock, roots=roots)
@@ -219,7 +218,10 @@
 
     @staticmethod
     def _get_comparable_dict(dep) -> dict:
-        excluded = {'constraint', 'repo', 'link', 'marker', 'license', 
'inherited_envs', 'locations'}
+        excluded = {
+            'constraint', 'repo', 'link', 'marker', 'license',
+            'inherited_envs', 'locations', 'applied',
+        }
         result = attr.asdict(dep, recurse=True, filter=lambda x, _: x.name not 
in excluded)
         result['constraint'] = str(dep.constraint)
         if dep.marker:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell/networking.py 
new/dephell-0.8.3/dephell/networking.py
--- old/dephell-0.8.2/dephell/networking.py     2019-12-17 15:33:35.000000000 
+0100
+++ new/dephell-0.8.3/dephell/networking.py     2020-04-27 19:06:45.000000000 
+0200
@@ -1,27 +1,38 @@
 # built-in
+from functools import partial, update_wrapper
+from logging import getLogger
 from ssl import create_default_context
+from time import sleep
 
 # external
 import certifi
 import requests
-from aiohttp import ClientSession, TCPConnector
+from aiohttp import ClientError, ClientSession, TCPConnector
 
 # app
 from . import __version__
+from .config import config
 
 
 USER_AGENT = 'DepHell/{version}'.format(version=__version__)
+logger = getLogger('dephell.networking')
 
 
 def aiohttp_session(*, auth=None, **kwargs):
-    headers = dict()
+    headers = {'User-Agent': USER_AGENT}
     if auth:
         headers['Authorization'] = auth.encode()
-    ssl_context = create_default_context(cafile=certifi.where())
+
+    # setup SSL
+    cafile = config.get('ca')
+    if not cafile:
+        cafile = certifi.where()
+    ssl_context = create_default_context(cafile=cafile)
     try:
         connector = TCPConnector(ssl=ssl_context)
     except TypeError:
         connector = TCPConnector(ssl_context=ssl_context)
+
     return ClientSession(headers=headers, connector=connector, **kwargs)
 
 
@@ -29,10 +40,37 @@
     session = requests.Session()
     if auth:
         session.auth = auth
+
+    # setup SSL
+    cafile = config.get('ca')
+    if cafile:
+        session.verify = cafile
+
+    # set headers
     if headers is None:
         headers = dict()
     headers.setdefault('User-Agent', USER_AGENT)
     session.headers = headers
     if kwargs:
         session.__dict__.update(kwargs)
+
     return session
+
+
+def aiohttp_repeat(func=None, *, count: int = 4):
+    if func is None:
+        return partial(func, count=count)
+
+    async def wrapper(*args, **kwargs):
+        for pause in range(1, count + 1):
+            try:
+                return await func(*args, **kwargs)
+            except ClientError:
+                if pause == count:
+                    raise
+                logger.debug('aiohttp payload error, repeating...', 
exc_info=True)
+                sleep(pause)
+        raise RuntimeError('unreachable')
+
+    wrapper = update_wrapper(wrapper=wrapper, wrapped=func)
+    return wrapper
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dephell-0.8.2/dephell/repositories/_warehouse/_api.py 
new/dephell-0.8.3/dephell/repositories/_warehouse/_api.py
--- old/dephell-0.8.2/dephell/repositories/_warehouse/_api.py   2019-12-17 
15:33:35.000000000 +0100
+++ new/dephell-0.8.3/dephell/repositories/_warehouse/_api.py   2020-04-28 
08:23:30.000000000 +0200
@@ -38,6 +38,8 @@
     'summary',
     'version',
 }
+# Do not download too big files
+SIZE_LIMIT = 2 * 1024 * 1024  # 2 Mb
 
 
 @attr.s()
@@ -284,8 +286,11 @@
 
         for converter, checker in rules:
             for file_info in files_info:
+                if file_info.get('size') > SIZE_LIMIT:
+                    continue
                 if not checker(file_info):
                     continue
+                logger.debug('downloading file...', 
extra=dict(url=file_info['url']))
                 try:
                     return await self._download_and_parse(
                         url=file_info['url'],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dephell-0.8.2/dephell/repositories/_warehouse/_base.py 
new/dephell-0.8.3/dephell/repositories/_warehouse/_base.py
--- old/dephell-0.8.2/dephell/repositories/_warehouse/_base.py  2019-12-17 
15:33:35.000000000 +0100
+++ new/dephell-0.8.3/dephell/repositories/_warehouse/_base.py  2020-04-27 
19:44:05.000000000 +0200
@@ -13,7 +13,7 @@
 # app
 from ...cached_property import cached_property
 from ...constants import WAREHOUSE_DOMAINS
-from ...networking import aiohttp_session
+from ...networking import aiohttp_repeat, aiohttp_session
 from ..base import Interface
 
 
@@ -99,22 +99,23 @@
                     ))
 
             try:
-                dep_extra = req.marker and Markers(req.marker).extra
+                dep_extras = req.marker and 
Markers(req.marker).get_strings('extra')
             except ValueError:  # unsupported operation for version marker 
python_version: in
-                dep_extra = None
+                dep_extras = set()
 
             # it's not extra and we want not extra too
-            if dep_extra is None and extra is None:
+            if not dep_extras and extra is None:
                 result.append(req)
                 continue
             # it's extra, but we want not the extra
             # or it's not the extra, but we want extra.
-            if dep_extra is None or extra is None:
+            if not dep_extras or extra is None:
                 continue
             # it's extra and we want this extra
-            elif dep_extra == extra:
-                result.append(req)
-                continue
+            for dep_extra in dep_extras:
+                if dep_extra == extra:
+                    result.append(req)
+                    break
 
         return tuple(result)
 
@@ -136,6 +137,7 @@
                         deps.append(str(dep))
             return tuple(deps)
 
+    @aiohttp_repeat
     async def _download(self, *, url: str, path: Path) -> None:
         async with aiohttp_session(auth=self.auth) as session:
             async with session.get(url) as response:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dephell-0.8.2/dephell/repositories/_warehouse/_simple.py 
new/dephell-0.8.3/dephell/repositories/_warehouse/_simple.py
--- old/dephell-0.8.2/dephell/repositories/_warehouse/_simple.py        
2020-03-18 10:58:30.000000000 +0100
+++ new/dephell-0.8.3/dephell/repositories/_warehouse/_simple.py        
2020-04-27 12:28:33.000000000 +0200
@@ -53,9 +53,19 @@
         releases_info = dict()
         for link in links:
             name, version = self._parse_name(link['name'])
-            if canonicalize_name(name) != dep.name:
+            if canonicalize_name(name) != canonicalize_name(dep.base_name):
+                logger.warning('bad dist name', extra=dict(
+                    dist_name=link['name'],
+                    package_name=dep.base_name,
+                    reason='package name does not match',
+                ))
                 continue
             if not version:
+                logger.warning('bad dist name', extra=dict(
+                    dist_name=link['name'],
+                    package_name=dep.base_name,
+                    reason='no version specified',
+                ))
                 continue
 
             if version not in releases_info:
@@ -112,6 +122,9 @@
         raise NotImplementedError
 
     async def download(self, name: str, version: str, path: Path) -> bool:
+        if not isinstance(version, str):
+            version = str(version)
+
         links = self._get_links(name=name)
         good_links = []
         for link in links:
@@ -142,11 +155,13 @@
             ttl=config['cache']['ttl'],
         )
         links = cache.load()
-        if links:
-            return links
+        if links is not None:
+            yield from links
+            return
 
         dep_url = posixpath.join(self.url, quote(name)) + '/'
         with requests_session() as session:
+            logger.debug('getting dep info from simple repo', 
extra=dict(url=dep_url))
             response = session.get(dep_url, auth=self.auth)
         if response.status_code == 404:
             raise PackageNotFoundError(package=name, url=dep_url)
@@ -164,17 +179,19 @@
 
             python = tag.get('data-requires-python')
             fragment = parse_qs(parsed.fragment)
-            yield dict(
+            link = dict(
                 url=urljoin(dep_url, link),
                 name=parsed.path.strip('/').split('/')[-1],
                 python=html.unescape(python) if python else '*',
                 digest=fragment['sha256'][0] if 'sha256' in fragment else None,
             )
+            links.append(link)
+            yield link
 
         cache.dump(links)
         return links
 
-    async def _get_deps_from_links(self, name, version):
+    async def _get_deps_from_links(self, name: str, version):
         from ...converters import SDistConverter, WheelConverter
 
         links = self._get_links(name=name)
@@ -183,7 +200,7 @@
             link_name, link_version = self._parse_name(link['name'])
             if canonicalize_name(link_name) != name:
                 continue
-            if link_version != version:
+            if link_version != str(version):
                 continue
             good_links.append(link)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/dephell.egg-info/PKG-INFO 
new/dephell-0.8.3/dephell.egg-info/PKG-INFO
--- old/dephell-0.8.2/dephell.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 
+0100
+++ new/dephell-0.8.3/dephell.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: dephell
-Version: 0.8.2
+Version: 0.8.3
 Summary: Dependency resolution for Python
 Project-URL: Homepage, https://dephell.org/
 Project-URL: Repository, https://github.com/dephell/dephell
@@ -317,6 +317,7 @@
 
 .. code-block:: bash
 
+   dephell self auth upload.pypi.org my-username my-password
    dephell project upload
 
 These are some of the most useful commands. See `documentation 
<https://dephell.org/docs/>`_ for more details.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/setup.cfg new/dephell-0.8.3/setup.cfg
--- old/dephell-0.8.2/setup.cfg 2019-11-19 13:50:34.000000000 +0100
+++ new/dephell-0.8.3/setup.cfg 2020-04-22 14:17:27.000000000 +0200
@@ -11,3 +11,8 @@
     .pytest_cache
     build
     setup.py
+
+[tool:pytest]
+addopts = --strict-markers
+markers =
+    allow_hosts: allow network requests to specified hostnames.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/setup.py new/dephell-0.8.3/setup.py
--- old/dephell-0.8.2/setup.py  2020-03-19 16:38:16.000000000 +0100
+++ new/dephell-0.8.3/setup.py  2020-04-28 08:29:15.000000000 +0200
@@ -21,7 +21,7 @@
 setup(
     long_description=readme,
     name='dephell',
-    version='0.8.2',
+    version='0.8.3',
     description='Dependency resolution for Python',
     python_requires='>=3.6',
     project_urls={
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/tests/helpers.py 
new/dephell-0.8.3/tests/helpers.py
--- old/dephell-0.8.2/tests/helpers.py  2019-12-17 15:33:35.000000000 +0100
+++ new/dephell-0.8.3/tests/helpers.py  2020-04-27 19:06:45.000000000 +0200
@@ -71,7 +71,15 @@
     return root_dep
 
 
-def check(root, resolved=True, missed=None, **deps):
+def set_envs(root: RootDependency, dep_name: str, envs: str) -> None:
+    for dep in root.dependencies:
+        if dep.name == dep_name:
+            dep.envs = envs
+            return
+    raise RuntimeError('cannot find dep')
+
+
+def check(root: RootDependency, resolved: bool = True, missed=None, envs: set 
= None, **deps):
     resolver = Resolver(
         graph=Graph(root),
         mutator=Mutator(),
@@ -82,6 +90,9 @@
     ):
         result = resolver.resolve(debug=True, silent=True)
 
+    if envs is not None:
+        resolver.apply_envs(envs=envs)
+
     reqs = Requirement.from_graph(resolver.graph, lock=True)
     reqs = {req.name: req for req in reqs}
 
@@ -104,4 +115,4 @@
 
     if missed:
         for name in missed:
-            assert name not in reqs
+            assert name not in reqs, '{} must be missed but it is 
not'.format(name)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/tests/requirements/setup.py 
new/dephell-0.8.3/tests/requirements/setup.py
--- old/dephell-0.8.2/tests/requirements/setup.py       2020-03-19 
13:33:18.000000000 +0100
+++ new/dephell-0.8.3/tests/requirements/setup.py       2020-04-27 
11:43:00.000000000 +0200
@@ -5,7 +5,6 @@
 # external
 from setuptools import setup
 
-
 here = path.abspath(path.dirname(__file__))
 root = path.dirname(path.dirname(here))
 with open(path.join(root, 'README.md'), encoding='utf-8') as f:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/tests/test_commands/test_deps_add.py 
new/dephell-0.8.3/tests/test_commands/test_deps_add.py
--- old/dephell-0.8.2/tests/test_commands/test_deps_add.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/dephell-0.8.3/tests/test_commands/test_deps_add.py      2020-04-27 
12:28:33.000000000 +0200
@@ -0,0 +1,33 @@
+# built-in
+from pathlib import Path
+
+# external
+import pytest
+
+# project
+from dephell.commands import DepsAddCommand
+from dephell.config import Config
+
+
+@pytest.mark.allow_hosts()
+def test_deps_add_command(temp_path: Path, capsys):
+    reqs_path = temp_path / 'requirements.txt'
+    reqs_path.write_text('six==1.12.0')
+
+    config = Config()
+    config.attach({
+        'level': 'WARNING',
+        'silent': True,
+        'nocolors': True,
+        'from': dict(format='pip', path=reqs_path),
+    })
+
+    command = DepsAddCommand(argv=['jinja2==2.0'], config=config)
+    result = command()
+
+    captured = capsys.readouterr()
+    print(captured.err)
+    print(captured.out)
+    assert result is True
+
+    assert set(reqs_path.read_text().split()) == {'six==1.12.0', 'jinja2==2.0'}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/tests/test_controllers/test_safety.py 
new/dephell-0.8.3/tests/test_controllers/test_safety.py
--- old/dephell-0.8.2/tests/test_controllers/test_safety.py     2019-12-17 
15:33:35.000000000 +0100
+++ new/dephell-0.8.3/tests/test_controllers/test_safety.py     2020-04-22 
14:17:27.000000000 +0200
@@ -9,5 +9,5 @@
 def test_safety():
     safety = Safety()
     vulns = safety.get('django', '1.11.0')
-    assert len(vulns) == 5
+    assert len(vulns) == 13
     assert {vuln.name for vuln in vulns} == {'django'}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/dephell-0.8.2/tests/test_docs.py 
new/dephell-0.8.3/tests/test_docs.py
--- old/dephell-0.8.2/tests/test_docs.py        1970-01-01 01:00:00.000000000 
+0100
+++ new/dephell-0.8.3/tests/test_docs.py        2020-04-28 08:23:30.000000000 
+0200
@@ -0,0 +1,15 @@
+# built-in
+from pathlib import Path
+
+# project
+from dephell.config.scheme import SCHEME
+
+
+def test_params_all_described():
+    undocumented = {'and', 'auth', 'vars', 'command'}
+    path = Path(__file__).parent.parent / 'docs' / 'params.md'
+    content = path.read_text()
+    for key in SCHEME:
+        if key in undocumented:
+            continue
+        assert '`--' + key in content
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dephell-0.8.2/tests/test_repositories/test_warehouse_api.py 
new/dephell-0.8.3/tests/test_repositories/test_warehouse_api.py
--- old/dephell-0.8.2/tests/test_repositories/test_warehouse_api.py     
2019-12-17 15:33:35.000000000 +0100
+++ new/dephell-0.8.3/tests/test_repositories/test_warehouse_api.py     
2020-04-27 12:28:33.000000000 +0200
@@ -5,6 +5,7 @@
 
 # external
 import pytest
+from packaging.version import Version
 
 # project
 from dephell.constants import DEFAULT_WAREHOUSE
@@ -110,8 +111,9 @@
     assert client._default_headers['authorization'] == 'Basic Z3JhbTp0ZXN0'
 
 
+@pytest.mark.parametrize('version', ['0.1.2', Version('0.1.2')])
 def test_download(asyncio_mock, temp_cache, fixtures_path: Path, temp_path: 
Path,
-                  requirements_path: Path):
+                  requirements_path: Path, version):
     pypi_url = 'https://custom.pypi.org/pypi/'
     json_response = (fixtures_path / 'warehouse-api-release.json').read_text()
     json_content = json.loads(json_response)
@@ -123,7 +125,7 @@
     asyncio_mock.get(file_url, body=file_content)
 
     repo = WarehouseAPIRepo(name='pypi', url=pypi_url)
-    coroutine = repo.download(name='dephell-shells', version='0.1.2', 
path=temp_path)
+    coroutine = repo.download(name='dephell-shells', version=version, 
path=temp_path)
     result = loop.run_until_complete(asyncio.gather(coroutine))[0]
     assert result is True
     assert (temp_path / file_name).exists()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dephell-0.8.2/tests/test_repositories/test_warehouse_simple.py 
new/dephell-0.8.3/tests/test_repositories/test_warehouse_simple.py
--- old/dephell-0.8.2/tests/test_repositories/test_warehouse_simple.py  
2019-12-17 15:33:35.000000000 +0100
+++ new/dephell-0.8.3/tests/test_repositories/test_warehouse_simple.py  
2020-04-27 12:28:33.000000000 +0200
@@ -6,6 +6,7 @@
 
 # external
 import pytest
+from packaging.version import Version
 
 # project
 from dephell.constants import DEFAULT_WAREHOUSE
@@ -128,8 +129,9 @@
     assert requests_mock.last_request.headers['Authorization'] == 'Basic 
Z3JhbTp0ZXN0'
 
 
+@pytest.mark.parametrize('version', ['0.1.2', Version('0.1.2')])
 def test_download(requests_mock, asyncio_mock, temp_cache, fixtures_path: Path,
-                  temp_path: Path, requirements_path: Path):
+                  temp_path: Path, requirements_path: Path, version):
     pypi_url = 'https://custom.pypi.org/pypi/'
     text_response = (fixtures_path / 'warehouse-simple.html').read_text()
     file_url = re.findall(
@@ -143,7 +145,7 @@
     asyncio_mock.get(file_url, body=file_content)
 
     repo = WarehouseSimpleRepo(name='pypi', url=pypi_url)
-    coroutine = repo.download(name='dephell-shells', version='0.1.2', 
path=temp_path)
+    coroutine = repo.download(name='dephell-shells', version=version, 
path=temp_path)
     result = loop.run_until_complete(asyncio.gather(coroutine))[0]
     assert result is True
     assert (temp_path / file_name).exists()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/dephell-0.8.2/tests/test_resolving/test_apply_envs.py 
new/dephell-0.8.3/tests/test_resolving/test_apply_envs.py
--- old/dephell-0.8.2/tests/test_resolving/test_apply_envs.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/dephell-0.8.3/tests/test_resolving/test_apply_envs.py   2020-04-28 
08:23:30.000000000 +0200
@@ -0,0 +1,145 @@
+# project
+from dephell.controllers import Graph, Mutator, Resolver
+from dephell.converters import PIPConverter
+from dephell.models import Requirement
+
+# app
+from ..helpers import Fake, check, make_root, set_envs
+
+
+def fast_filter(root, *, deep=True):
+    resolver = Resolver(
+        graph=Graph(root),
+        mutator=Mutator(),
+    )
+    resolver.graph.fast_apply()
+    resolver.apply_envs(envs={'main'}, deep=deep)
+    reqs = Requirement.from_graph(resolver.graph, lock=False)
+    return {req.name: req for req in reqs}
+
+
+def test_direct_dependencies():
+    root = make_root(
+        root=Fake('', 'a', 'b'),
+        a=(Fake('1.0'), ),
+        b=(Fake('1.0'), ),
+    )
+    set_envs(root, 'a', {'main'})
+    set_envs(root, 'b', {'dev'})
+    check(root=root, a='==1.0', missed=['b'], envs={'main'})
+
+
+def test_subdependencies():
+    root = make_root(
+        root=Fake('', 'a', 'b'),
+        a=(Fake('1.0'), ),
+        b=(Fake('1.0', 'c'), ),
+        c=(Fake('1.0'), ),
+    )
+    set_envs(root, 'a', {'main'})
+    set_envs(root, 'b', {'dev'})
+    check(root=root, a='==1.0', missed=['b', 'c'], envs={'main'})
+
+
+def test_reapply():
+    root = make_root(
+        root=Fake('', 'a', 'b'),
+        a=(Fake('1.0', 'c'), ),
+        b=(Fake('1.0', 'c'), ),
+        c=(Fake('1.0'), ),
+    )
+    set_envs(root, 'a', {'main'})
+    set_envs(root, 'b', {'dev'})
+    check(root=root, a='==1.0', c='==1.0', missed=['b'], envs={'main'})
+
+
+def test_unapply_twice():
+    root = make_root(
+        root=Fake('', 'a', 'b', 'c'),
+        a=(Fake('1.0'), ),
+        b=(Fake('1.0', 'd'), ),
+        c=(Fake('1.0', 'd'), ),
+        d=(Fake('1.0'), ),
+    )
+    set_envs(root, 'a', {'main'})
+    set_envs(root, 'b', {'dev'})
+    set_envs(root, 'c', {'dev'})
+    check(root=root, a='==1.0', missed=['b', 'c', 'd'], envs={'main'})
+
+
+def test_with_real_names():
+    root = make_root(
+        root=Fake('', 'bandit', 'boltons'),
+        bandit=(Fake('1.0', 'colorama'), ),
+        boltons=(Fake('1.0'), ),
+        colorama=(Fake('1.0'), ),
+    )
+    set_envs(root, 'boltons', {'main'})
+    set_envs(root, 'bandit', {'dev'})
+    check(root=root, boltons='==1.0', missed=['bandit', 'colorama'], 
envs={'main'})
+
+
+def test_deep_dependencies():
+    root = make_root(
+        root=Fake('', 'apiwrapper', 'sphinx', 'certifi'),
+        sphinx=(Fake('1.0', 'requests'), ),
+        apiwrapper=(Fake('1.0', 'requests'), ),
+        requests=(Fake('1.0', 'certifi'), ),
+        certifi=(Fake('1.0'), ),
+    )
+    set_envs(root, 'sphinx', {'main'})
+    set_envs(root, 'apiwrapper', {'dev'})
+    set_envs(root, 'certifi', {'dev'})
+    check(root=root, sphinx='==1.0', certifi='==1.0', requests='==1.0', 
missed=['apiwrapper'], envs={'main'})
+
+
+def test_very_deep_dependencies_reapply():
+    root = make_root(
+        root=Fake('', 'a', 'b'),
+        a=(Fake('1.0', 'c'), ),
+        b=(Fake('1.0', 'c'), ),
+
+        c=(Fake('1.0', 'd'), ),
+        d=(Fake('1.0', 'e'), ),
+        e=(Fake('1.0'), ),
+    )
+    set_envs(root, 'a', {'main'})
+    set_envs(root, 'b', {'dev'})
+    check(root=root, a='==1.0', c='==1.0', d='==1.0', e='==1.0', missed=['b'], 
envs={'main'})
+
+
+def test_dependencies_unapply_twice_and_reapply():
+    root = make_root(
+        root=Fake('', 'requests', 'sphinx', 'certifi'),
+        requests=(Fake('1.0', 'certifi'), ),
+        sphinx=(Fake('1.0', 'requests'), ),
+        certifi=(Fake('1.0'), ),
+    )
+    set_envs(root, 'sphinx', {'main'})
+    set_envs(root, 'requests', {'dev'})
+    set_envs(root, 'certifi', {'dev'})
+    check(root=root, sphinx='==1.0', requests='==1.0', certifi='==1.0', 
missed=[], envs={'main'})
+
+
+def test_direct_dependencies_without_resolving():
+    root = make_root(
+        root=Fake('', 'a', 'b'),
+        a=(Fake('1.0'), ),
+        b=(Fake('1.0'), ),
+    )
+    set_envs(root, 'a', {'main'})
+    set_envs(root, 'b', {'dev'})
+
+    reqs = fast_filter(root)
+    assert set(reqs) == {'a'}
+
+
+def test_not_deep():
+    """If deep is False, filtering must work and no network requests can be 
made.
+    """
+    loader = PIPConverter(lock=False)
+    root = loader.loads(content='sphinx\nrequests')
+    root.dependencies[0].envs = {'main'}
+    root.dependencies[1].envs = {'dev'}
+    reqs = fast_filter(root, deep=False)
+    assert set(reqs) == {'sphinx'}


Reply via email to