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
6eef349c
Commit
6eef349c
authored
Aug 23, 2022
by
Sébastien Eustace
Committed by
Randy Döring
Feb 05, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use the wheel builder to build editable wheels
parent
ab087af1
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
143 additions
and
86 deletions
+143
-86
src/poetry/installation/chef.py
+8
-4
src/poetry/installation/executor.py
+48
-56
tests/installation/test_chef.py
+14
-0
tests/installation/test_executor.py
+73
-26
No files found.
src/poetry/installation/chef.py
View file @
6eef349c
...
@@ -94,18 +94,22 @@ class Chef:
...
@@ -94,18 +94,22 @@ class Chef:
Path
(
config
.
get
(
"cache-dir"
))
.
expanduser
()
.
joinpath
(
"artifacts"
)
Path
(
config
.
get
(
"cache-dir"
))
.
expanduser
()
.
joinpath
(
"artifacts"
)
)
)
def
prepare
(
self
,
archive
:
Path
,
output_dir
:
Path
|
None
=
None
)
->
Path
:
def
prepare
(
self
,
archive
:
Path
,
output_dir
:
Path
|
None
=
None
,
*
,
editable
:
bool
=
False
)
->
Path
:
if
not
self
.
_should_prepare
(
archive
):
if
not
self
.
_should_prepare
(
archive
):
return
archive
return
archive
if
archive
.
is_dir
():
if
archive
.
is_dir
():
tmp_dir
=
tempfile
.
mkdtemp
(
prefix
=
"poetry-chef-"
)
tmp_dir
=
tempfile
.
mkdtemp
(
prefix
=
"poetry-chef-"
)
return
self
.
_prepare
(
archive
,
Path
(
tmp_dir
))
return
self
.
_prepare
(
archive
,
Path
(
tmp_dir
)
,
editable
=
editable
)
return
self
.
_prepare_sdist
(
archive
,
destination
=
output_dir
)
return
self
.
_prepare_sdist
(
archive
,
destination
=
output_dir
)
def
_prepare
(
self
,
directory
:
Path
,
destination
:
Path
)
->
Path
:
def
_prepare
(
self
,
directory
:
Path
,
destination
:
Path
,
*
,
editable
:
bool
=
False
)
->
Path
:
with
ephemeral_environment
(
self
.
_env
.
python
)
as
venv
:
with
ephemeral_environment
(
self
.
_env
.
python
)
as
venv
:
env
=
IsolatedEnv
(
venv
,
self
.
_config
)
env
=
IsolatedEnv
(
venv
,
self
.
_config
)
builder
=
ProjectBuilder
(
builder
=
ProjectBuilder
(
...
@@ -124,7 +128,7 @@ class Chef:
...
@@ -124,7 +128,7 @@ class Chef:
try
:
try
:
return
Path
(
return
Path
(
builder
.
build
(
builder
.
build
(
"wheel"
,
"wheel"
if
not
editable
else
"editable"
,
destination
.
as_posix
(),
destination
.
as_posix
(),
)
)
)
)
...
...
src/poetry/installation/executor.py
View file @
6eef349c
...
@@ -477,17 +477,18 @@ class Executor:
...
@@ -477,17 +477,18 @@ class Executor:
def
_install
(
self
,
operation
:
Install
|
Update
)
->
int
:
def
_install
(
self
,
operation
:
Install
|
Update
)
->
int
:
package
=
operation
.
package
package
=
operation
.
package
if
package
.
source_type
==
"directory"
:
if
package
.
source_type
==
"directory"
and
not
self
.
_use_wheel_installer
:
if
not
self
.
_use_wheel_installer
:
return
self
.
_install_directory_without_wheel_installer
(
operation
)
return
self
.
_install_directory_without_wheel_installer
(
operation
)
return
self
.
_install_directory
(
operation
)
cleanup_archive
:
bool
=
False
if
package
.
source_type
==
"git"
:
if
package
.
source_type
==
"git"
:
return
self
.
_install_git
(
operation
)
archive
=
self
.
_prepare_git_archive
(
operation
)
cleanup_archive
=
True
if
package
.
source_type
==
"file"
:
el
if
package
.
source_type
==
"file"
:
archive
=
self
.
_prepare_archive
(
operation
)
archive
=
self
.
_prepare_archive
(
operation
)
elif
package
.
source_type
==
"directory"
:
archive
=
self
.
_prepare_directory_archive
(
operation
)
cleanup_archive
=
True
elif
package
.
source_type
==
"url"
:
elif
package
.
source_type
==
"url"
:
assert
package
.
source_url
is
not
None
assert
package
.
source_url
is
not
None
archive
=
self
.
_download_link
(
operation
,
Link
(
package
.
source_url
))
archive
=
self
.
_download_link
(
operation
,
Link
(
package
.
source_url
))
...
@@ -504,7 +505,18 @@ class Executor:
...
@@ -504,7 +505,18 @@ class Executor:
if
not
self
.
_use_wheel_installer
:
if
not
self
.
_use_wheel_installer
:
return
self
.
pip_install
(
archive
,
upgrade
=
operation
.
job_type
==
"update"
)
return
self
.
pip_install
(
archive
,
upgrade
=
operation
.
job_type
==
"update"
)
self
.
_wheel_installer
.
install
(
archive
)
try
:
if
operation
.
job_type
==
"update"
:
# Uninstall first
# TODO: Make an uninstaller and find a way to rollback in case
# the new package can't be installed
assert
isinstance
(
operation
,
Update
)
self
.
_remove
(
operation
.
initial_package
)
self
.
_wheel_installer
.
install
(
archive
)
finally
:
if
cleanup_archive
:
archive
.
unlink
()
return
0
return
0
...
@@ -541,9 +553,9 @@ class Executor:
...
@@ -541,9 +553,9 @@ class Executor:
if
not
Path
(
package
.
source_url
)
.
is_absolute
()
and
package
.
root_dir
:
if
not
Path
(
package
.
source_url
)
.
is_absolute
()
and
package
.
root_dir
:
archive
=
package
.
root_dir
/
archive
archive
=
package
.
root_dir
/
archive
return
self
.
_chef
.
prepare
(
archive
)
return
self
.
_chef
.
prepare
(
archive
,
editable
=
package
.
develop
)
def
_
install_directory
(
self
,
operation
:
Install
|
Update
)
->
int
:
def
_
prepare_directory_archive
(
self
,
operation
:
Install
|
Update
)
->
Path
:
package
=
operation
.
package
package
=
operation
.
package
operation_message
=
self
.
get_operation_message
(
operation
)
operation_message
=
self
.
get_operation_message
(
operation
)
...
@@ -562,27 +574,35 @@ class Executor:
...
@@ -562,27 +574,35 @@ class Executor:
if
package
.
source_subdirectory
:
if
package
.
source_subdirectory
:
req
/=
package
.
source_subdirectory
req
/=
package
.
source_subdirectory
if
package
.
develop
:
return
self
.
_prepare_archive
(
operation
)
# Editable installations are currently not supported
# for PEP-517 build systems so we defer to pip.
# TODO: Remove this workaround once either PEP-660 or PEP-662 is accepted
return
self
.
pip_install
(
req
,
editable
=
True
)
archive
=
self
.
_prepare_archive
(
operation
)
def
_prepare_git_archive
(
self
,
operation
:
Install
|
Update
)
->
Path
:
from
poetry.vcs.git
import
Git
try
:
package
=
operation
.
package
if
operation
.
job_type
==
"update"
:
operation_message
=
self
.
get_operation_message
(
operation
)
# Uninstall first
# TODO: Make an uninstaller and find a way to rollback in case
# the new package can't be installed
assert
isinstance
(
operation
,
Update
)
self
.
_remove
(
operation
.
initial_package
)
self
.
_wheel_installer
.
install
(
archive
)
message
=
(
finally
:
f
" <fg=blue;options=bold>•</> {operation_message}: <info>Cloning...</info>"
archive
.
unlink
()
)
self
.
_write
(
operation
,
message
)
return
0
assert
package
.
source_url
is
not
None
source
=
Git
.
clone
(
url
=
package
.
source_url
,
source_root
=
self
.
_env
.
path
/
"src"
,
revision
=
package
.
source_resolved_reference
or
package
.
source_reference
,
)
# Now we just need to install from the source directory
original_url
=
package
.
source_url
package
.
_source_url
=
str
(
source
.
path
)
archive
=
self
.
_prepare_directory_archive
(
operation
)
package
.
_source_url
=
original_url
return
archive
def
_install_directory_without_wheel_installer
(
def
_install_directory_without_wheel_installer
(
self
,
operation
:
Install
|
Update
self
,
operation
:
Install
|
Update
...
@@ -650,34 +670,6 @@ class Executor:
...
@@ -650,34 +670,6 @@ class Executor:
return
self
.
pip_install
(
req
,
upgrade
=
True
,
editable
=
package
.
develop
)
return
self
.
pip_install
(
req
,
upgrade
=
True
,
editable
=
package
.
develop
)
def
_install_git
(
self
,
operation
:
Install
|
Update
)
->
int
:
from
poetry.vcs.git
import
Git
package
=
operation
.
package
operation_message
=
self
.
get_operation_message
(
operation
)
message
=
(
f
" <fg=blue;options=bold>•</> {operation_message}: <info>Cloning...</info>"
)
self
.
_write
(
operation
,
message
)
assert
package
.
source_url
is
not
None
source
=
Git
.
clone
(
url
=
package
.
source_url
,
source_root
=
self
.
_env
.
path
/
"src"
,
revision
=
package
.
source_resolved_reference
or
package
.
source_reference
,
)
# Now we just need to install from the source directory
original_url
=
package
.
source_url
package
.
_source_url
=
str
(
source
.
path
)
status_code
=
self
.
_install_directory
(
operation
)
package
.
_source_url
=
original_url
return
status_code
def
_download
(
self
,
operation
:
Install
|
Update
)
->
Path
:
def
_download
(
self
,
operation
:
Install
|
Update
)
->
Path
:
link
=
self
.
_chooser
.
choose_for
(
operation
.
package
)
link
=
self
.
_chooser
.
choose_for
(
operation
.
package
)
...
...
tests/installation/test_chef.py
View file @
6eef349c
...
@@ -2,6 +2,7 @@ from __future__ import annotations
...
@@ -2,6 +2,7 @@ from __future__ import annotations
from
pathlib
import
Path
from
pathlib
import
Path
from
typing
import
TYPE_CHECKING
from
typing
import
TYPE_CHECKING
from
zipfile
import
ZipFile
import
pytest
import
pytest
...
@@ -165,3 +166,16 @@ def test_prepare_directory_with_extensions(
...
@@ -165,3 +166,16 @@ def test_prepare_directory_with_extensions(
wheel
=
chef
.
prepare
(
archive
)
wheel
=
chef
.
prepare
(
archive
)
assert
wheel
.
name
==
f
"extended-0.1-{env.supported_tags[0]}.whl"
assert
wheel
.
name
==
f
"extended-0.1-{env.supported_tags[0]}.whl"
def
test_prepare_directory_editable
(
config
:
Config
,
config_cache_dir
:
Path
):
chef
=
Chef
(
config
,
EnvManager
.
get_system_env
())
archive
=
Path
(
__file__
)
.
parent
.
parent
.
joinpath
(
"fixtures/simple_project"
)
.
resolve
()
wheel
=
chef
.
prepare
(
archive
,
editable
=
True
)
assert
wheel
.
name
==
"simple_project-1.2.3-py2.py3-none-any.whl"
with
ZipFile
(
wheel
)
as
z
:
assert
"simple_project.pth"
in
z
.
namelist
()
tests/installation/test_executor.py
View file @
6eef349c
...
@@ -4,16 +4,19 @@ import csv
...
@@ -4,16 +4,19 @@ import csv
import
json
import
json
import
re
import
re
import
shutil
import
shutil
import
tempfile
from
pathlib
import
Path
from
pathlib
import
Path
from
typing
import
TYPE_CHECKING
from
typing
import
TYPE_CHECKING
from
typing
import
Any
from
typing
import
Any
from
typing
import
Callable
from
urllib.parse
import
urlparse
from
urllib.parse
import
urlparse
import
pytest
import
pytest
from
cleo.formatters.style
import
Style
from
cleo.formatters.style
import
Style
from
cleo.io.buffered_io
import
BufferedIO
from
cleo.io.buffered_io
import
BufferedIO
from
cleo.io.outputs.output
import
Verbosity
from
poetry.core.packages.package
import
Package
from
poetry.core.packages.package
import
Package
from
poetry.core.packages.utils.link
import
Link
from
poetry.core.packages.utils.link
import
Link
...
@@ -41,26 +44,40 @@ if TYPE_CHECKING:
...
@@ -41,26 +44,40 @@ if TYPE_CHECKING:
class
Chef
(
BaseChef
):
class
Chef
(
BaseChef
):
_directory_wheel
=
None
_directory_wheel
s
:
list
[
Path
]
|
None
=
None
_sdist_wheel
=
None
_sdist_wheel
s
:
list
[
Path
]
|
None
=
None
def
set_directory_wheel
(
self
,
wheel
:
Path
)
->
None
:
def
set_directory_wheel
(
self
,
wheels
:
Path
|
list
[
Path
])
->
None
:
self
.
_directory_wheel
=
wheel
if
not
isinstance
(
wheels
,
list
):
wheels
=
[
wheels
]
def
set_sdist_wheel
(
self
,
wheel
:
Path
)
->
None
:
self
.
_directory_wheels
=
wheels
self
.
_sdist_wheel
=
wheel
def
set_sdist_wheel
(
self
,
wheels
:
Path
|
list
[
Path
])
->
None
:
if
not
isinstance
(
wheels
,
list
):
wheels
=
[
wheels
]
self
.
_sdist_wheels
=
wheels
def
_prepare_sdist
(
self
,
archive
:
Path
,
destination
:
Path
|
None
=
None
)
->
Path
:
def
_prepare_sdist
(
self
,
archive
:
Path
,
destination
:
Path
|
None
=
None
)
->
Path
:
if
self
.
_sdist_wheel
is
not
None
:
if
self
.
_sdist_wheels
is
not
None
:
return
self
.
_sdist_wheel
wheel
=
self
.
_sdist_wheels
.
pop
(
0
)
self
.
_sdist_wheels
.
append
(
wheel
)
return
wheel
return
super
()
.
_prepare_sdist
(
archive
)
return
super
()
.
_prepare_sdist
(
archive
)
def
_prepare
(
self
,
directory
:
Path
,
destination
:
Path
)
->
Path
:
def
_prepare
(
if
self
.
_directory_wheel
is
not
None
:
self
,
directory
:
Path
,
destination
:
Path
,
*
,
editable
:
bool
=
False
return
self
.
_directory_wheel
)
->
Path
:
if
self
.
_directory_wheels
is
not
None
:
wheel
=
self
.
_directory_wheels
.
pop
(
0
)
self
.
_directory_wheels
.
append
(
wheel
)
return
super
()
.
_prepare
(
directory
,
destination
)
return
wheel
return
super
()
.
_prepare
(
directory
,
destination
,
editable
=
editable
)
@pytest.fixture
@pytest.fixture
...
@@ -132,17 +149,38 @@ def mock_file_downloads(http: type[httpretty.httpretty]) -> None:
...
@@ -132,17 +149,38 @@ def mock_file_downloads(http: type[httpretty.httpretty]) -> None:
@pytest.fixture
()
@pytest.fixture
()
def
wheel
(
tmp_dir
:
Path
)
->
Path
:
def
copy_wheel
(
tmp_dir
:
Path
)
->
Callable
[[],
Path
]:
shutil
.
copyfile
(
def
_copy_wheel
()
->
Path
:
Path
(
__file__
)
tmp_name
=
tempfile
.
mktemp
()
.
parent
.
parent
.
joinpath
(
Path
(
tmp_dir
)
.
joinpath
(
tmp_name
)
.
mkdir
()
"fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl"
shutil
.
copyfile
(
Path
(
__file__
)
.
parent
.
parent
.
joinpath
(
"fixtures/distributions/demo-0.1.2-py2.py3-none-any.whl"
)
.
as_posix
(),
Path
(
tmp_dir
)
.
joinpath
(
tmp_name
)
.
joinpath
(
"demo-0.1.2-py2.py3-none-any.whl"
)
.
as_posix
(),
)
return
(
Path
(
tmp_dir
)
.
joinpath
(
tmp_name
)
.
joinpath
(
"demo-0.1.2-py2.py3-none-any.whl"
)
)
)
.
as_posix
(),
Path
(
tmp_dir
)
.
joinpath
(
"demo-0.1.2-py2.py3-none-any.whl"
)
.
as_posix
(),
)
return
Path
(
tmp_dir
)
.
joinpath
(
"demo-0.1.2-py2.py3-none-any.whl"
)
return
_copy_wheel
@pytest.fixture
()
def
wheel
(
copy_wheel
:
Callable
[[],
Path
])
->
Path
:
archive
=
copy_wheel
()
yield
archive
if
archive
.
exists
():
archive
.
unlink
()
def
test_execute_executes_a_batch_of_operations
(
def
test_execute_executes_a_batch_of_operations
(
...
@@ -153,15 +191,18 @@ def test_execute_executes_a_batch_of_operations(
...
@@ -153,15 +191,18 @@ def test_execute_executes_a_batch_of_operations(
tmp_dir
:
str
,
tmp_dir
:
str
,
mock_file_downloads
:
None
,
mock_file_downloads
:
None
,
env
:
MockEnv
,
env
:
MockEnv
,
wheel
:
Path
,
copy_wheel
:
Callable
[[],
Path
]
,
):
):
wheel_install
=
mocker
.
patch
.
object
(
WheelInstaller
,
"install"
)
wheel_install
=
mocker
.
patch
.
object
(
WheelInstaller
,
"install"
)
config
.
merge
({
"cache-dir"
:
tmp_dir
})
config
.
merge
({
"cache-dir"
:
tmp_dir
})
prepare_spy
=
mocker
.
spy
(
Chef
,
"_prepare"
)
chef
=
Chef
(
config
,
env
)
chef
=
Chef
(
config
,
env
)
chef
.
set_directory_wheel
(
wheel
)
chef
.
set_directory_wheel
([
copy_wheel
(),
copy_wheel
()])
chef
.
set_sdist_wheel
(
wheel
)
chef
.
set_sdist_wheel
(
copy_wheel
())
io
.
set_verbosity
(
Verbosity
.
VERY_VERBOSE
)
executor
=
Executor
(
env
,
pool
,
config
,
io
)
executor
=
Executor
(
env
,
pool
,
config
,
io
)
executor
.
_chef
=
chef
executor
.
_chef
=
chef
...
@@ -223,11 +264,17 @@ Package operations: 4 installs, 1 update, 1 removal
...
@@ -223,11 +264,17 @@ Package operations: 4 installs, 1 update, 1 removal
expected
=
set
(
expected
.
splitlines
())
expected
=
set
(
expected
.
splitlines
())
output
=
set
(
io
.
fetch_output
()
.
splitlines
())
output
=
set
(
io
.
fetch_output
()
.
splitlines
())
assert
output
==
expected
assert
output
==
expected
assert
wheel_install
.
call_count
==
4
assert
wheel_install
.
call_count
==
5
#
One pip uninstall and one pip editable install
#
Two pip uninstalls: one for the remove operation one for the update operation
assert
len
(
env
.
executed
)
==
2
assert
len
(
env
.
executed
)
==
2
assert
return_code
==
0
assert
return_code
==
0
assert
prepare_spy
.
call_count
==
2
assert
prepare_spy
.
call_args_list
==
[
mocker
.
call
(
chef
,
mocker
.
ANY
,
mocker
.
ANY
,
editable
=
False
),
mocker
.
call
(
chef
,
mocker
.
ANY
,
mocker
.
ANY
,
editable
=
True
),
]
@pytest.mark.parametrize
(
@pytest.mark.parametrize
(
"operations, has_warning"
,
"operations, has_warning"
,
...
...
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