Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
python-poetry
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
open
python-poetry
Commits
2ed53be4
Unverified
Commit
2ed53be4
authored
Apr 26, 2019
by
Sébastien Eustace
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix root package installation with pip>=19.0
parent
457e2205
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
309 additions
and
22 deletions
+309
-22
.gitignore
+1
-0
CHANGELOG.md
+1
-0
poetry/console/commands/install.py
+4
-17
poetry/masonry/builders/__init__.py
+1
-0
poetry/masonry/builders/builder.py
+1
-1
poetry/masonry/builders/editable.py
+127
-0
poetry/utils/env.py
+44
-0
tests/masonry/builders/test_editable.py
+130
-0
tests/masonry/builders/test_wheel.py
+0
-4
No files found.
.gitignore
View file @
2ed53be4
...
@@ -35,3 +35,4 @@ MANIFEST.in
...
@@ -35,3 +35,4 @@ MANIFEST.in
.venv
.venv
/releases/*
/releases/*
pip-wheel-metadata
CHANGELOG.md
View file @
2ed53be4
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
### Fixed
### Fixed
-
Fixed root package installation with
`pip>=19.0`
.
-
Fixed packages not being removed after using the
`remove`
command.
-
Fixed packages not being removed after using the
`remove`
command.
...
...
poetry/console/commands/install.py
View file @
2ed53be4
...
@@ -28,10 +28,8 @@ exist it will look for <comment>pyproject.toml</> and do the same.
...
@@ -28,10 +28,8 @@ exist it will look for <comment>pyproject.toml</> and do the same.
def
handle
(
self
):
def
handle
(
self
):
from
poetry.installation
import
Installer
from
poetry.installation
import
Installer
from
poetry.io
import
NullIO
from
poetry.io
import
NullIO
from
poetry.masonry.builders
import
Sdist
Builder
from
poetry.masonry.builders
import
Editable
Builder
from
poetry.masonry.utils.module
import
ModuleOrPackageNotFound
from
poetry.masonry.utils.module
import
ModuleOrPackageNotFound
from
poetry.utils._compat
import
decode
from
poetry.utils.env
import
NullEnv
installer
=
Installer
(
installer
=
Installer
(
self
.
output
,
self
.
output
,
...
@@ -60,7 +58,7 @@ exist it will look for <comment>pyproject.toml</> and do the same.
...
@@ -60,7 +58,7 @@ exist it will look for <comment>pyproject.toml</> and do the same.
return
return_code
return
return_code
try
:
try
:
builder
=
SdistBuilder
(
self
.
poetry
,
NullEnv
()
,
NullIO
())
builder
=
EditableBuilder
(
self
.
poetry
,
self
.
_env
,
NullIO
())
except
ModuleOrPackageNotFound
:
except
ModuleOrPackageNotFound
:
# This is likely due to the fact that the project is an application
# This is likely due to the fact that the project is an application
# not following the structure expected by Poetry
# not following the structure expected by Poetry
...
@@ -76,17 +74,6 @@ exist it will look for <comment>pyproject.toml</> and do the same.
...
@@ -76,17 +74,6 @@ exist it will look for <comment>pyproject.toml</> and do the same.
if
self
.
option
(
"dry-run"
):
if
self
.
option
(
"dry-run"
):
return
0
return
0
setup
=
self
.
poetry
.
file
.
parent
/
"setup.py"
builder
.
build
()
has_setup
=
setup
.
exists
()
if
has_setup
:
return
0
self
.
line
(
"<warning>A setup.py file already exists. Using it.</warning>"
)
else
:
with
setup
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
decode
(
builder
.
build_setup
()))
try
:
self
.
env
.
run
(
"pip"
,
"install"
,
"-e"
,
str
(
setup
.
parent
),
"--no-deps"
)
finally
:
if
not
has_setup
:
os
.
remove
(
str
(
setup
))
poetry/masonry/builders/__init__.py
View file @
2ed53be4
from
.complete
import
CompleteBuilder
from
.complete
import
CompleteBuilder
from
.editable
import
EditableBuilder
from
.sdist
import
SdistBuilder
from
.sdist
import
SdistBuilder
from
.wheel
import
WheelBuilder
from
.wheel
import
WheelBuilder
poetry/masonry/builders/builder.py
View file @
2ed53be4
...
@@ -35,7 +35,7 @@ class Builder(object):
...
@@ -35,7 +35,7 @@ class Builder(object):
AVAILABLE_PYTHONS
=
{
"2"
,
"2.7"
,
"3"
,
"3.4"
,
"3.5"
,
"3.6"
,
"3.7"
}
AVAILABLE_PYTHONS
=
{
"2"
,
"2.7"
,
"3"
,
"3.4"
,
"3.5"
,
"3.6"
,
"3.7"
}
def
__init__
(
self
,
poetry
,
env
,
io
):
def
__init__
(
self
,
poetry
,
env
,
io
):
# type: (Poetry, Env, IO) -> None
self
.
_poetry
=
poetry
self
.
_poetry
=
poetry
self
.
_env
=
env
self
.
_env
=
env
self
.
_io
=
io
self
.
_io
=
io
...
...
poetry/masonry/builders/editable.py
View file @
2ed53be4
from
__future__
import
unicode_literals
import
os
import
shutil
from
collections
import
defaultdict
from
poetry.semver.version
import
Version
from
poetry.utils._compat
import
decode
from
.builder
import
Builder
from
.sdist
import
SdistBuilder
class
EditableBuilder
(
Builder
):
def
build
(
self
):
if
self
.
_package
.
build
:
# If the project has some kind of special
# build needs we delegate to the setup.py file
return
self
.
_setup_build
()
self
.
_build_egg_info
()
self
.
_build_egg_link
()
self
.
_add_easy_install_entry
()
def
_setup_build
(
self
):
builder
=
SdistBuilder
(
self
.
_poetry
,
self
.
_env
,
self
.
_io
)
setup
=
self
.
_path
/
"setup.py"
has_setup
=
setup
.
exists
()
if
has_setup
:
self
.
_io
.
write_line
(
"<warning>A setup.py file already exists. Using it.</warning>"
)
else
:
with
setup
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
decode
(
builder
.
build_setup
()))
try
:
if
self
.
_env
.
pip_version
<
Version
(
19
,
0
):
self
.
_env
.
run
(
"python"
,
"-m"
,
"pip"
,
"install"
,
"-e"
,
str
(
self
.
_path
))
else
:
# Temporarily rename pyproject.toml
shutil
.
move
(
str
(
self
.
_poetry
.
file
),
str
(
self
.
_poetry
.
file
.
with_suffix
(
".tmp"
))
)
try
:
self
.
_env
.
run
(
"python"
,
"-m"
,
"pip"
,
"install"
,
"-e"
,
str
(
self
.
_path
)
)
finally
:
shutil
.
move
(
str
(
self
.
_poetry
.
file
.
with_suffix
(
".tmp"
)),
str
(
self
.
_poetry
.
file
),
)
finally
:
if
not
has_setup
:
os
.
remove
(
str
(
setup
))
def
_build_egg_info
(
self
):
egg_info
=
self
.
_path
/
"{}.egg-info"
.
format
(
self
.
_package
.
name
.
replace
(
"-"
,
"_"
)
)
egg_info
.
mkdir
(
exist_ok
=
True
)
with
egg_info
.
joinpath
(
"PKG-INFO"
)
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
decode
(
self
.
get_metadata_content
()))
with
egg_info
.
joinpath
(
"entry_points.txt"
)
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
entry_points
=
self
.
convert_entry_points
()
for
group_name
in
sorted
(
entry_points
):
f
.
write
(
"[{}]
\n
"
.
format
(
group_name
))
for
ep
in
sorted
(
entry_points
[
group_name
]):
f
.
write
(
ep
.
replace
(
" "
,
""
)
+
"
\n
"
)
f
.
write
(
"
\n
"
)
with
egg_info
.
joinpath
(
"requires.txt"
)
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
self
.
_generate_requires
())
def
_build_egg_link
(
self
):
egg_link
=
self
.
_env
.
site_packages
/
"{}.egg-link"
.
format
(
self
.
_package
.
name
)
with
egg_link
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
str
(
self
.
_poetry
.
file
.
parent
.
resolve
())
+
"
\n
"
)
f
.
write
(
"."
)
def
_add_easy_install_entry
(
self
):
easy_install_pth
=
self
.
_env
.
site_packages
/
"easy-install.pth"
path
=
str
(
self
.
_poetry
.
file
.
parent
.
resolve
())
content
=
""
if
easy_install_pth
.
exists
():
with
easy_install_pth
.
open
(
encoding
=
"utf-8"
)
as
f
:
content
=
f
.
read
()
if
path
in
content
:
return
content
+=
"{}
\n
"
.
format
(
path
)
with
easy_install_pth
.
open
(
"w"
,
encoding
=
"utf-8"
)
as
f
:
f
.
write
(
content
)
def
_generate_requires
(
self
):
extras
=
defaultdict
(
list
)
requires
=
""
for
dep
in
sorted
(
self
.
_package
.
requires
,
key
=
lambda
d
:
d
.
name
):
marker
=
dep
.
marker
if
marker
.
is_any
():
requires
+=
"{}
\n
"
.
format
(
dep
.
base_pep_508_name
)
continue
extras
[
str
(
marker
)]
.
append
(
dep
.
base_pep_508_name
)
if
extras
:
requires
+=
"
\n
"
for
marker
,
deps
in
sorted
(
extras
.
items
()):
requires
+=
"[:{}]
\n
"
.
format
(
marker
)
for
dep
in
deps
:
requires
+=
dep
+
"
\n
"
requires
+=
"
\n
"
return
requires
poetry/utils/env.py
View file @
2ed53be4
import
json
import
json
import
os
import
os
import
platform
import
platform
import
re
import
subprocess
import
subprocess
import
sys
import
sys
import
sysconfig
import
sysconfig
...
@@ -15,6 +16,7 @@ from typing import Tuple
...
@@ -15,6 +16,7 @@ from typing import Tuple
from
poetry.config
import
Config
from
poetry.config
import
Config
from
poetry.locations
import
CACHE_DIR
from
poetry.locations
import
CACHE_DIR
from
poetry.semver.version
import
Version
from
poetry.utils._compat
import
Path
from
poetry.utils._compat
import
Path
from
poetry.utils._compat
import
decode
from
poetry.utils._compat
import
decode
from
poetry.utils._compat
import
encode
from
poetry.utils._compat
import
encode
...
@@ -114,6 +116,7 @@ class Env(object):
...
@@ -114,6 +116,7 @@ class Env(object):
self
.
_base
=
base
or
path
self
.
_base
=
base
or
path
self
.
_marker_env
=
None
self
.
_marker_env
=
None
self
.
_pip_version
=
None
@property
@property
def
path
(
self
):
# type: () -> Path
def
path
(
self
):
# type: () -> Path
...
@@ -152,6 +155,25 @@ class Env(object):
...
@@ -152,6 +155,25 @@ class Env(object):
"""
"""
return
self
.
_bin
(
"pip"
)
return
self
.
_bin
(
"pip"
)
@property
def
pip_version
(
self
):
if
self
.
_pip_version
is
None
:
self
.
_pip_version
=
self
.
get_pip_version
()
return
self
.
_pip_version
@property
def
site_packages
(
self
):
# type: () -> Path
if
self
.
_is_windows
:
return
self
.
_path
/
"Lib"
/
"site-packages"
return
(
self
.
_path
/
"lib"
/
"python{}.{}"
.
format
(
*
self
.
version_info
[:
2
])
/
"site-packages"
)
@classmethod
@classmethod
def
get
(
cls
,
cwd
,
reload
=
False
):
# type: (Path, bool) -> Env
def
get
(
cls
,
cwd
,
reload
=
False
):
# type: (Path, bool) -> Env
if
cls
.
_env
is
not
None
and
not
reload
:
if
cls
.
_env
is
not
None
and
not
reload
:
...
@@ -308,6 +330,9 @@ class Env(object):
...
@@ -308,6 +330,9 @@ class Env(object):
def
config_var
(
self
,
var
):
# type: (str) -> Any
def
config_var
(
self
,
var
):
# type: (str) -> Any
raise
NotImplementedError
()
raise
NotImplementedError
()
def
get_pip_version
(
self
):
# type: () -> Version
raise
NotImplementedError
()
def
is_valid_for_marker
(
self
,
marker
):
# type: (BaseMarker) -> bool
def
is_valid_for_marker
(
self
,
marker
):
# type: (BaseMarker) -> bool
return
marker
.
validate
(
self
.
marker_env
)
return
marker
.
validate
(
self
.
marker_env
)
...
@@ -424,6 +449,11 @@ class SystemEnv(Env):
...
@@ -424,6 +449,11 @@ class SystemEnv(Env):
return
return
def
get_pip_version
(
self
):
# type: () -> Version
from
pip
import
__version__
return
Version
.
parse
(
__version__
)
def
is_venv
(
self
):
# type: () -> bool
def
is_venv
(
self
):
# type: () -> bool
return
self
.
_path
!=
self
.
_base
return
self
.
_path
!=
self
.
_base
...
@@ -474,6 +504,14 @@ class VirtualEnv(Env):
...
@@ -474,6 +504,14 @@ class VirtualEnv(Env):
return
value
return
value
def
get_pip_version
(
self
):
# type: () -> Version
output
=
self
.
run
(
"python"
,
"-m"
,
"pip"
,
"--version"
)
.
strip
()
m
=
re
.
match
(
"pip (.+?)(?: from .+)?$"
,
output
)
if
not
m
:
return
Version
.
parse
(
"0.0"
)
return
Version
.
parse
(
m
.
group
(
1
))
def
is_venv
(
self
):
# type: () -> bool
def
is_venv
(
self
):
# type: () -> bool
return
True
return
True
...
@@ -546,6 +584,7 @@ class MockEnv(NullEnv):
...
@@ -546,6 +584,7 @@ class MockEnv(NullEnv):
platform
=
"darwin"
,
platform
=
"darwin"
,
os_name
=
"posix"
,
os_name
=
"posix"
,
is_venv
=
False
,
is_venv
=
False
,
pip_version
=
"19.1"
,
**
kwargs
**
kwargs
):
):
super
(
MockEnv
,
self
)
.
__init__
(
**
kwargs
)
super
(
MockEnv
,
self
)
.
__init__
(
**
kwargs
)
...
@@ -555,6 +594,7 @@ class MockEnv(NullEnv):
...
@@ -555,6 +594,7 @@ class MockEnv(NullEnv):
self
.
_platform
=
platform
self
.
_platform
=
platform
self
.
_os_name
=
os_name
self
.
_os_name
=
os_name
self
.
_is_venv
=
is_venv
self
.
_is_venv
=
is_venv
self
.
_pip_version
=
Version
.
parse
(
pip_version
)
@property
@property
def
version_info
(
self
):
# type: () -> Tuple[int]
def
version_info
(
self
):
# type: () -> Tuple[int]
...
@@ -572,5 +612,9 @@ class MockEnv(NullEnv):
...
@@ -572,5 +612,9 @@ class MockEnv(NullEnv):
def
os
(
self
):
# type: () -> str
def
os
(
self
):
# type: () -> str
return
self
.
_os_name
return
self
.
_os_name
@property
def
pip_version
(
self
):
return
self
.
_pip_version
def
is_venv
(
self
):
# type: () -> bool
def
is_venv
(
self
):
# type: () -> bool
return
self
.
_is_venv
return
self
.
_is_venv
tests/masonry/builders/test_editable.py
0 → 100644
View file @
2ed53be4
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
poetry.io
import
NullIO
from
poetry.masonry.builders
import
EditableBuilder
from
poetry.poetry
import
Poetry
from
poetry.utils._compat
import
Path
from
poetry.utils.env
import
MockEnv
fixtures_dir
=
Path
(
__file__
)
.
parent
/
"fixtures"
def
test_build_pure_python_package
(
tmp_dir
):
tmp_dir
=
Path
(
tmp_dir
)
env
=
MockEnv
(
path
=
tmp_dir
)
env
.
site_packages
.
mkdir
(
parents
=
True
)
module_path
=
fixtures_dir
/
"complete"
builder
=
EditableBuilder
(
Poetry
.
create
(
module_path
),
env
,
NullIO
())
builder
.
_path
=
tmp_dir
builder
.
build
()
egg_info
=
tmp_dir
/
"my_package.egg-info"
assert
egg_info
.
exists
()
entry_points
=
"""
\
[console_scripts]
extra-script=my_package.extra:main[time]
my-2nd-script=my_package:main2
my-script=my_package:main
"""
pkg_info
=
"""
\
Metadata-Version: 2.1
Name: my-package
Version: 1.2.3
Summary: Some description.
Home-page: https://poetry.eustace.io/
License: MIT
Keywords: packaging,dependency,poetry
Author: Sébastien Eustace
Author-email: sebastien@eustace.io
Requires-Python: >=3.6,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Provides-Extra: time
Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0)
Requires-Dist: cleo (>=0.6,<0.7)
Requires-Dist: pendulum (>=1.4,<2.0); extra == "time"
Project-URL: Documentation, https://poetry.eustace.io/docs
Project-URL: Repository, https://github.com/sdispater/poetry
Description-Content-Type: text/x-rst
My Package
==========
"""
requires
=
"""
\
cachy[msgpack] (>=0.2.0,<0.3.0)
cleo (>=0.6,<0.7)
pendulum (>=1.4,<2.0)
"""
with
egg_info
.
joinpath
(
"entry_points.txt"
)
.
open
(
encoding
=
"utf-8"
)
as
f
:
assert
entry_points
==
f
.
read
()
with
egg_info
.
joinpath
(
"PKG-INFO"
)
.
open
(
encoding
=
"utf-8"
)
as
f
:
assert
pkg_info
==
f
.
read
()
with
egg_info
.
joinpath
(
"requires.txt"
)
.
open
(
encoding
=
"utf-8"
)
as
f
:
assert
requires
==
f
.
read
()
egg_link
=
env
.
site_packages
/
"my-package.egg-link"
with
egg_link
.
open
(
encoding
=
"utf-8"
)
as
f
:
assert
str
(
module_path
)
+
"
\n
."
==
f
.
read
()
easy_install
=
env
.
site_packages
/
"easy-install.pth"
with
easy_install
.
open
(
encoding
=
"utf-8"
)
as
f
:
assert
str
(
module_path
)
+
"
\n
"
in
f
.
readlines
()
def
test_build_should_delegate_to_pip_for_non_pure_python_packages
(
tmp_dir
,
mocker
):
move
=
mocker
.
patch
(
"shutil.move"
)
tmp_dir
=
Path
(
tmp_dir
)
env
=
MockEnv
(
path
=
tmp_dir
,
pip_version
=
"18.1"
,
execute
=
False
)
env
.
site_packages
.
mkdir
(
parents
=
True
)
module_path
=
fixtures_dir
/
"extended"
builder
=
EditableBuilder
(
Poetry
.
create
(
module_path
),
env
,
NullIO
())
builder
.
build
()
expected
=
[[
"python"
,
"-m"
,
"pip"
,
"install"
,
"-e"
,
str
(
module_path
)]]
assert
expected
==
env
.
executed
assert
0
==
move
.
call_count
def
test_build_should_temporarily_remove_the_pyproject_file
(
tmp_dir
,
mocker
):
move
=
mocker
.
patch
(
"shutil.move"
)
tmp_dir
=
Path
(
tmp_dir
)
env
=
MockEnv
(
path
=
tmp_dir
,
pip_version
=
"19.1"
,
execute
=
False
)
env
.
site_packages
.
mkdir
(
parents
=
True
)
module_path
=
fixtures_dir
/
"extended"
builder
=
EditableBuilder
(
Poetry
.
create
(
module_path
),
env
,
NullIO
())
builder
.
build
()
expected
=
[[
"python"
,
"-m"
,
"pip"
,
"install"
,
"-e"
,
str
(
module_path
)]]
assert
expected
==
env
.
executed
assert
2
==
move
.
call_count
expected_calls
=
[
mocker
.
call
(
str
(
module_path
/
"pyproject.toml"
),
str
(
module_path
/
"pyproject.tmp"
)
),
mocker
.
call
(
str
(
module_path
/
"pyproject.tmp"
),
str
(
module_path
/
"pyproject.toml"
)
),
]
assert
expected_calls
==
move
.
call_args_list
tests/masonry/builders/test_wheel.py
View file @
2ed53be4
...
@@ -3,15 +3,11 @@ import pytest
...
@@ -3,15 +3,11 @@ import pytest
import
shutil
import
shutil
import
zipfile
import
zipfile
from
email.parser
import
Parser
from
poetry.io
import
NullIO
from
poetry.io
import
NullIO
from
poetry.masonry.builders
import
WheelBuilder
from
poetry.masonry.builders
import
WheelBuilder
from
poetry.poetry
import
Poetry
from
poetry.poetry
import
Poetry
from
poetry.utils._compat
import
Path
from
poetry.utils._compat
import
Path
from
poetry.utils._compat
import
to_str
from
poetry.utils.env
import
NullEnv
from
poetry.utils.env
import
NullEnv
from
poetry.packages
import
ProjectPackage
fixtures_dir
=
Path
(
__file__
)
.
parent
/
"fixtures"
fixtures_dir
=
Path
(
__file__
)
.
parent
/
"fixtures"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment