Giter VIP home page Giter VIP logo

tox-poetry-installer's People

Contributors

callek avatar chriskuehl avatar enpaul avatar oshmoun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

tox-poetry-installer's Issues

Logging warnings when unsupported config options are set

There are a number of options Tox provides that are fairly specific to configuring how Tox interacts with Pip. These options have no usage when installing using Poetry because Poetry replaces the functionality these options are used to configure (there are exceptions however, see #4).

When the require_locked_deps = true setting is set for a Tox environment the plugin should log a warning for each of these unsupported config options that are also in use for the environment. These should not be breaking, but a notice should be given to the user that a setting they specified is being ignored. I think this is preferable to having unsupported (and inherently unusable) settings cause the environment to fail.

A non-exhaustive list of settings this plugin should warn on is below:

Note: If the required_locked_deps = false option is set then the warnings should not be raised. This is because these settings can and will still impact installations that use the default Tox installation backend, which may be in use if the setting is not true.

Installation does not properly take into account sub-dependencies

Reproduce repo: https://github.com/tizz98/tox-poetry-installer-bug-repro

How to reproduce

Ensure you have poetry==1.1.4 installed

git clone [email protected]:tizz98/tox-poetry-installer-bug-repro.git
cd tox-poetry-installer-bug-repro

poetry --version  # 1.1.4
pyenv virtualenv 3.8.6 tpibr
pyenv local tpibr

poetry install
tox

Expected result

Pytest should run the tests.

==================================== test session starts ====================================
platform linux -- Python 3.8.6, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /home/noteable/dev/tizz98/tox-poetry-installer-bug-repro
collected 1 item                                                                            

test_foo.py .                                                                         [100%]

===================================== 1 passed in 0.01s =====================================

Error

    ModuleNotFoundError: No module named 'ipython_genutils'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-60h8tk8n/ipypandex_68cec70a49ca4cd7b86c78b3788bf74a/setup.py", line 43, in <module>
        setup(
      File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/distutils/core.py", line 148, in setup
        dist.run_commands()
      File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/distutils/dist.py", line 966, in run_commands
        self.run_command(cmd)
      File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/distutils/dist.py", line 985, in run_command
        cmd_obj.run()
      File "/tmp/pip-install-60h8tk8n/ipypandex_68cec70a49ca4cd7b86c78b3788bf74a/setup.py", line 40, in run
        raise RuntimeError("ipypandex requires an ipython installation to work")
    RuntimeError: ipypandex requires an ipython installation to work

or

    ModuleNotFoundError: No module named 'IPython'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-install-m4usifx2/ipypandex_77e6e67871334c719f775eaa405b2ecb/setup.py", line 43, in <module>
        setup(
      File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/distutils/core.py", line 148, in setup
        dist.run_commands()
      File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/distutils/dist.py", line 966, in run_commands
        self.run_command(cmd)
      File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/distutils/dist.py", line 985, in run_command
        cmd_obj.run()
      File "/tmp/pip-install-m4usifx2/ipypandex_77e6e67871334c719f775eaa405b2ecb/setup.py", line 40, in run
        raise RuntimeError("ipypandex requires an ipython installation to work")
    RuntimeError: ipypandex requires an ipython installation to work

Explanation

The ipython package is a sub-dependency of ipypandex and when installing with poetry regularly it works fine because sub-dependencies are installed first. In this case though, the tox-poetry-installer plugin is just installing dependencies in a semi-random order which doesn't always work.

Even when specifying ipython explicitly as a dev dependency the same issue occurs.

Proposed fix

tox-poetry-installer should use a similar approach as how the poetry cli installs packages, see https://github.com/python-poetry/poetry/blob/18a9e2dfad36332c9b6c53f7a1cf980b66325fe4/poetry/installation/installer.py#L223

A way to emulate usedevelop=false? (i.e. install as wheel)

Is there a way to emulate how tox works when usedevelop is false? (It builds a wheel and installs it the "normal" way.)

Some of my integration tests rely on verifying that things work properly when my package is installed "normally" as opposed to as "source/develop". (Primarily b/c the way to get certain reflection data is different between installed as a package vs installed as source/develop.)

Thanks!

Add option to disable installation of project dependencies from the lockfile

Envs should have a configuration option that controls whether the dependencies of the package under test are installed to the environment. This should be checked independently of the skip_install and skipsdist built-in tox options. I can't imagine a real use case for this option, but it's the one part of the plugin's functionality that can't be configured when the plugin is installed, so it feels incomplete to not add it.

The new option should be named install_project_deps (counterpart to install_dev_deps) and have a default value of True.

Replace "poetry" dependency with "poetry-core"

poetry-core is a project to produce a lighter weight version of core poetry functionality specifically to better support PEP-517 build systems.

However (based on reading the published goals as of the 1.0 RC1 release) I believe it will also contain all the functionality necessary for this plugin to function.

Using the poetry-core package instead of poetry would give this project two main advantages:

  1. By design the poetry-core dependencies are considerably lighter than those of poetry. This was done to speed up the creation of PEP-517 build environments, but here it would reduce the size of this project's dependency chain. Since this is supposed to be a minimalist, lightweight plugin that just bridges the functionality of Tox and Poetry it would be nice if it did not need to install a huge number of dependencies to function.
  2. The poetry-core package will provide the poetry.core module namespace and replace a decent amount of functionality currently in the main poetry module. This means, at minimum, this plugin will need to be updated to be compatible with Poetry 1.1.0+ (which will use poetry-core). However due to the design goals of poetry-core it will, by necessity, have a more stable API than some of the more esoteric elements of poetry that this plugin currently imports. Using poetry.core instead of poetry will reduce the likelihood of future updates to Poetry breaking this plugin.

I need to confirm what- if any- functionality of poetry that this plugin needs is not included in poetry-core and determine how to work around those limitations. At minimum I suspect some of the types that are currently imported will not be available, but I have not confirmed this yet.

Failure in non-poetry project

  File "/usr/lib/python3.8/site-packages/tox_poetry_installer.py", line 269, in tox_testenv_install_deps
    poetry = PoetryFactory().create_poetry(venv.envconfig.config.toxinidir)
  File "/usr/lib/python3.8/site-packages/poetry/factory.py", line 42, in create_poetry
    raise RuntimeError(
RuntimeError: [tool.poetry] section not found in pyproject.toml

Allow installing all locked dev-dependencies easily into a tox env

The way many of the projects I work on are setup with tox env's is to specify and lock all dev-dependencies, and then install them into each tox environment that is used for development.

From the pip-tools world, this looks like this:

What I find useful about this work flow, is that I don't have to repeat the deps for every single tox testenv. This does have the downside of installing extra dependencies into certain testenvs, but for development testenvs, my teams have found that reducing the duplication and simplifying the process is worth it.

The feature I would like to offer for consideration is being to specify that I would like to optionally install all of the [tool.poetry.dev-dependencies] installed into a testenv.

So that instead of:

[testenv]
description = Run the tests
require_locked_deps = true
deps =
    pytest
    pytest-cov
    black
    pylint
    mypy
commands = ...

Where all of the deps are duplicated in the testenv and pyproject.toml, it was possible to instead write:

[testenv]
description = Run the tests
require_locked_deps = true
install_poetry_dev_dependencies = true
commands = ...

Note, this is similar to https://github.com/sinoroc/tox-poetry-dev-dependencies, but tox-poetry-installer also supports a wider existing feature set.

LockedDepNotFoundError even though the dependency is in the lock file

I'm trying out tox-poetry-installer on my project, but I'm running into an issue:

$ tox -re py37
.package recreate: /Users/brechtm/Documents/Code/rinohtype/.tox/.package
.package installdeps: poetry-core>=1.0.0
py37 recreate: /Users/brechtm/Documents/Code/rinohtype/.tox/py37
ERROR: [tox-poetry-installer]: No version of locked dependency 'rinoh-typeface-texgyrecursor' found in the project lockfile
py37 inst: /Users/brechtm/Documents/Code/rinohtype/.tox/.tmp/package/1/rinohtype-0.5.0.dev0.tar.gz
____________________________________________ summary _____________________________________________
ERROR:   py37: LockedDepNotFoundError

As far as I can see, rinoh-typeface-texgyrecursor is listed in the lock file. Perhaps the fact that this package also specifies my package under test as a dependency is relevant?

ERROR: Error creating virtualenv. Note that spaces in paths are not supported by virtualenv. Error details: FileNotFoundError(2, 'No such file or directory')

I tried to use the lib for the first time and failed. 😞 Maybe I'm doing something wrong, so I reproduced it in a small project. https://github.com/iurisilvio/test-tox-poetry-installer

It is a clean poetry project (generated with poetry new), added only tox.ini and .github/workflows/python-package.yml files.

Here are log files for one failing build: test-tox-poetry-installer.txt

Add docs for usage with variable dependencies in a matrix

The below is a fairly typical setup used to test compatibility with multiple versions of a package:

[tox]
envlist = py3{7,8,9}-foo{1,2,3}

[testenv]
description = Some very cool tests
deps =
    foo1: foo == 1.0
    foo2: foo == 2.0
    foo3: foo == 3.0

This use case is supported and can exist alongside the plugin being installed. A section should be added to the Other Notes section explaining how.

(In the meantime see #58)

Private repository with credentials

I have a poetry project with packages from a private repository. With poetry alone it works as expected, but tox-poetry-installer emits the warning

"WARNING: tox-poetry-installer: Skipping XXXX: no locked version found compatible with target python version 3.7.9"

for the packages XXXX in the private repo.

Despite what it says it seems not to be a problem with the python version. The warning is shown for different versions of python, (and where poetry has no issue installing the package).

Could it be that a custom repository (e.g. with 'secondary=true') with credentials is not (yet) supported by tox-poetry-installer ?

Specifically I have

[[tool.poetry.source]]
name = "custom_repo"
url = "https://XXXXXXXXXXXX/packages/pypi/simple"
secondary = true

Which I configured once with

poetry config http-basic.custom_repo username password

As a workaround I tried setting

[testenv]
setenv = 
	PIP_EXTRA_INDEX_URL = https://username:password@XXXXXXXXXXX/packages/pypi/simple
....

but that did not help. The warning is still there and the packages are not installed.

Installation tries to install packages not meant for OS

tl;dr: this library is trying to install pywin32 even on linux/mac which won't work.

How to reproduce

Ensure you have poetry==1.1.4 installed

git clone [email protected]:tizz98/tox-poetry-installer-bug-repro.git
cd tox-poetry-installer-bug-repro

poetry --version  # 1.1.4
pyenv virtualenv 3.8.6 tpibr
pyenv local tpibr

poetry install
tox

Expected result

Pytest should run the tests.

==================================== test session starts ====================================
platform linux -- Python 3.8.6, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /home/noteable/dev/tizz98/tox-poetry-installer-bug-repro
collected 1 item                                                                            

test_foo.py .                                                                         [100%]

===================================== 1 passed in 0.01s =====================================

Error

py38 create: /home/noteable/dev/tizz98/tox-poetry-installer-bug-repro/.tox/py38
py38 tox-poetry-installer: Installing 96 dependencies from Poetry lock file
__________________________________________________________________________________________ summary __________________________________________________________________________________________
  py38: commands succeeded
  congratulations :)
Traceback (most recent call last):
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/utils/env.py", line 1070, in _run
    output = subprocess.check_output(
  File "/home/noteable/.pyenv/versions/3.8.6/lib/python3.8/subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/utils/_compat.py", line 217, in run
    raise CalledProcessError(
poetry.utils._compat.CalledProcessError: Command '['/home/noteable/dev/tizz98/tox-poetry-installer-bug-repro/.tox/py38/bin/pip', 'install', '--no-deps', '-r', '/tmp/pywin32-3002s811h8kreqs.txt']' returned non-zero exit status 1.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/noteable/.pyenv/versions/tpibr/bin/tox", line 8, in <module>
    sys.exit(cmdline())
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/session/__init__.py", line 44, in cmdline
    main(args)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/session/__init__.py", line 69, in main
    exit_code = session.runcommand()
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/session/__init__.py", line 197, in runcommand
    return self.subcommand_test()
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/session/__init__.py", line 225, in subcommand_test
    run_sequential(self.config, self.venv_dict)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/session/commands/run/sequential.py", line 9, in run_sequential
    if venv.setupenv():
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/venv.py", line 628, in setupenv
    status = self.update(action=action)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox/venv.py", line 275, in update
    self.hook.tox_testenv_install_deps(action=action, venv=self)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/pluggy/hooks.py", line 286, in __call__
    return self._hookexec(self, self.get_hookimpls(), kwargs)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/pluggy/manager.py", line 93, in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/pluggy/manager.py", line 84, in <lambda>
    self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/pluggy/callers.py", line 208, in _multicall
    return outcome.get_result()
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/pluggy/callers.py", line 80, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/pluggy/callers.py", line 187, in _multicall
    res = hook_impl.function(*args)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox_poetry_installer/hooks.py", line 148, in tox_testenv_install_deps
    installer.install(poetry, venv, dependencies)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/tox_poetry_installer/installer.py", line 48, in install
    pip.install(dependency)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/installation/pip_installer.py", line 86, in install
    self.run(*args)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/installation/pip_installer.py", line 129, in run
    return self._env.run_pip(*args, **kwargs)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/utils/env.py", line 1042, in run_pip
    return self._run(cmd, **kwargs)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/utils/env.py", line 1332, in _run
    return super(VirtualEnv, self)._run(cmd, **kwargs)
  File "/home/noteable/.pyenv/versions/3.8.6/envs/tpibr/lib/python3.8/site-packages/poetry/utils/env.py", line 1074, in _run
    raise EnvCommandError(e, input=input_)
poetry.utils.env.EnvCommandError: Command ['/home/noteable/dev/tizz98/tox-poetry-installer-bug-repro/.tox/py38/bin/pip', 'install', '--no-deps', '-r', '/tmp/pywin32-3002s811h8kreqs.txt'] errored with the following return code 1, and output: 
ERROR: Could not find a version that satisfies the requirement pywin32==300
ERROR: No matching distribution found for pywin32==300

Explanation

The pywin32 package is a subdependency of jupyter-core which is a subdependency of nbformat. When looking at the poetry.lock file, there is a line that says pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\""} which indicates this should only be installed on windows OSes.

Request: Recommend users to explicitly depend on poetry instead of `[poetry]` extra

When we upgraded our codebase to use Poetry 1.3, we updated all our lockfiles with --no-update. But because tox-poetry-installer[poetry] adds an upper bound on cleo < 2.0.0 (which conflicts with poetry 1.3.1's cleo >= 2.0.0 dependency), tox-poetry-installer wasn't using the same version of Poetry. Luckily, Poetry 1.2.2 is forwards compatible with the new lock file, but we might not be so lucky in the future. When Poetry 2.0.0 is released, I wouldn't be surprised if this happens again: tox-poetry-installer[poetry] has poetry < 2.0.0, so tox-poetry-installer will be using a different version of poetry than the one being used on the command line.

But on a higher level, it's not immediately obvious that upgrading Poetry on the command line requires you to upgrade all the deps in your lock file (since it's not currently possible to upgrade a single transitive dependency in poetry: python-poetry/poetry#4991). Whereas if I had poetry as an explicit dep in pyproject.toml it's 1) obvious that I need to update it, and 2) easy to update that one dependency.

IMO I think the following makes sense for tox-poetry-installer:

  1. Have the installation instructions mention to add poetry as a dependency in addition to tox-poetry-installer
    • Since Poetry's official installation instructions put Poetry in a separate virtual environment, I don't think tox-poetry-installer should care about the use case of someone having the Poetry CLI installed in the same environment, as documented here:
      Poetry is an optional dependency of this package explicitly to support the use case of having the
      plugin and the `poetry` package installed to the same python environment; this is most common in
      containers and/or CI. In this case there are two potential problems that can arise in this case:
  2. Remove [poetry] extra and do one of the following:
    • Just make poetry/cleo normal deps of tox-poetry-installer
      • This way, if Poetry 2.0.0 comes out and the user updates their pyproject.toml to use poetry = "2.0.0", poetry lock will error saying the poetry requested by user and the poetry depended on by tox-poetry-installer conflict, which is a good thing because the user is made aware that tox-poetry-installer (might) not work with the new version of Poetry
    • Make poetry/cleo deps with any * version allowed
      • This way, new Poetry/cleo versions with just work if they don't touch the few things tox-poetry-installer needs
      • If Poetry/cleo makes changes that would actually break tox-poetry-installer, then tox-poetry-installer would need to make a new release for the fix, which would need to happen anyway even if tox-poetry-installer had an upper bound
    • Remove poetry/cleo deps altogether
      • Effectively the same as making the constraint *
      • The advantage of this approach is that if the user forgets to add poetry to pyproject.toml, tox-poetry-installer can show a nice "Please add poetry to pyproject.toml" error instead of implicitly using the newest version of poetry which may or may not align with the user's version of Poetry

Usage with envlist?

How is this meant to be used with envlist? I'm not sure I fully understand the purpose of this project, but I have this in my tox.ini:

[tox]
skipsdist = True
envlist = py{37,38,39}-django{20,21,22,30,31,32}

[testenv]
deps =
    django20: Django==2.0
    django21: Django==2.1
    django22: Django==2.2
    django30: Django==3.0
    django31: Django==3.1
    django32: Django==3.2

The idea is to use a build Matrix with Python 3.7-3.9 and Django 2.0-3.2, you get the idea. However, when I use this plugin, it always installs Django 3.2 because that's what's in my lockfile.

So maybe I am misunderstanding the purpose of the project, I'd appreciate if someone could shed some light on this.

Plugin does not respect python requiement markers

The dataclasses package does not support Python-3.7 and newer. This is properly marked in the Poetry lockfile (sample below) but is not respected by the plugin. I suspect that this is because the plugin does not properly make use of python version requirements, as that logic is in the Poetry resolver classes and not in the pip installer class.

The fix for this is to check the expected python version before blindly installing the dependency.

Excerpt from poetry.lock:

[[package]]
name = "dataclasses"
version = "0.8"
description = "A backport of the dataclasses module for Python 3.6"
category = "dev"
optional = false
python-versions = ">=3.6, <3.7"  # <-- proper python version constraint properly marked

Minimum reproducer tox.ini:

[tox]
envlist = py38
skipsdist = true

[testenv]
description = Run the tests
locked_deps =
    dataclasses
commands =
    pip freeze

Traceback from running using the above tox config:

Traceback (most recent call last):
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/utils/env.py", line 1070, in _run
    output = subprocess.check_output(
  File "/usr/lib64/python3.8/subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/utils/_compat.py", line 217, in run
    raise CalledProcessError(
poetry.utils._compat.CalledProcessError: Command '['/home/enpaul/Git/tox-poetry-installer/.tox/static/bin/pip', 'install', '--no-deps', '-r', '/tmp/dataclasses-0.8gveip4tureqs.txt']' returned non-zero exit status 1.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/enpaul/Git/tox-poetry-installer/.venv/bin/tox", line 8, in <module>
    sys.exit(cmdline())
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/session/__init__.py", line 44, in cmdline
    main(args)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/session/__init__.py", line 69, in main
    exit_code = session.runcommand()
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/session/__init__.py", line 197, in runcommand
    return self.subcommand_test()
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/session/__init__.py", line 225, in subcommand_test
    run_sequential(self.config, self.venv_dict)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/session/commands/run/sequential.py", line 9, in run_sequential
    if venv.setupenv():
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/venv.py", line 620, in setupenv
    status = self.update(action=action)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/tox/venv.py", line 275, in update
    self.hook.tox_testenv_install_deps(action=action, venv=self)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/pluggy/hooks.py", line 286, in __call__
    return self._hookexec(self, self.get_hookimpls(), kwargs)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/pluggy/manager.py", line 93, in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/pluggy/manager.py", line 84, in <lambda>
    self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/pluggy/callers.py", line 208, in _multicall
    return outcome.get_result()
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/pluggy/callers.py", line 80, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/pluggy/callers.py", line 187, in _multicall
    res = hook_impl.function(*args)
  File "/home/enpaul/Git/tox-poetry-installer/tox_poetry_installer/hooks.py", line 126, in tox_testenv_install_deps
    utilities.install_to_venv(poetry, venv, dependencies)
  File "/home/enpaul/Git/tox-poetry-installer/tox_poetry_installer/utilities.py", line 44, in install_to_venv
    installer.install(dependency)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/installation/pip_installer.py", line 86, in install
    self.run(*args)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/installation/pip_installer.py", line 129, in run
    return self._env.run_pip(*args, **kwargs)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/utils/env.py", line 1042, in run_pip
    return self._run(cmd, **kwargs)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/utils/env.py", line 1332, in _run
    return super(VirtualEnv, self)._run(cmd, **kwargs)
  File "/home/enpaul/Git/tox-poetry-installer/.venv/lib/python3.8/site-packages/poetry/utils/env.py", line 1074, in _run
    raise EnvCommandError(e, input=input_)
poetry.utils.env.EnvCommandError: Command ['/home/enpaul/Git/tox-poetry-installer/.tox/static/bin/pip', 'install', '--no-deps', '-r', '/tmp/dataclasses-0.8gveip4tureqs.txt'] errored with the following return code 1, and output: 
ERROR: Could not find a version that satisfies the requirement dataclasses==0.8 (from -r /tmp/dataclasses-0.8gveip4tureqs.txt (line 1)) (from versions: 0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
ERROR: No matching distribution found for dataclasses==0.8 (from -r /tmp/dataclasses-0.8gveip4tureqs.txt (line 1))

Use Poetry 1.3 with `[poetry]` extra

I believe the only thing blocking this is updating

-cleo = {version = "^1.0.0a5", optional = true, allow-prereleases = true}
+cleo = {version = "^2.0.0", optional = true, allow-prereleases = true}
-poetry = {version = "^1.2.0", optional = true}
+poetry = {version = "^1.3.0", optional = true}

Update hook reference to use recommended access pattern

Currently the Tox plugin hooks are implemented by importing hookimpl directly from the tox module like below:

from tox import hookimpl

@hookimpl
def tox_testenv_install_deps(...):
    ...

However the Tox plugin documentation specifies that plugins should import pluggy directly like the below example:

import pluggy

hookimpl = pluggy.HookimplMarker("tox")

@hookimpl
def tox_testenv_install_deps(...):
    ...

I'm not exactly clear on what the functional difference is between these two approaches, but that just makes me more inclined to err on the side of following the documentation. One obvious upside I can see is that if the tox module ever moves where tox.hookimpl is available it prevents the import from breaking, though I'm not sure if this is the only tangible benefit.

Either way, this is a relatively minor change that will make the plugin more compliant with the existing standards.

[Refactor] Clean up the imports

Imports are currently done in a bunch of different ways through out the module. Ideally, all imports should be done using the full namespace they're importable under (i.e. instead of from foo import bar do import foo.bar) and should never change imported names (i.e. instead of import Foo as FizzFoo do import Foo). This should be standardized.

Support the "extras" setting for Tox env configs

The extras setting allows extras of the project package to be installed to a specific Tox environment. Poetry supports extras, Tox supports extras, there's no reason this plugin can't too.

The plugin's current behaviour when installing project package dependencies is to install all dependencies of the project package, regardless of which are optional and which are not. In lieu of support for the extras setting this is preferable to the alternative (only installing non-optional dependencies) because it still allows extras of the project package to be tested in the Tox environment. However environments that require a project extra not be installed cannot be run, and all environments must install all dependencies which is excessive.

The data for identifying optional dependencies- and the extra(s) they belong to- exist in the lockfile, there should be support for specifying extras to install, with the default behaviour being to install only non-optional project package dependencies.

Support dependency groups

The latest alpha release of Poetry (1.12.0a2) supports the usage of depedency groups, added by this PR. For my use case these groups are able to resolve solve problems within my CI setup, but they do not work with the current version of tox-poetry-installer. My question is thus wether this feature will be supported in the future.

Trying to debug this issue myself I found that imports from the poetry-core package no longer work, but they are easily fixed:

Click to view patch
diff --git a/tests/fixtures.py b/tests/fixtures.py
index 8d0b142..f807d9e 100644
--- a/tests/fixtures.py
+++ b/tests/fixtures.py
@@ -7,7 +7,7 @@ import poetry.installation.pip_installer
 import poetry.utils.env
 import pytest
 import tox
-from poetry.core.packages import Package as PoetryPackage
+from poetry.core.packages.package import Package as PoetryPackage
 
 from tox_poetry_installer import utilities
 
diff --git a/tox_poetry_installer/datatypes.py b/tox_poetry_installer/datatypes.py
index 4233a94..34f8a2e 100644
--- a/tox_poetry_installer/datatypes.py
+++ b/tox_poetry_installer/datatypes.py
@@ -1,7 +1,7 @@
 """Definitions for typehints/containers used by the plugin"""
 from typing import Dict
 
-from poetry.core.packages import Package as PoetryPackage
+from poetry.core.packages.package import Package as PoetryPackage
 
 
 # Map of package names to the package object
diff --git a/tox_poetry_installer/installer.py b/tox_poetry_installer/installer.py
index bbde07f..c29b3c1 100644
--- a/tox_poetry_installer/installer.py
+++ b/tox_poetry_installer/installer.py
@@ -8,7 +8,7 @@ import typing
 from typing import Sequence
 from typing import Set
 
-from poetry.core.packages import Package as PoetryPackage
+from poetry.core.packages.package import Package as PoetryPackage
 from tox.venv import VirtualEnv as ToxVirtualEnv
 
 from tox_poetry_installer import logger
diff --git a/tox_poetry_installer/utilities.py b/tox_poetry_installer/utilities.py
index 87e1d98..3ce4c5b 100644
--- a/tox_poetry_installer/utilities.py
+++ b/tox_poetry_installer/utilities.py
@@ -9,8 +9,8 @@ from typing import Sequence
 from typing import Set
 from typing import Union
 
-from poetry.core.packages import Dependency as PoetryDependency
-from poetry.core.packages import Package as PoetryPackage
+from poetry.core.packages.dependency import Dependency as PoetryDependency
+from poetry.core.packages.package import Package as PoetryPackage
 from tox.action import Action as ToxAction
 from tox.venv import VirtualEnv as ToxVirtualEnv

However, when this issue is resolved I still run into the following exception which prohibits the use of this package:

No version of Poetry could be imported under the current environment for '<poetry-venv>'

Add integration tests

A series of integration tests should be added (in addition to the unit tests already present). The goal of the integration tests is to validate the integration with Tox, since that component of the program can't be adequately tested via unit tests because Tox is not directly imported anywhere.

Emphasis of the integration tests should be on the various config options having the intended effects and the proper dependencies being "installed" to the venv at runtime.

Support tox ^4.0

Depencies currently prevent from using with tox 4.0 and above

Plugin dumps a traceback when an unsafe dependency is a direct dependency of the package under test

Creating a package with a direct dependency on setuptools dumps a traceback and causes the plugin to fail:

Traceback
Traceback (most recent call last):
  File "/home/enpaul/Git/test/.venv/bin/tox", line 8, in 
    sys.exit(cmdline())
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/session/__init__.py", line 44, in cmdline
    main(args)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/session/__init__.py", line 69, in main
    exit_code = session.runcommand()
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/session/__init__.py", line 197, in runcommand
    return self.subcommand_test()
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/session/__init__.py", line 225, in subcommand_test
    run_sequential(self.config, self.venv_dict)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/session/commands/run/sequential.py", line 9, in run_sequential
    if venv.setupenv():
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/venv.py", line 629, in setupenv
    status = self.update(action=action)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox/venv.py", line 278, in update
    self.hook.tox_testenv_install_deps(action=action, venv=self)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/pluggy/_hooks.py", line 265, in __call__
    return self._hookexec(self.name, self.get_hookimpls(), kwargs, firstresult)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/pluggy/_manager.py", line 80, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/pluggy/_callers.py", line 60, in _multicall
    return outcome.get_result()
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/pluggy/_result.py", line 60, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/pluggy/_callers.py", line 39, in _multicall
    res = hook_impl.function(*args)
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox_poetry_installer/hooks.py", line 236, in tox_testenv_install_deps
    raise err
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox_poetry_installer/hooks.py", line 221, in tox_testenv_install_deps
    packages, virtualenv, poetry, venv.envconfig.extras
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox_poetry_installer/utilities.py", line 171, in find_project_deps
    for item in poetry.package.requires
  File "/home/enpaul/Git/test/.venv/lib/python3.7/site-packages/tox_poetry_installer/utilities.py", line 172, in 
    if not item.is_optional()
KeyError: 'setuptools'

Looking over the handling of unsafe dependencies in the code, it looks like it doesn't handle the case of an unsafe dependency being a direct dependency of the package under test; the unsafe dependency being a toxenv dependency or otherwise elsewhere in the transient dependency tree works as expected/documented, but not this case.

While this is being fixed, the accessing of the UNSAFE_PACKAGES attribute should also be updated to maintain compatibility with the upcoming poetry 1.2 release.

Testenv virtual environments are always recreated even if dependencies have not changed

The envs are recreated each time, which causes subsequent runs to be just as slow as the initial run, rather than skipping installation as is typical with tox. This makes it difficult to use in a pre-commit situation, due to how long the delay is before tox commands complete.

% tox -e fmt
fmt create: /Users/user/PycharmProjects/python-test/.tox/fmt
[tox-poetry-installer]: (fmt) installing 10 env dependencies from lockfile
fmt installed: appdirs==1.4.4,black==20.8b1,click==7.1.2,isort==5.6.4,mypy-extensions==0.4.3,pathspec==0.8.0,regex==2020.10.23,toml==0.10.1,typed-ast==1.4.1,typing-extensions==3.7.4.3
fmt run-test-pre: PYTHONHASHSEED='943038290'
fmt run-test: commands[0] | isort .
Skipped 1 files
fmt run-test: commands[1] | black .
All done! ✨ 🍰 ✨
1 file left unchanged.
_________________________________________________________________________________________ summary _________________________________________________________________________________________
  fmt: commands succeeded
  congratulations :)
% tox -e fmt
fmt recreate: /Users/user/PycharmProjects/python-test/.tox/fmt
[tox-poetry-installer]: (fmt) installing 10 env dependencies from lockfile
fmt installed: appdirs==1.4.4,black==20.8b1,click==7.1.2,isort==5.6.4,mypy-extensions==0.4.3,pathspec==0.8.0,regex==2020.10.23,toml==0.10.1,typed-ast==1.4.1,typing-extensions==3.7.4.3
fmt run-test-pre: PYTHONHASHSEED='3751324691'
fmt run-test: commands[0] | isort .
Skipped 1 files
fmt run-test: commands[1] | black .
All done! ✨ 🍰 ✨
1 file left unchanged.
_________________________________________________________________________________________ summary _________________________________________________________________________________________
  fmt: commands succeeded
  congratulations :)

With require_locked_deps = true:

tox -e fmt  17.97s user 4.11s system 49% cpu 44.674 total

When set to false:

tox -e fmt  1.62s user 0.53s system 16% cpu 12.796 total

Config

[tox]
skipsdist = true
isolated_build = true
requires =
    tox-poetry-installer>=0.3.1

[testenv:fmt]
require_locked_deps = false
deps =
    isort
    black
commands =
    isort .
    black .
[tool.poetry]
name = "python-test"
version = "0.1.0"
description = ""
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.dev-dependencies]
black = "^20.8b1"
isort = "^5.6.4"
tox = "^3.20.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
poetry.lock
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "black"
version = "20.8b1"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6"

[package.dependencies]
appdirs = "*"
click = ">=7.1.2"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.6,<1"
regex = ">=2020.1.8"
toml = ">=0.10.1"
typed-ast = ">=1.4.0"
typing-extensions = ">=3.7.4"

[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]

[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package]]
name = "distlib"
version = "0.3.1"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "filelock"
version = "3.0.12"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "isort"
version = "5.6.4"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"

[package.extras]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]

[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "packaging"
version = "20.4"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"

[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"

[[package]]
name = "pathspec"
version = "0.8.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package]]
name = "pluggy"
version = "0.13.1"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"

[package.extras]
dev = ["pre-commit", "tox"]

[[package]]
name = "py"
version = "1.9.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"

[[package]]
name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"

[[package]]
name = "regex"
version = "2020.10.23"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"

[[package]]
name = "toml"
version = "0.10.1"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "tox"
version = "3.20.1"
description = "tox is a generic virtualenv management and test command line tool"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"

[package.dependencies]
colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""}
filelock = ">=3.0.0"
packaging = ">=14"
pluggy = ">=0.12.0"
py = ">=1.4.17"
six = ">=1.14.0"
toml = ">=0.9.4"
virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7"

[package.extras]
docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"]
testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "pytest-xdist (>=1.22.2)"]

[[package]]
name = "typed-ast"
version = "1.4.1"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "typing-extensions"
version = "3.7.4.3"
description = "Backported and Experimental Type Hints for Python 3.5+"
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "virtualenv"
version = "20.1.0"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"

[package.dependencies]
appdirs = ">=1.4.3,<2"
distlib = ">=0.3.1,<1"
filelock = ">=3.0.0,<4"
six = ">=1.9.0,<2"

[package.extras]
docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"]
testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "pytest-xdist (>=1.31.0)", "packaging (>=20.0)", "xonsh (>=0.9.16)"]

[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "e71e289ece40ec13401b98c63c9e58b8a23dfa899e4e8fe436a035c6bb4a1a30"

[metadata.files]
appdirs = [
    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
black = [
    {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
]
click = [
    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
colorama = [
    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
distlib = [
    {file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
    {file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
]
filelock = [
    {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"},
    {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"},
]
isort = [
    {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"},
    {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"},
]
mypy-extensions = [
    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
packaging = [
    {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
    {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
]
pathspec = [
    {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
    {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
]
pluggy = [
    {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
    {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
py = [
    {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
    {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
]
pyparsing = [
    {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
    {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
regex = [
    {file = "regex-2020.10.23-cp27-cp27m-win32.whl", hash = "sha256:781906e45ef1d10a0ed9ec8ab83a09b5e0d742de70e627b20d61ccb1b1d3964d"},
    {file = "regex-2020.10.23-cp27-cp27m-win_amd64.whl", hash = "sha256:8cd0d587aaac74194ad3e68029124c06245acaeddaae14cb45844e5c9bebeea4"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:af360e62a9790e0a96bc9ac845d87bfa0e4ee0ee68547ae8b5a9c1030517dbef"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e21340c07090ddc8c16deebfd82eb9c9e1ec5e62f57bb86194a2595fd7b46e0"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:e5f6aa56dda92472e9d6f7b1e6331f4e2d51a67caafff4d4c5121cadac03941e"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c30d8766a055c22e39dd7e1a4f98f6266169f2de05db737efe509c2fb9c8a3c8"},
    {file = "regex-2020.10.23-cp36-cp36m-win32.whl", hash = "sha256:1a065e7a6a1b4aa851a0efa1a2579eabc765246b8b3a5fd74000aaa3134b8b4e"},
    {file = "regex-2020.10.23-cp36-cp36m-win_amd64.whl", hash = "sha256:c95d514093b80e5309bdca5dd99e51bcf82c44043b57c34594d9d7556bd04d05"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f4b1c65ee86bfbf7d0c3dfd90592a9e3d6e9ecd36c367c884094c050d4c35d04"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d62205f00f461fe8b24ade07499454a3b7adf3def1225e258b994e2215fd15c5"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b706c70070eea03411b1761fff3a2675da28d042a1ab7d0863b3efe1faa125c9"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d43cf21df524283daa80ecad551c306b7f52881c8d0fe4e3e76a96b626b6d8d8"},
    {file = "regex-2020.10.23-cp37-cp37m-win32.whl", hash = "sha256:570e916a44a361d4e85f355aacd90e9113319c78ce3c2d098d2ddf9631b34505"},
    {file = "regex-2020.10.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1c447b0d108cddc69036b1b3910fac159f2b51fdeec7f13872e059b7bc932be1"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8469377a437dbc31e480993399fd1fd15fe26f382dc04c51c9cb73e42965cc06"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:59d5c6302d22c16d59611a9fd53556554010db1d47e9df5df37be05007bebe75"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a973d5a7a324e2a5230ad7c43f5e1383cac51ef4903bf274936a5634b724b531"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:97a023f97cddf00831ba04886d1596ef10f59b93df7f855856f037190936e868"},
    {file = "regex-2020.10.23-cp38-cp38-win32.whl", hash = "sha256:e289a857dca3b35d3615c3a6a438622e20d1bf0abcb82c57d866c8d0be3f44c4"},
    {file = "regex-2020.10.23-cp38-cp38-win_amd64.whl", hash = "sha256:0cb23ed0e327c18fb7eac61ebbb3180ebafed5b9b86ca2e15438201e5903b5dd"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c53dc8ee3bb7b7e28ee9feb996a0c999137be6c1d3b02cb6b3c4cba4f9e5ed09"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a46eba253cedcbe8a6469f881f014f0a98819d99d341461630885139850e281"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:240509721a663836b611fa13ca1843079fc52d0b91ef3f92d9bba8da12e768a0"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f567df0601e9c7434958143aebea47a9c4b45434ea0ae0286a4ec19e9877169"},
    {file = "regex-2020.10.23-cp39-cp39-win32.whl", hash = "sha256:bfd7a9fddd11d116a58b62ee6c502fd24cfe22a4792261f258f886aa41c2a899"},
    {file = "regex-2020.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:1a511470db3aa97432ac8c1bf014fcc6c9fbfd0f4b1313024d342549cf86bcd6"},
    {file = "regex-2020.10.23.tar.gz", hash = "sha256:2278453c6a76280b38855a263198961938108ea2333ee145c5168c36b8e2b376"},
]
six = [
    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
toml = [
    {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
    {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
]
tox = [
    {file = "tox-3.20.1-py2.py3-none-any.whl", hash = "sha256:42ce19ce5dc2f6d6b1fdc5666c476e1f1e2897359b47e0aa3a5b774f335d57c2"},
    {file = "tox-3.20.1.tar.gz", hash = "sha256:4321052bfe28f9d85082341ca8e233e3ea901fdd14dab8a5d3fbd810269fbaf6"},
]
typed-ast = [
    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
    {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
    {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
    {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
    {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
    {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
    {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
    {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
    {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
    {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
    {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
    {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
    {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
    {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
    {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
    {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
    {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
    {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
]
typing-extensions = [
    {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
    {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
    {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
]
virtualenv = [
    {file = "virtualenv-20.1.0-py2.py3-none-any.whl", hash = "sha256:b0011228208944ce71052987437d3843e05690b2f23d1c7da4263fde104c97a2"},
    {file = "virtualenv-20.1.0.tar.gz", hash = "sha256:b8d6110f493af256a40d65e29846c69340a947669eec8ce784fcf3dd3af28380"},
]

Include tests in PyPI sdist

In order to submit this for inclusion in openSUSE package collection, I need to run the tests offline in the isolated rpmbuild workers. The first step for that is getting the tests into the tarball.

This can be done using a MANIFEST.in, but there are newer solutions depending on how the sdist is built and uploaded to PyPI.

e.g. dephell automatically adds tests to sdist if they are below a certain size.

Infinite recursion installing mkdocs-material from lockfile

[tox]
skipsdist = true
isolated_build = true
requires =
    tox-poetry-installer>=0.3.0

[testenv:docs]
require_locked_deps = true
deps =
    mkdocs-material
commands =
    mkdocs build
[tool.poetry]
name = "python-test"
version = "0.1.0"
description = ""
authors = []
license = "MIT"

[tool.poetry.dependencies]
python = "^3.8"

[tool.poetry.dev-dependencies]
mkdocs-material = "^6.1.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
poetry.lock
[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package]]
name = "future"
version = "0.18.2"
description = "Clean single-source support for Python 3 and 2"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"

[[package]]
name = "jinja2"
version = "2.11.2"
description = "A very fast and expressive template engine."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[package.dependencies]
MarkupSafe = ">=0.23"

[package.extras]
i18n = ["Babel (>=0.8)"]

[[package]]
name = "joblib"
version = "0.17.0"
description = "Lightweight pipelining: using Python functions as pipeline jobs."
category = "dev"
optional = false
python-versions = ">=3.6"

[[package]]
name = "livereload"
version = "2.6.3"
description = "Python LiveReload is an awesome tool for web developers"
category = "dev"
optional = false
python-versions = "*"

[package.dependencies]
six = "*"
tornado = {version = "*", markers = "python_version > \"2.7\""}

[[package]]
name = "lunr"
version = "0.5.8"
description = "A Python implementation of Lunr.js"
category = "dev"
optional = false
python-versions = "*"

[package.dependencies]
future = ">=0.16.0"
nltk = {version = ">=3.2.5", optional = true, markers = "python_version > \"2.7\" and extra == \"languages\""}
six = ">=1.11.0"

[package.extras]
languages = ["nltk (>=3.2.5,<3.5)", "nltk (>=3.2.5)"]

[[package]]
name = "markdown"
version = "3.3.3"
description = "Python implementation of Markdown."
category = "dev"
optional = false
python-versions = ">=3.6"

[package.extras]
testing = ["coverage", "pyyaml"]

[[package]]
name = "markupsafe"
version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"

[[package]]
name = "mkdocs"
version = "1.1.2"
description = "Project documentation with Markdown."
category = "dev"
optional = false
python-versions = ">=3.5"

[package.dependencies]
click = ">=3.3"
Jinja2 = ">=2.10.1"
livereload = ">=2.5.1"
lunr = {version = "0.5.8", extras = ["languages"]}
Markdown = ">=3.2.1"
PyYAML = ">=3.10"
tornado = ">=5.0"

[[package]]
name = "mkdocs-material"
version = "6.1.0"
description = "A Material Design theme for MkDocs"
category = "dev"
optional = false
python-versions = "*"

[package.dependencies]
markdown = ">=3.2"
mkdocs = ">=1.1"
mkdocs-material-extensions = ">=1.0"
Pygments = ">=2.4"
pymdown-extensions = ">=7.0"

[[package]]
name = "mkdocs-material-extensions"
version = "1.0.1"
description = "Extension pack for Python Markdown."
category = "dev"
optional = false
python-versions = ">=3.5"

[package.dependencies]
mkdocs-material = ">=5.0.0"

[[package]]
name = "nltk"
version = "3.5"
description = "Natural Language Toolkit"
category = "dev"
optional = false
python-versions = "*"

[package.dependencies]
click = "*"
joblib = "*"
regex = "*"
tqdm = "*"

[package.extras]
all = ["requests", "numpy", "python-crfsuite", "scikit-learn", "twython", "pyparsing", "scipy", "matplotlib", "gensim"]
corenlp = ["requests"]
machine_learning = ["gensim", "numpy", "python-crfsuite", "scikit-learn", "scipy"]
plot = ["matplotlib"]
tgrep = ["pyparsing"]
twitter = ["twython"]

[[package]]
name = "pygments"
version = "2.7.2"
description = "Pygments is a syntax highlighting package written in Python."
category = "dev"
optional = false
python-versions = ">=3.5"

[[package]]
name = "pymdown-extensions"
version = "8.0.1"
description = "Extension pack for Python Markdown."
category = "dev"
optional = false
python-versions = ">=3.5"

[package.dependencies]
Markdown = ">=3.2"

[[package]]
name = "pyyaml"
version = "5.3.1"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package]]
name = "regex"
version = "2020.10.23"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"

[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"

[[package]]
name = "tornado"
version = "6.0.4"
description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
category = "dev"
optional = false
python-versions = ">= 3.5"

[[package]]
name = "tqdm"
version = "4.51.0"
description = "Fast, Extensible Progress Meter"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*"

[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "argopt", "pydoc-markdown"]

[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "c46027b9272eba7119ccc2c0c942183ce1c22fd4133a39c794f78afebdcc6fb8"

[metadata.files]
click = [
    {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
    {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
future = [
    {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
]
jinja2 = [
    {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"},
    {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
]
joblib = [
    {file = "joblib-0.17.0-py3-none-any.whl", hash = "sha256:698c311779f347cf6b7e6b8a39bb682277b8ee4aba8cf9507bc0cf4cd4737b72"},
    {file = "joblib-0.17.0.tar.gz", hash = "sha256:9e284edd6be6b71883a63c9b7f124738a3c16195513ad940eae7e3438de885d5"},
]
livereload = [
    {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
]
lunr = [
    {file = "lunr-0.5.8-py2.py3-none-any.whl", hash = "sha256:aab3f489c4d4fab4c1294a257a30fec397db56f0a50273218ccc3efdbf01d6ca"},
    {file = "lunr-0.5.8.tar.gz", hash = "sha256:c4fb063b98eff775dd638b3df380008ae85e6cb1d1a24d1cd81a10ef6391c26e"},
]
markdown = [
    {file = "Markdown-3.3.3-py3-none-any.whl", hash = "sha256:c109c15b7dc20a9ac454c9e6025927d44460b85bd039da028d85e2b6d0bcc328"},
    {file = "Markdown-3.3.3.tar.gz", hash = "sha256:5d9f2b5ca24bc4c7a390d22323ca4bad200368612b5aaa7796babf971d2b2f18"},
]
markupsafe = [
    {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
    {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
    {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
    {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
    {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
    {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
    {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
    {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
    {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
    {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
    {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
    {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
    {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
    {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
    {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
    {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
    {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
    {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
    {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
    {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
    {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
    {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
    {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
    {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
    {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
    {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
    {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
    {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
    {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
    {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
mkdocs = [
    {file = "mkdocs-1.1.2-py3-none-any.whl", hash = "sha256:096f52ff52c02c7e90332d2e53da862fde5c062086e1b5356a6e392d5d60f5e9"},
    {file = "mkdocs-1.1.2.tar.gz", hash = "sha256:f0b61e5402b99d7789efa032c7a74c90a20220a9c81749da06dbfbcbd52ffb39"},
]
mkdocs-material = [
    {file = "mkdocs-material-6.1.0.tar.gz", hash = "sha256:a4bfd736898e6bfeb2c0d00ab448f464a389fb6a7dd2a30a90343756fd8aec83"},
    {file = "mkdocs_material-6.1.0-py2.py3-none-any.whl", hash = "sha256:81095982fc4d634ee4b16abb91fa7b2e1f50ab7e39df67aafa8f5d4fd7b28e24"},
]
mkdocs-material-extensions = [
    {file = "mkdocs-material-extensions-1.0.1.tar.gz", hash = "sha256:6947fb7f5e4291e3c61405bad3539d81e0b3cd62ae0d66ced018128af509c68f"},
    {file = "mkdocs_material_extensions-1.0.1-py3-none-any.whl", hash = "sha256:d90c807a88348aa6d1805657ec5c0b2d8d609c110e62b9dce4daf7fa981fa338"},
]
nltk = [
    {file = "nltk-3.5.zip", hash = "sha256:845365449cd8c5f9731f7cb9f8bd6fd0767553b9d53af9eb1b3abf7700936b35"},
]
pygments = [
    {file = "Pygments-2.7.2-py3-none-any.whl", hash = "sha256:88a0bbcd659fcb9573703957c6b9cff9fab7295e6e76db54c9d00ae42df32773"},
    {file = "Pygments-2.7.2.tar.gz", hash = "sha256:381985fcc551eb9d37c52088a32914e00517e57f4a21609f48141ba08e193fa0"},
]
pymdown-extensions = [
    {file = "pymdown-extensions-8.0.1.tar.gz", hash = "sha256:9ba704052d4bdc04a7cd63f7db4ef6add73bafcef22c0cf6b2e3386cf4ece51e"},
    {file = "pymdown_extensions-8.0.1-py2.py3-none-any.whl", hash = "sha256:a3689c04f4cbddacd9d569425c571ae07e2673cc4df63a26cdbf1abc15229137"},
]
pyyaml = [
    {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
    {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},
    {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"},
    {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"},
    {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"},
    {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"},
    {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"},
    {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"},
    {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"},
    {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"},
    {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"},
]
regex = [
    {file = "regex-2020.10.23-cp27-cp27m-win32.whl", hash = "sha256:781906e45ef1d10a0ed9ec8ab83a09b5e0d742de70e627b20d61ccb1b1d3964d"},
    {file = "regex-2020.10.23-cp27-cp27m-win_amd64.whl", hash = "sha256:8cd0d587aaac74194ad3e68029124c06245acaeddaae14cb45844e5c9bebeea4"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:af360e62a9790e0a96bc9ac845d87bfa0e4ee0ee68547ae8b5a9c1030517dbef"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e21340c07090ddc8c16deebfd82eb9c9e1ec5e62f57bb86194a2595fd7b46e0"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:e5f6aa56dda92472e9d6f7b1e6331f4e2d51a67caafff4d4c5121cadac03941e"},
    {file = "regex-2020.10.23-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c30d8766a055c22e39dd7e1a4f98f6266169f2de05db737efe509c2fb9c8a3c8"},
    {file = "regex-2020.10.23-cp36-cp36m-win32.whl", hash = "sha256:1a065e7a6a1b4aa851a0efa1a2579eabc765246b8b3a5fd74000aaa3134b8b4e"},
    {file = "regex-2020.10.23-cp36-cp36m-win_amd64.whl", hash = "sha256:c95d514093b80e5309bdca5dd99e51bcf82c44043b57c34594d9d7556bd04d05"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f4b1c65ee86bfbf7d0c3dfd90592a9e3d6e9ecd36c367c884094c050d4c35d04"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d62205f00f461fe8b24ade07499454a3b7adf3def1225e258b994e2215fd15c5"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b706c70070eea03411b1761fff3a2675da28d042a1ab7d0863b3efe1faa125c9"},
    {file = "regex-2020.10.23-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d43cf21df524283daa80ecad551c306b7f52881c8d0fe4e3e76a96b626b6d8d8"},
    {file = "regex-2020.10.23-cp37-cp37m-win32.whl", hash = "sha256:570e916a44a361d4e85f355aacd90e9113319c78ce3c2d098d2ddf9631b34505"},
    {file = "regex-2020.10.23-cp37-cp37m-win_amd64.whl", hash = "sha256:1c447b0d108cddc69036b1b3910fac159f2b51fdeec7f13872e059b7bc932be1"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8469377a437dbc31e480993399fd1fd15fe26f382dc04c51c9cb73e42965cc06"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:59d5c6302d22c16d59611a9fd53556554010db1d47e9df5df37be05007bebe75"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a973d5a7a324e2a5230ad7c43f5e1383cac51ef4903bf274936a5634b724b531"},
    {file = "regex-2020.10.23-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:97a023f97cddf00831ba04886d1596ef10f59b93df7f855856f037190936e868"},
    {file = "regex-2020.10.23-cp38-cp38-win32.whl", hash = "sha256:e289a857dca3b35d3615c3a6a438622e20d1bf0abcb82c57d866c8d0be3f44c4"},
    {file = "regex-2020.10.23-cp38-cp38-win_amd64.whl", hash = "sha256:0cb23ed0e327c18fb7eac61ebbb3180ebafed5b9b86ca2e15438201e5903b5dd"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c53dc8ee3bb7b7e28ee9feb996a0c999137be6c1d3b02cb6b3c4cba4f9e5ed09"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a46eba253cedcbe8a6469f881f014f0a98819d99d341461630885139850e281"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:240509721a663836b611fa13ca1843079fc52d0b91ef3f92d9bba8da12e768a0"},
    {file = "regex-2020.10.23-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f567df0601e9c7434958143aebea47a9c4b45434ea0ae0286a4ec19e9877169"},
    {file = "regex-2020.10.23-cp39-cp39-win32.whl", hash = "sha256:bfd7a9fddd11d116a58b62ee6c502fd24cfe22a4792261f258f886aa41c2a899"},
    {file = "regex-2020.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:1a511470db3aa97432ac8c1bf014fcc6c9fbfd0f4b1313024d342549cf86bcd6"},
    {file = "regex-2020.10.23.tar.gz", hash = "sha256:2278453c6a76280b38855a263198961938108ea2333ee145c5168c36b8e2b376"},
]
six = [
    {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
    {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
tornado = [
    {file = "tornado-6.0.4-cp35-cp35m-win32.whl", hash = "sha256:5217e601700f24e966ddab689f90b7ea4bd91ff3357c3600fa1045e26d68e55d"},
    {file = "tornado-6.0.4-cp35-cp35m-win_amd64.whl", hash = "sha256:c98232a3ac391f5faea6821b53db8db461157baa788f5d6222a193e9456e1740"},
    {file = "tornado-6.0.4-cp36-cp36m-win32.whl", hash = "sha256:5f6a07e62e799be5d2330e68d808c8ac41d4a259b9cea61da4101b83cb5dc673"},
    {file = "tornado-6.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c952975c8ba74f546ae6de2e226ab3cc3cc11ae47baf607459a6728585bb542a"},
    {file = "tornado-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:2c027eb2a393d964b22b5c154d1a23a5f8727db6fda837118a776b29e2b8ebc6"},
    {file = "tornado-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:5618f72e947533832cbc3dec54e1dffc1747a5cb17d1fd91577ed14fa0dc081b"},
    {file = "tornado-6.0.4-cp38-cp38-win32.whl", hash = "sha256:22aed82c2ea340c3771e3babc5ef220272f6fd06b5108a53b4976d0d722bcd52"},
    {file = "tornado-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c58d56003daf1b616336781b26d184023ea4af13ae143d9dda65e31e534940b9"},
    {file = "tornado-6.0.4.tar.gz", hash = "sha256:0fe2d45ba43b00a41cd73f8be321a44936dc1aba233dee979f17a042b83eb6dc"},
]
tqdm = [
    {file = "tqdm-4.51.0-py2.py3-none-any.whl", hash = "sha256:9ad44aaf0fc3697c06f6e05c7cf025dd66bc7bcb7613c66d85f4464c47ac8fad"},
    {file = "tqdm-4.51.0.tar.gz", hash = "sha256:ef54779f1c09f346b2b5a8e5c61f96fbcb639929e640e59f8cf810794f406432"},
]
% tox -r -e docs
.tox recreate: /Users/user/PycharmProjects/python-test/.tox/.tox
.tox installdeps: tox-poetry-installer>=0.3.0, tox >= 3.20.1
docs create: /Users/user/PycharmProjects/python-test/.tox/docs
_________________________________________________________________________________________ summary _________________________________________________________________________________________
  docs: commands succeeded
  congratulations :)
Traceback (most recent call last):
  File "/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.8/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.8/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/__main__.py", line 4, in <module>
    tox.cmdline()
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/session/__init__.py", line 44, in cmdline
    main(args)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/session/__init__.py", line 69, in main
    exit_code = session.runcommand()
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/session/__init__.py", line 197, in runcommand
    return self.subcommand_test()
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/session/__init__.py", line 225, in subcommand_test
    run_sequential(self.config, self.venv_dict)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/session/commands/run/sequential.py", line 9, in run_sequential
    if venv.setupenv():
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/venv.py", line 620, in setupenv
    status = self.update(action=action)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox/venv.py", line 275, in update
    self.hook.tox_testenv_install_deps(action=action, venv=self)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/pluggy/hooks.py", line 286, in __call__
    return self._hookexec(self, self.get_hookimpls(), kwargs)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/pluggy/manager.py", line 93, in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/pluggy/manager.py", line 84, in <lambda>
    self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/pluggy/callers.py", line 208, in _multicall
    return outcome.get_result()
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/pluggy/callers.py", line 80, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/pluggy/callers.py", line 187, in _multicall
    res = hook_impl.function(*args)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox_poetry_installer.py", line 361, in tox_testenv_install_deps
    _install_env_dependencies(venv, poetry, package_map)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox_poetry_installer.py", line 229, in _install_env_dependencies
    dependencies += _find_transients(packages, dep.name.lower())
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox_poetry_installer.py", line 198, in _find_transients
    return set(find_deps_of_deps(top_level.name))
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox_poetry_installer.py", line 195, in find_deps_of_deps
    transients += find_deps_of_deps(dep.name)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox_poetry_installer.py", line 195, in find_deps_of_deps
    transients += find_deps_of_deps(dep.name)
  File "/Users/user/PycharmProjects/python-test/.tox/.tox/lib/python3.8/site-packages/tox_poetry_installer.py", line 195, in find_deps_of_deps
    transients += find_deps_of_deps(dep.name)
  [Previous line repeated 978 more times]
RecursionError: maximum recursion depth exceeded

Case sensitive dependency names

Dependencies in the lockfile are always stored in lowercase, however the plugin does not enforce this when taking in the dependency list, like both pip and poetry do.

The plugin should convert dependency names to lowercase before trying to find them in the lockfile.

dev-dependencies not installing on python2

I'm trying to use tox-poetry-installer to support testing my py2/3 library with tox but the dev dependencies are apparently not installing under py2 when run via tox (but do when using poetry).

I know Python 2.7 has been dead for over 2 years (the requirement to continue support for it at the moment is out of my hands), but since the dependencies install correctly using poetry I think its reasonable to expect that they would install correctly using tox-poetry-installer

The following can be used to reproduce this issue:

  1. Setup a project directory:
$ mkdir -p grimlib/grimlib
$ touch grimlib/grimlib.__init__.py
$ touch grimlib/pyproject.toml
$ touch grimlib/tox.ini
  1. Populate tox.ini and pyproject.ini:

tox.ini:

[tox]
isolated_build = true
envlist = {py27,py3}
requires =
    tox-poetry-installer[poetry] == 0.8.3

[testenv]
require_locked_deps = true
install_dev_deps = true

commands =
    python -V
    python -m pylint --version

pyproject.toml:

[tool.poetry]
name = "grimlib"
version = "1.0.0" 
description = "poetry test"
authors = ["GrimHacker"]

[tool.poetry.dependencies]
python = "^2.7 || ^3.7"

[tool.poetry.dev-dependencies]
pylint = [
    { version = "<=1.9.5", python = "~2.7" },  # dropped py2 after 1.9.5
    { version = ">=2,<3", python = "^3.7" }  # 2.0.0 first version with py3.7
]
pytest = [
    { version = "<5", python = "~2.7" },  # dropped py2 in 5.0
    { version = "<8", python = "^3.7" }
]
lazy-object-proxy = { version = "!=1.7.0"}  # Dep of astroid from pylint. 7.0.0 was yanked but is being selected by poetry

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
  1. Lock the dependencies
$ poetry lock
Updating dependencies
Resolving dependencies... (18.5s)

Writing lock file
  1. Run tox and observe the InvocationError under py2.
$ tox -r
.tox recreate: /home/grimhacker/foo/grimlib/.tox/.tox
.tox installdeps: tox-poetry-installer[poetry] == 0.8.3, tox >= 3.24.5
.package recreate: /home/grimhacker/foo/grimlib/.tox/.package
.package installdeps: poetry-core>=1.0.0
py27 recreate: /home/grimhacker/foo/grimlib/.tox/py27
py27 tox-poetry-installer: Installing 1 dependencies from Poetry lock file (using 10 threads)
py27 inst: /home/grimhacker/foo/grimlib/.tox/.tmp/package/1/grimlib-1.0.0.tar.gz
py27 installed: DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.,grimlib @ file:///home/grimhacker/foo/grimlib/.tox/.tmp/package/1/grimlib-1.0.0.tar.gz,lazy-object-proxy==1.6.0
py27 run-test-pre: PYTHONHASHSEED='2565277340'
py27 run-test: commands[0] | python -V
Python 2.7.17
py27 run-test: commands[1] | python -m pylint --version
/home/grimhacker/foo/grimlib/.tox/py27/bin/python: No module named pylint
ERROR: InvocationError for command /home/grimhacker/foo/grimlib/.tox/py27/bin/python -m pylint --version (exited with code 1)

py3 recreate: /home/grimhacker/foo/grimlib/.tox/py3
py3 tox-poetry-installer: Installing 18 dependencies from Poetry lock file (using 10 threads)
py3 inst: /home/grimhacker/foo/grimlib/.tox/.tmp/package/1/grimlib-1.0.0.tar.gz
py3 installed: astroid==2.9.3,attrs==21.4.0,grimlib @ file:///home/grimhacker/foo/grimlib/.tox/.tmp/package/1/grimlib-1.0.0.tar.gz,iniconfig==1.1.1,isort==5.10.1,lazy-object-proxy==1.6.0,mccabe==0.6.1,packaging==21.3,platformdirs==2.5.1,pluggy==1.0.0,py==1.11.0,pylint==2.12.2,pyparsing==3.0.7,pytest==7.0.1,toml==0.10.2,tomli==2.0.1,typing_extensions==4.1.1,wrapt==1.14.0
py3 run-test-pre: PYTHONHASHSEED='2565277340'
py3 run-test: commands[0] | python -V
Python 3.8.0
py3 run-test: commands[1] | python -m pylint --version
pylint 2.12.2
astroid 2.9.3
Python 3.8.0 (default, Dec  9 2021, 17:53:27) 
[GCC 8.4.0]
______________________________________________________ summary _______________________________________________________
ERROR:   py27: commands failed
  py3: commands succeeded
  1. Use poetry to create a python2.7 virtualenv, install the dependencies, and run python -m pylint --version to show the error does not occur:
$ poetry env use python2.7
...
$ poetry install
Installing dependencies from lock file

Package operations: 14 installs, 14 updates, 0 removals

  • Updating contextlib2 (0.6.0.post1 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 0.6.0.post1)
  • Updating scandir (1.10.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 1.10.0)
  • Updating six (1.16.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 1.16.0)
  • Updating typing (3.10.0.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 3.10.0.0)
  • Updating configparser (4.0.2 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 4.0.2)
  • Updating pathlib2 (2.3.7.post1 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 2.3.7.post1)
  • Updating zipp (1.2.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 1.2.0)
  • Updating backports.functools-lru-cache (1.6.4 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 1.6.4)
  • Updating enum34 (1.1.10 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 1.1.10)
  • Updating futures (3.3.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 3.3.0)
  • Updating importlib-metadata (2.1.3 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 2.1.3)
  • Installing lazy-object-proxy (1.6.0)
  • Updating pyparsing (2.4.7 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 2.4.7)
  • Updating singledispatch (3.7.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 3.7.0)
  • Updating wrapt (1.14.0 /home/grimhacker/.cache/pypoetry/virtualenvs/grimlib-4i1phUPA-py2.7/lib/python2.7/site-packages -> 1.14.0)
  • Installing astroid (1.6.6)
  • Installing atomicwrites (1.4.0)
  • Installing attrs (21.4.0)
  • Installing funcsigs (1.0.2)
  • Installing isort (4.3.21)
  • Installing mccabe (0.6.1)
  • Installing more-itertools (5.0.0)
  • Installing packaging (20.9)
  • Installing pluggy (0.13.1)
  • Installing py (1.11.0)
  • Installing wcwidth (0.2.5)
  • Installing pylint (1.9.5)
  • Installing pytest (4.6.11)

$ poetry run python -m pylint --version
No config file found, using default configuration
__main__.py 1.9.5, 
astroid 1.6.6
Python 2.7.17 (default, Feb 27 2021, 15:10:58) 
[GCC 7.5.0]

setuptools prevents installation of packages that should be installable

Hello!
First of all, thank you so much for writing this. It's great: it requires very minimal tox.ini changes, and the changes that are necessary are well-documented and straightforward. 👍

Problem:
Occasionally, tox-poetry-installer cannot install some locked dependencies that have setuptools is a subdependency.

Expected result:
Setuptools is skipped, and tox-poetry-installer continues to the next locked dependency.

Actual result:
A LockedDepNotFoundError is thrown, stopping installation of all locked dependencies and causing tox to fail.

Potential cause:

  • find_deps_of_deps in utilities.py tries to access packages[name] before it checks for unsafe packages
  • the unsafe package is not in packages, and it throws a KeyError

Potential solution:
Move the packages[name] line to be after the check for unsafe packages. For example:

diff --git a/tox_poetry_installer/utilities.py b/tox_poetry_installer/utilities.py
index c753a75..e17a318 100644
--- a/tox_poetry_installer/utilities.py
+++ b/tox_poetry_installer/utilities.py
@@ -65,8 +65,6 @@ def find_transients(packages: PackageMap, dependency_name: str) -> Set[PoetryPac
     try:
 
         def find_deps_of_deps(name: str, searched: Set[str]) -> PackageMap:
-            package = packages[name]
-            transients: PackageMap = {}
             searched.update([name])
 
             if name in _poetry.Provider.UNSAFE_PACKAGES:
@@ -74,15 +72,23 @@ def find_transients(packages: PackageMap, dependency_name: str) -> Set[PoetryPac
                     f"{constants.REPORTER_PREFIX} Installing package '{name}' using Poetry is not supported; skipping installation of package '{name}'"
                 )
                 reporter.verbosity2(
-                    f"{constants.REPORTER_PREFIX} Skip {package}: designated unsafe by Poetry"
+                    f"{constants.REPORTER_PREFIX} Skip package '{name}': designated unsafe by Poetry"
                 )
-            elif not package.python_constraint.allows(constants.PLATFORM_VERSION):
+                return {}
+
+            # this needs to go _after_ the check for unsafe packages
+            # otherwise, if `name` is an unsafe package, it won't be in the
+            # PackageMap and will throw a KeyError
+            package = packages[name]
+            transients: PackageMap = {}
+
+            if not package.python_constraint.allows(constants.PLATFORM_VERSION):
                 reporter.verbosity2(
-                    f"{constants.REPORTER_PREFIX} Skip {package}: incompatible Python requirement '{package.python_constraint}' for current version '{constants.PLATFORM_VERSION}'"
+                    f"{constants.REPORTER_PREFIX} Skip package '{name}': incompatible Python requirement '{package.python_constraint}' for current version '{constants.PLATFORM_VERSION}'"
                 )
             elif package.platform is not None and package.platform != sys.platform:
                 reporter.verbosity2(
-                    f"{constants.REPORTER_PREFIX} Skip {package}: incompatible platform requirement '{package.platform}' for current platform '{sys.platform}'"
+                    f"{constants.REPORTER_PREFIX} Skip package '{name}': incompatible platform requirement '{package.platform}' for current platform '{sys.platform}'"
                 )
             else:
                 reporter.verbosity2(f"{constants.REPORTER_PREFIX} Include {package}")

Add a Changelog

It would be very helpful if there was a changelog that noted the various fixes and changes for the past few releases and new ones going forward.

Thanks!

tox-poetry-installer crashes due to import error

Hey,

we released poetry1.2.0 and poetry-core 1.1.0 today 🎉 Unfortunately this breaks tox-poetry-installer due to changed import paths.

Most of them can be fixed easily:

from poetry.core.packages import Package as PoetryPackage

from poetry.core.packages.package import Package as PoetryPackage 

and

from poetry.core.packages import Dependency as PoetryDependency
from poetry.core.packages import Package as PoetryPackage

from poetry.core.packages.dependency import Dependency as PoetryDependency
from poetry.core.packages.package import Package as PoetryPackage

But I'm not sure what's the best way to fix

from poetry.io.null_io import NullIO

This is not available anymore in poetry 1.2.0. Instead it's available in the most recent version of cleo:

from cleo.io.null_io import NullIO

While the easy changes are compatible with poetry < 1.2, the change of NullIO import isn't. Maybe just put another try/except in here to find put what's available?

fin swimmer

AttributeError: 'Config' object has no attribute 'isolated_build_env'

I'm getting the following error when calling tox:

➜ tox
.tox create: /Users/[email protected]/Documents/repos/sheetwork/.tox/.tox
Traceback (most recent call last):
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/bin/tox", line 8, in <module>
    sys.exit(cmdline())
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/session/__init__.py", line 44, in cmdline
    main(args)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/session/__init__.py", line 69, in main
    exit_code = session.runcommand()
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/session/__init__.py", line 187, in runcommand
    return provision_tox(provision_tox_venv, self.config.args)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/session/commands/provision.py", line 10, in provision_tox
    ensure_meta_env_up_to_date(provision_venv)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/session/commands/provision.py", line 24, in ensure_meta_env_up_to_date
    if provision_venv.setupenv():
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/venv.py", line 628, in setupenv
    status = self.update(action=action)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox/venv.py", line 275, in update
    self.hook.tox_testenv_install_deps(action=action, venv=self)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/pluggy/hooks.py", line 286, in __call__
    return self._hookexec(self, self.get_hookimpls(), kwargs)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/pluggy/manager.py", line 93, in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/pluggy/manager.py", line 84, in <lambda>
    self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/pluggy/callers.py", line 208, in _multicall
    return outcome.get_result()
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/pluggy/callers.py", line 80, in get_result
    raise ex[1].with_traceback(ex[2])
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/pluggy/callers.py", line 187, in _multicall
    res = hook_impl.function(*args)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox_poetry_installer/hooks.py", line 73, in tox_testenv_install_deps
    poetry = utilities.check_preconditions(venv, action)
  File "/Users/[email protected]/Library/Caches/pypoetry/virtualenvs/sheetwork-H1mpejJG-py3.8/lib/python3.8/site-packages/tox_poetry_installer/utilities.py", line 139, in check_preconditions
    if action.name == venv.envconfig.config.isolated_build_env:
AttributeError: 'Config' object has no attribute 'isolated_build_env'

my tox config looks like this:

[tox]
envlist = {py3611,py37,py38}-{cover,nocov}-{linux,windows}
isolated_build=true
requires =
    tox-poetry-dev-dependencies
    tox-wheel

[gh-actions]
python =
    3.8: py38
    3.6: py3611
    3.7: py37

[gh-actions:env]
PLATFORM =
    ubuntu-latest: linux
    windows-latest: windows

[testenv]
poetry_experimental_add_locked_dependencies = True
poetry_add_dev_dependencies = True
wheel =
    cover: false
    nocover: true
setenv =
    APPDATA = "''"
whitelist_externals = echo
commands =
    echo {env:APPDATA}
    pytest

[testenv:package]
skip_install = True
deps =
    twine
commands =
    python3 -m poetry build
    python3 -m twine check dist/*

Am I potentially doing something bad in my config? I tried looking for similar error messages but couldn't find anything with isolated_build_env in there.

`poetry export --with-credentials` fails under tox with tox-poetry-installer

I used poetry config http-basic.private-index __token__ TOKEN to store credentials for a private package index. I have a tox testenv that runs poetry export --with-credentials to generate a requirements file. When the testenv runs poetry export --with-credentials under tox, it prints the following message:

No suitable keyring backends were found
Using a plaintext file to store and retrieve credentials

The resulting output contains the username part of the index credentials but is missing the password part:

--extra-index-url https://__token__:@example.com/pypi/simple

When I run poetry export --with-credentials on the command line, poetry doesn't print the warning and its output contains both the username and password for the index, like this:

--extra-index-url https://__token__:[email protected]/pypi/simple

Should I expect to be able to run poetry export --with-credentials under tox with tox-poetry-installer installed? Here is some information I thought would be helpful in diagnosing my problem. I'm happy to provide other details if needed.

~/.poetry/bin/poetry is the copy of poetry that the testenv runs -- I checked with which poetry in the testenv. I'm not sure if it's relevant, but running .tox/.tox/bin/poetry export --with-credentials on the command line also produces the correct output with the username and password present.

Poetry version: 1.1.15
tox version: 3.25.1
Poetry installation method:

curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3.9 -

tox.ini:

[tox]
minversion = 3.3.0
isolated_build = True
requires = tox-poetry-installer[poetry] == 0.8.4

[testenv]
sitepackages = False
require_poetry = True
require_locked_deps = True

[testenv:export]
skip_install = True
allowlist_externals = poetry
commands = poetry export --with-credentials

Relevant portion of pyproject.toml:

[[tool.poetry.source]]
name = "private-index"
url = "https://example.com/pypi/simple"
secondary = true

poetry.toml:

[repositories]
[repositories.private-index]
url = "https://example.com/pypi/simple"

Properly integrate with Tox logging/reporter functionality

I suspect that this plugin is not using Tox's logging/reporter functionality properly. I suspect this because I haven't actually looked at how to do it properly. The current usage of Tox's reporter module in the plugin is trivially functional, which is all I needed, but it should be updated to log to the proper environment logger.

I need to take a closer look at Tox's logging/reporter functionality and find a better way to log the main display messages (currently the reporter.verbosity0(...) calls in the plugin). I haven't been able to find anything in the Tox documentation about the logging model so I expect other plugins and the source for tox.reporter will be the best resources.

Move require-poetry option to the tox config

The --require-poetry runtime option makes more sense as a config option. A given config is more likely to need poetry to run properly than a given run of tox is.

The --require-poetry option should be renamed to require_poetry and added to the envconfig object, with a default value of False.

The --require-poetry option should be kept, but if it is used then a deprecation message should be sent to the console. The feature should be targeted for removal in the 1.0 release.

Mitigate "UNSAFE_DEPENDENCIES" issue

This issue is related to finding a work around to python-poetry/poetry#1584. If the noted functionality is removed so that the "unsafe" packages can be installed to the lockfile then no further work is needed then this issue can be closed. However in the meantime a workaround is needed.

Currently the behaviour of the plugin when encountering a dependency Poetry classifies as unsafe is to a) log a warning indicating that it will not be installed and b) skips it without installing. This is a disaster waiting to happen.

I believe that the best way to solve this problem (which admittedly is still not a great solution) is to hot-load dependencies into the Tox environment config to be installed using the legacy backend. Even though Poetry provides the version of an unsafe dependency in the Package().requires attribute of a package that requires it, the plugin doesn't have any information about the dependencies of the unsafe packages because they aren't in the lockfile. If, for example, Tox installs setuptools==1.2.3 by default, a dependency requires setuptools==2.3.4 and the dependencies of the newer version are different (not unreasonable for a major version bump) then those updated dependencies cannot be installed by the plugin.

The most approachable solution is to add these as unlocked dependencies to the Tox environment config so that the default backend can install them and their dependencies. This is dangerous as it exposes the environment to all the problems this plugin is designed to solve, but it does solve trivial functionality issues for packages that depend on these packages Poetry has classified as unsafe.

Hopefully a more robust solution will be made upstream eventually.

Provide better information about poetry command failure

It looks like if poetry fails for any reason, you just get:

tox-poetry-installer: Project does not use Poetry for env management, skipping installation of locked dependencies

This makes it really difficult to see what's going on. #1 seems to have changed this handling to just blindly capture all errors. If some indication of what went wrong could be provided, that would be very useful.

In my case, the problem was my poetry config wasn't compatible with v1.4.2, but outside of tox I'm using v1.6.1 - so tox-poetry-installer was using v1.4.2, failing on the invalid config, and deciding "does not use Poetry for env management"

Packages with missing and unbuildable wheels silently not installed when `skipsdist=true` is set

When skipsdist = true is set in tox.ini and one of the required packages is missing a wheel and that wheel can't be built on the spot, it just silently doesn't get installed.

One curious thing is that this can be partially fixed while keeping skipsdist = true by doing tox --parallel-install-threads 0, which causes tox to print out a success message, but then subsequently print out the failed wheel build's log and return 1 as the exitcode.

Here's a minimal reproduction I created: https://github.com/benbariteau/tox-poetry-installer-parallel-install-bug-repro

Unable to install project deps with skipsdist = true

Using the following project:

[tox]
skipsdist = true
requires =
    tox-poetry-installer[poetry] == 0.8.0

[testenv]
install_project_deps = true
commands =
    http --help
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "poetry-test"
version = "0.1.0"
description = ""
authors = ["Your Name <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.9"
httpie = "^2.4.0"

[tool.poetry.dev-dependencies]
pytest = "^6.2.4"

Creating the testenv does not install the project dependencies (in this case httpie):

tox -e py39
.tox recreate: .tox/.tox
.tox installdeps: tox-poetry-installer[poetry] == 0.8.0, tox >= 3.23.1
py39 recreate: .tox/py39
py39 tox-poetry-installer: Installing 0 dependencies from Poetry lock file (using 10 threads)
py39 run-test-pre: PYTHONHASHSEED='2607249235'
py39 run-test: commands[0] | http --help
WARNING: test command found but not installed in testenv

The README for this project says that install_project_deps defaults to True. Even setting it manually does not install them.

Unhandled ModuleNotFoundError when installing without the [poetry] extra

The package explicitly depends on the poetry-core package which uses some trickery to be importable under the poetry.core namespace. This means that the principle import of import poetry works without the poetry package being installed.

This impacts us because we detect weather Poetry is installed based on whether poetry is importable. However, if poetry is not installed but poetry-core is then import poetry works but import poetry.config (or any other module other than poetry.core) fails with a ModuleNotFoundError. A patch can be made to avoid this problem by adding ModuleNotFoundError to the try/except in tox_poetry_installer._poetry, however this may be worth rethinking as part of #2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.