Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Jaime Collado
/
textflow
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
1
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
7b8e1ab1
authored
Apr 08, 2022
by
Estrella Vallecillo
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
archivos borrados
parent
3f37096a
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
0 additions
and
2026 deletions
get-poetry.py
install-poetry.py
get-poetry.py
deleted
100644 → 0
View file @
3f37096a
"""
This script will install Poetry and its dependencies
in isolation from the rest of the system.
It does, in order:
- Downloads the latest stable (or pre-release) version of poetry.
- Downloads all its dependencies in the poetry/_vendor directory.
- Copies it and all extra files in $POETRY_HOME.
- Updates the PATH in a system-specific way.
There will be a `poetry` script that will be installed in $POETRY_HOME/bin
which will act as the poetry command but is slightly different in the sense
that it will use the current Python installation.
What this means is that one Poetry installation can serve for multiple
Python versions.
"""
import
argparse
import
hashlib
import
json
import
os
import
platform
import
re
import
shutil
import
stat
import
subprocess
import
sys
import
tarfile
import
tempfile
from
contextlib
import
closing
from
contextlib
import
contextmanager
from
functools
import
cmp_to_key
from
gzip
import
GzipFile
from
io
import
UnsupportedOperation
from
io
import
open
try
:
from
urllib.error
import
HTTPError
from
urllib.request
import
Request
from
urllib.request
import
urlopen
except
ImportError
:
from
urllib2
import
HTTPError
from
urllib2
import
Request
from
urllib2
import
urlopen
try
:
input
=
raw_input
except
NameError
:
pass
try
:
try
:
import
winreg
except
ImportError
:
import
_winreg
as
winreg
except
ImportError
:
winreg
=
None
try
:
u
=
unicode
except
NameError
:
u
=
str
SHELL
=
os
.
getenv
(
"SHELL"
,
""
)
WINDOWS
=
sys
.
platform
.
startswith
(
"win"
)
or
(
sys
.
platform
==
"cli"
and
os
.
name
==
"nt"
)
FOREGROUND_COLORS
=
{
"black"
:
30
,
"red"
:
31
,
"green"
:
32
,
"yellow"
:
33
,
"blue"
:
34
,
"magenta"
:
35
,
"cyan"
:
36
,
"white"
:
37
,
}
BACKGROUND_COLORS
=
{
"black"
:
40
,
"red"
:
41
,
"green"
:
42
,
"yellow"
:
43
,
"blue"
:
44
,
"magenta"
:
45
,
"cyan"
:
46
,
"white"
:
47
,
}
OPTIONS
=
{
"bold"
:
1
,
"underscore"
:
4
,
"blink"
:
5
,
"reverse"
:
7
,
"conceal"
:
8
}
def
style
(
fg
,
bg
,
options
):
codes
=
[]
if
fg
:
codes
.
append
(
FOREGROUND_COLORS
[
fg
])
if
bg
:
codes
.
append
(
BACKGROUND_COLORS
[
bg
])
if
options
:
if
not
isinstance
(
options
,
(
list
,
tuple
)):
options
=
[
options
]
for
option
in
options
:
codes
.
append
(
OPTIONS
[
option
])
return
"
\033
[{}m"
.
format
(
";"
.
join
(
map
(
str
,
codes
)))
STYLES
=
{
"info"
:
style
(
"green"
,
None
,
None
),
"comment"
:
style
(
"yellow"
,
None
,
None
),
"error"
:
style
(
"red"
,
None
,
None
),
"warning"
:
style
(
"yellow"
,
None
,
None
),
}
def
is_decorated
():
if
platform
.
system
()
.
lower
()
==
"windows"
:
return
(
os
.
getenv
(
"ANSICON"
)
is
not
None
or
os
.
getenv
(
"ConEmuANSI"
)
==
"ON"
or
os
.
getenv
(
"Term"
)
==
"xterm"
)
if
not
hasattr
(
sys
.
stdout
,
"fileno"
):
return
False
try
:
return
os
.
isatty
(
sys
.
stdout
.
fileno
())
except
UnsupportedOperation
:
return
False
def
is_interactive
():
if
not
hasattr
(
sys
.
stdin
,
"fileno"
):
return
False
try
:
return
os
.
isatty
(
sys
.
stdin
.
fileno
())
except
UnsupportedOperation
:
return
False
def
colorize
(
style
,
text
):
if
not
is_decorated
():
return
text
return
"{}{}
\033
[0m"
.
format
(
STYLES
[
style
],
text
)
@contextmanager
def
temporary_directory
(
*
args
,
**
kwargs
):
try
:
from
tempfile
import
TemporaryDirectory
except
ImportError
:
name
=
tempfile
.
mkdtemp
(
*
args
,
**
kwargs
)
yield
name
shutil
.
rmtree
(
name
)
else
:
with
TemporaryDirectory
(
*
args
,
**
kwargs
)
as
name
:
yield
name
def
string_to_bool
(
value
):
value
=
value
.
lower
()
return
value
in
{
"true"
,
"1"
,
"y"
,
"yes"
}
def
expanduser
(
path
):
"""
Expand ~ and ~user constructions.
Includes a workaround for http://bugs.python.org/issue14768
"""
expanded
=
os
.
path
.
expanduser
(
path
)
if
path
.
startswith
(
"~/"
)
and
expanded
.
startswith
(
"//"
):
expanded
=
expanded
[
1
:]
return
expanded
HOME
=
expanduser
(
"~"
)
POETRY_HOME
=
os
.
environ
.
get
(
"POETRY_HOME"
)
or
os
.
path
.
join
(
HOME
,
".poetry"
)
POETRY_BIN
=
os
.
path
.
join
(
POETRY_HOME
,
"bin"
)
POETRY_ENV
=
os
.
path
.
join
(
POETRY_HOME
,
"env"
)
POETRY_LIB
=
os
.
path
.
join
(
POETRY_HOME
,
"lib"
)
POETRY_LIB_BACKUP
=
os
.
path
.
join
(
POETRY_HOME
,
"lib-backup"
)
BIN
=
"""# -*- coding: utf-8 -*-
import glob
import sys
import os
lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib"))
vendors = os.path.join(lib, "poetry", "_vendor")
current_vendors = os.path.join(
vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2]))
)
sys.path.insert(0, lib)
sys.path.insert(0, current_vendors)
if __name__ == "__main__":
from poetry.console import main
main()
"""
BAT
=
u
(
'@echo off
\r\n
{python_executable} "{poetry_bin}"
%*
\r\n
'
)
PRE_MESSAGE
=
"""# Welcome to {poetry}!
This will download and install the latest version of {poetry},
a dependency and package manager for Python.
It will add the `poetry` command to {poetry}'s bin directory, located at:
{poetry_home_bin}
{platform_msg}
You can uninstall at any time by executing this script with the --uninstall option,
and these changes will be reverted.
"""
PRE_UNINSTALL_MESSAGE
=
"""# We are sorry to see you go!
This will uninstall {poetry}.
It will remove the `poetry` command from {poetry}'s bin directory, located at:
{poetry_home_bin}
This will also remove {poetry} from your system's PATH.
"""
PRE_MESSAGE_UNIX
=
"""This path will then be added to your `PATH` environment variable by
modifying the profile file{plural} located at:
{rcfiles}"""
PRE_MESSAGE_FISH
=
"""This path will then be added to your `PATH` environment variable by
modifying the `fish_user_paths` universal variable."""
PRE_MESSAGE_WINDOWS
=
"""This path will then be added to your `PATH` environment variable by
modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."""
PRE_MESSAGE_NO_MODIFY_PATH
=
"""This path needs to be in your `PATH` environment variable,
but will not be added automatically."""
POST_MESSAGE_UNIX
=
"""{poetry} ({version}) is installed now. Great!
To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
environment variable. Next time you log in this will be done
automatically.
To configure your current shell run `source {poetry_home_env}`
"""
POST_MESSAGE_FISH
=
"""{poetry} ({version}) is installed now. Great!
{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH`
environment variable by modifying the `fish_user_paths` universal variable.
"""
POST_MESSAGE_WINDOWS
=
"""{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
environment variable. Future applications will automatically have the
correct environment, but you may need to restart your current shell.
"""
POST_MESSAGE_UNIX_NO_MODIFY_PATH
=
"""{poetry} ({version}) is installed now. Great!
To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
environment variable.
To configure your current shell run `source {poetry_home_env}`
"""
POST_MESSAGE_FISH_NO_MODIFY_PATH
=
"""{poetry} ({version}) is installed now. Great!
To get started you need {poetry}'s bin directory ({poetry_home_bin})
in your `PATH` environment variable, which you can add by running
the following command:
set -U fish_user_paths {poetry_home_bin} $fish_user_paths
"""
POST_MESSAGE_WINDOWS_NO_MODIFY_PATH
=
"""{poetry} ({version}) is installed now. Great!
To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
environment variable. This has not been done automatically.
"""
class
Installer
:
CURRENT_PYTHON
=
sys
.
executable
CURRENT_PYTHON_VERSION
=
sys
.
version_info
[:
2
]
METADATA_URL
=
"https://pypi.org/pypi/poetry/json"
VERSION_REGEX
=
re
.
compile
(
r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?"
"("
"[._-]?"
r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?"
"([.-]?dev)?"
")?"
r"(?:\+[^\s]+)?"
)
REPOSITORY_URL
=
"https://github.com/python-poetry/poetry"
BASE_URL
=
REPOSITORY_URL
+
"/releases/download/"
FALLBACK_BASE_URL
=
"https://github.com/sdispater/poetry/releases/download/"
def
__init__
(
self
,
version
=
None
,
preview
=
False
,
force
=
False
,
modify_path
=
True
,
accept_all
=
False
,
file
=
None
,
base_url
=
BASE_URL
,
):
self
.
_version
=
version
self
.
_preview
=
preview
self
.
_force
=
force
self
.
_modify_path
=
modify_path
self
.
_accept_all
=
accept_all
self
.
_offline_file
=
file
self
.
_base_url
=
base_url
def
allows_prereleases
(
self
):
return
self
.
_preview
def
run
(
self
):
version
,
current_version
=
self
.
get_version
()
if
version
is
None
:
return
0
self
.
customize_install
()
self
.
display_pre_message
()
self
.
ensure_home
()
try
:
self
.
install
(
version
,
upgrade
=
current_version
is
not
None
,
file
=
self
.
_offline_file
)
except
subprocess
.
CalledProcessError
as
e
:
print
(
colorize
(
"error"
,
"An error has occurred: {}"
.
format
(
str
(
e
))))
print
(
e
.
output
.
decode
())
return
e
.
returncode
self
.
display_post_message
(
version
)
return
0
def
uninstall
(
self
):
self
.
display_pre_uninstall_message
()
if
not
self
.
customize_uninstall
():
return
self
.
remove_home
()
self
.
remove_from_path
()
def
get_version
(
self
):
current_version
=
None
if
os
.
path
.
exists
(
POETRY_LIB
):
with
open
(
os
.
path
.
join
(
POETRY_LIB
,
"poetry"
,
"__version__.py"
),
encoding
=
"utf-8"
)
as
f
:
version_content
=
f
.
read
()
current_version_re
=
re
.
match
(
'(?ms).*__version__ = "(.+)".*'
,
version_content
)
if
not
current_version_re
:
print
(
colorize
(
"warning"
,
"Unable to get the current Poetry version. Assuming None"
,
)
)
else
:
current_version
=
current_version_re
.
group
(
1
)
# Skip retrieving online release versions if install file is specified
if
self
.
_offline_file
is
not
None
:
if
current_version
is
not
None
and
not
self
.
_force
:
print
(
"There is a version of Poetry already installed."
)
return
None
,
current_version
return
"from an offline file"
,
current_version
print
(
colorize
(
"info"
,
"Retrieving Poetry metadata"
))
metadata
=
json
.
loads
(
self
.
_get
(
self
.
METADATA_URL
)
.
decode
())
def
_compare_versions
(
x
,
y
):
mx
=
self
.
VERSION_REGEX
.
match
(
x
)
my
=
self
.
VERSION_REGEX
.
match
(
y
)
vx
=
tuple
(
int
(
p
)
for
p
in
mx
.
groups
()[:
3
])
+
(
mx
.
group
(
5
),)
vy
=
tuple
(
int
(
p
)
for
p
in
my
.
groups
()[:
3
])
+
(
my
.
group
(
5
),)
if
vx
<
vy
:
return
-
1
elif
vx
>
vy
:
return
1
return
0
print
(
""
)
releases
=
sorted
(
metadata
[
"releases"
]
.
keys
(),
key
=
cmp_to_key
(
_compare_versions
)
)
if
self
.
_version
and
self
.
_version
not
in
releases
:
print
(
colorize
(
"error"
,
"Version {} does not exist."
.
format
(
self
.
_version
)))
return
None
,
None
version
=
self
.
_version
if
not
version
:
for
release
in
reversed
(
releases
):
m
=
self
.
VERSION_REGEX
.
match
(
release
)
if
m
.
group
(
5
)
and
not
self
.
allows_prereleases
():
continue
version
=
release
break
def
_is_supported
(
x
):
mx
=
self
.
VERSION_REGEX
.
match
(
x
)
vx
=
tuple
(
int
(
p
)
for
p
in
mx
.
groups
()[:
3
])
+
(
mx
.
group
(
5
),)
return
vx
<
(
1
,
2
,
0
)
if
not
_is_supported
(
version
):
print
(
colorize
(
"error"
,
"Version {version} does not support this installation method."
" Please specify a version prior to 1.2.0a1 explicitly using the"
" '--version' option.
\n
Please see"
" https://python-poetry.org/blog/announcing-poetry-1-2-0a1.html#deprecation-of-the-get-poetry-py-script"
" for more information."
.
format
(
version
=
version
),
)
)
return
None
,
None
print
(
colorize
(
"warning"
,
"This installer is deprecated. Poetry versions installed using this"
" script will not be able to use 'self update' command to upgrade to"
" 1.2.0a1 or later."
,
)
)
current_version
=
None
if
os
.
path
.
exists
(
POETRY_LIB
):
with
open
(
os
.
path
.
join
(
POETRY_LIB
,
"poetry"
,
"__version__.py"
),
encoding
=
"utf-8"
)
as
f
:
version_content
=
f
.
read
()
current_version_re
=
re
.
match
(
'(?ms).*__version__ = "(.+)".*'
,
version_content
)
if
not
current_version_re
:
print
(
colorize
(
"warning"
,
"Unable to get the current Poetry version. Assuming None"
,
)
)
else
:
current_version
=
current_version_re
.
group
(
1
)
if
current_version
==
version
and
not
self
.
_force
:
print
(
"Latest version already installed."
)
return
None
,
current_version
return
version
,
current_version
def
customize_install
(
self
):
if
not
self
.
_accept_all
:
print
(
"Before we start, please answer the following questions."
)
print
(
"You may simply press the Enter key to leave unchanged."
)
modify_path
=
input
(
"Modify PATH variable? ([y]/n) "
)
or
"y"
if
modify_path
.
lower
()
in
{
"n"
,
"no"
}:
self
.
_modify_path
=
False
print
(
""
)
def
customize_uninstall
(
self
):
if
not
self
.
_accept_all
:
print
()
uninstall
=
(
input
(
"Are you sure you want to uninstall Poetry? (y/[n]) "
)
or
"n"
)
if
uninstall
.
lower
()
not
in
{
"y"
,
"yes"
}:
return
False
print
(
""
)
return
True
def
ensure_home
(
self
):
"""
Ensures that $POETRY_HOME exists or create it.
"""
if
not
os
.
path
.
exists
(
POETRY_HOME
):
os
.
mkdir
(
POETRY_HOME
,
0
o755
)
def
remove_home
(
self
):
"""
Removes $POETRY_HOME.
"""
if
not
os
.
path
.
exists
(
POETRY_HOME
):
return
shutil
.
rmtree
(
POETRY_HOME
)
def
install
(
self
,
version
,
upgrade
=
False
,
file
=
None
):
"""
Installs Poetry in $POETRY_HOME.
"""
if
file
is
not
None
:
print
(
"Attempting to install from file: "
+
colorize
(
"info"
,
file
))
else
:
print
(
"Installing version: "
+
colorize
(
"info"
,
version
))
self
.
make_lib
(
version
)
self
.
make_bin
()
self
.
make_env
()
self
.
update_path
()
return
0
def
make_lib
(
self
,
version
):
"""
Packs everything into a single lib/ directory.
"""
if
os
.
path
.
exists
(
POETRY_LIB_BACKUP
):
shutil
.
rmtree
(
POETRY_LIB_BACKUP
)
# Backup the current installation
if
os
.
path
.
exists
(
POETRY_LIB
):
shutil
.
copytree
(
POETRY_LIB
,
POETRY_LIB_BACKUP
)
shutil
.
rmtree
(
POETRY_LIB
)
try
:
self
.
_make_lib
(
version
)
except
Exception
:
if
not
os
.
path
.
exists
(
POETRY_LIB_BACKUP
):
raise
shutil
.
copytree
(
POETRY_LIB_BACKUP
,
POETRY_LIB
)
shutil
.
rmtree
(
POETRY_LIB_BACKUP
)
raise
finally
:
if
os
.
path
.
exists
(
POETRY_LIB_BACKUP
):
shutil
.
rmtree
(
POETRY_LIB_BACKUP
)
def
_make_lib
(
self
,
version
):
# Check if an offline installer file has been specified
if
self
.
_offline_file
is
not
None
:
try
:
self
.
extract_lib
(
self
.
_offline_file
)
return
except
Exception
:
raise
RuntimeError
(
"Could not install from offline file."
)
# We get the payload from the remote host
platform
=
sys
.
platform
if
platform
==
"linux2"
:
platform
=
"linux"
url
=
self
.
_base_url
+
"{}/"
.
format
(
version
)
name
=
"poetry-{}-{}.tar.gz"
.
format
(
version
,
platform
)
checksum
=
"poetry-{}-{}.sha256sum"
.
format
(
version
,
platform
)
try
:
r
=
urlopen
(
url
+
"{}"
.
format
(
checksum
))
except
HTTPError
as
e
:
if
e
.
code
==
404
:
raise
RuntimeError
(
"Could not find {} file"
.
format
(
checksum
))
raise
checksum
=
r
.
read
()
.
decode
()
try
:
r
=
urlopen
(
url
+
"{}"
.
format
(
name
))
except
HTTPError
as
e
:
if
e
.
code
==
404
:
raise
RuntimeError
(
"Could not find {} file"
.
format
(
name
))
raise
meta
=
r
.
info
()
size
=
int
(
meta
[
"Content-Length"
])
current
=
0
block_size
=
8192
print
(
" - Downloading {} ({:.2f}MB)"
.
format
(
colorize
(
"comment"
,
name
),
size
/
1024
/
1024
)
)
sha
=
hashlib
.
sha256
()
with
temporary_directory
(
prefix
=
"poetry-installer-"
)
as
dir_
:
tar
=
os
.
path
.
join
(
dir_
,
name
)
with
open
(
tar
,
"wb"
)
as
f
:
while
True
:
buffer
=
r
.
read
(
block_size
)
if
not
buffer
:
break
current
+=
len
(
buffer
)
f
.
write
(
buffer
)
sha
.
update
(
buffer
)
# Checking hashes
if
checksum
!=
sha
.
hexdigest
():
raise
RuntimeError
(
"Hashes for {} do not match: {} != {}"
.
format
(
name
,
checksum
,
sha
.
hexdigest
()
)
)
self
.
extract_lib
(
tar
)
def
extract_lib
(
self
,
filename
):
gz
=
GzipFile
(
filename
,
mode
=
"rb"
)
try
:
with
tarfile
.
TarFile
(
filename
,
fileobj
=
gz
,
format
=
tarfile
.
PAX_FORMAT
)
as
f
:
f
.
extractall
(
POETRY_LIB
)
finally
:
gz
.
close
()
def
_which_python
(
self
):
"""Decides which python executable we'll embed in the launcher script."""
allowed_executables
=
[
"python3"
,
"python"
]
if
WINDOWS
:
allowed_executables
+=
[
"py.exe -3"
,
"py.exe -2"
]
# \d in regex ensures we can convert to int later
version_matcher
=
re
.
compile
(
r"^Python (?P<major>\d+)\.(?P<minor>\d+)\..+$"
)
fallback
=
None
for
executable
in
allowed_executables
:
try
:
raw_version
=
subprocess
.
check_output
(
executable
+
" --version"
,
stderr
=
subprocess
.
STDOUT
,
shell
=
True
)
.
decode
(
"utf-8"
)
except
subprocess
.
CalledProcessError
:
continue
match
=
version_matcher
.
match
(
raw_version
.
strip
())
if
match
:
return
executable
if
fallback
is
None
:
# keep this one as the fallback; it was the first valid executable we
# found.
fallback
=
executable
if
fallback
is
None
:
raise
RuntimeError
(
"No python executable found in shell environment. Tried: "
+
str
(
allowed_executables
)
)
return
fallback
def
make_bin
(
self
):
if
not
os
.
path
.
exists
(
POETRY_BIN
):
os
.
mkdir
(
POETRY_BIN
,
0
o755
)
python_executable
=
self
.
_which_python
()
if
WINDOWS
:
with
open
(
os
.
path
.
join
(
POETRY_BIN
,
"poetry.bat"
),
"w"
)
as
f
:
f
.
write
(
u
(
BAT
.
format
(
python_executable
=
python_executable
,
poetry_bin
=
os
.
path
.
join
(
POETRY_BIN
,
"poetry"
)
.
replace
(
os
.
environ
[
"USERPROFILE"
],
"
%
USERPROFILE
%
"
),
)
)
)
with
open
(
os
.
path
.
join
(
POETRY_BIN
,
"poetry"
),
"w"
,
encoding
=
"utf-8"
)
as
f
:
if
WINDOWS
:
python_executable
=
"python"
f
.
write
(
u
(
"#!/usr/bin/env {}
\n
"
.
format
(
python_executable
)))
f
.
write
(
u
(
BIN
))
if
not
WINDOWS
:
# Making the file executable
st
=
os
.
stat
(
os
.
path
.
join
(
POETRY_BIN
,
"poetry"
))
os
.
chmod
(
os
.
path
.
join
(
POETRY_BIN
,
"poetry"
),
st
.
st_mode
|
stat
.
S_IEXEC
)
def
make_env
(
self
):
if
WINDOWS
:
return
with
open
(
os
.
path
.
join
(
POETRY_HOME
,
"env"
),
"w"
)
as
f
:
f
.
write
(
u
(
self
.
get_export_string
()))
def
update_path
(
self
):
"""
Tries to update the $PATH automatically.
"""
if
not
self
.
_modify_path
:
return
if
"fish"
in
SHELL
:
return
self
.
add_to_fish_path
()
if
WINDOWS
:
return
self
.
add_to_windows_path
()
# Updating any profile we can on UNIX systems
export_string
=
self
.
get_export_string
()
addition
=
"
\n
{}
\n
"
.
format
(
export_string
)
profiles
=
self
.
get_unix_profiles
()
for
profile
in
profiles
:
if
not
os
.
path
.
exists
(
profile
):
continue
with
open
(
profile
,
"r"
)
as
f
:
content
=
f
.
read
()
if
addition
not
in
content
:
with
open
(
profile
,
"a"
)
as
f
:
f
.
write
(
u
(
addition
))
def
add_to_fish_path
(
self
):
"""
Ensure POETRY_BIN directory is on Fish shell $PATH
"""
current_path
=
os
.
environ
.
get
(
"PATH"
,
None
)
if
current_path
is
None
:
print
(
colorize
(
"warning"
,
"
\n
Unable to get the PATH value. It will not be updated"
" automatically."
,
)
)
self
.
_modify_path
=
False
return
if
POETRY_BIN
not
in
current_path
:
fish_user_paths
=
subprocess
.
check_output
(
[
"fish"
,
"-c"
,
"echo $fish_user_paths"
]
)
.
decode
(
"utf-8"
)
if
POETRY_BIN
not
in
fish_user_paths
:
cmd
=
"set -U fish_user_paths {} $fish_user_paths"
.
format
(
POETRY_BIN
)
set_fish_user_path
=
[
"fish"
,
"-c"
,
"{}"
.
format
(
cmd
)]
subprocess
.
check_output
(
set_fish_user_path
)
else
:
print
(
colorize
(
"warning"
,
"
\n
PATH already contains {} and thus was not modified."
.
format
(
POETRY_BIN
),
)
)
def
add_to_windows_path
(
self
):
try
:
old_path
=
self
.
get_windows_path_var
()
except
WindowsError
:
old_path
=
None
if
old_path
is
None
:
print
(
colorize
(
"warning"
,
"Unable to get the PATH value. It will not be updated"
" automatically"
,
)
)
self
.
_modify_path
=
False
return
new_path
=
POETRY_BIN
if
POETRY_BIN
in
old_path
:
old_path
=
old_path
.
replace
(
POETRY_BIN
+
";"
,
""
)
if
old_path
:
new_path
+=
";"
new_path
+=
old_path
self
.
set_windows_path_var
(
new_path
)
def
get_windows_path_var
(
self
):
with
winreg
.
ConnectRegistry
(
None
,
winreg
.
HKEY_CURRENT_USER
)
as
root
:
with
winreg
.
OpenKey
(
root
,
"Environment"
,
0
,
winreg
.
KEY_ALL_ACCESS
)
as
key
:
path
,
_
=
winreg
.
QueryValueEx
(
key
,
"PATH"
)
return
path
def
set_windows_path_var
(
self
,
value
):
import
ctypes
with
winreg
.
ConnectRegistry
(
None
,
winreg
.
HKEY_CURRENT_USER
)
as
root
:
with
winreg
.
OpenKey
(
root
,
"Environment"
,
0
,
winreg
.
KEY_ALL_ACCESS
)
as
key
:
winreg
.
SetValueEx
(
key
,
"PATH"
,
0
,
winreg
.
REG_EXPAND_SZ
,
value
)
# Tell other processes to update their environment
HWND_BROADCAST
=
0xFFFF
WM_SETTINGCHANGE
=
0x1A
SMTO_ABORTIFHUNG
=
0x0002
result
=
ctypes
.
c_long
()
SendMessageTimeoutW
=
ctypes
.
windll
.
user32
.
SendMessageTimeoutW
SendMessageTimeoutW
(
HWND_BROADCAST
,
WM_SETTINGCHANGE
,
0
,
"Environment"
,
SMTO_ABORTIFHUNG
,
5000
,
ctypes
.
byref
(
result
),
)
def
remove_from_path
(
self
):
if
"fish"
in
SHELL
:
return
self
.
remove_from_fish_path
()
elif
WINDOWS
:
return
self
.
remove_from_windows_path
()
return
self
.
remove_from_unix_path
()
def
remove_from_fish_path
(
self
):
fish_user_paths
=
subprocess
.
check_output
(
[
"fish"
,
"-c"
,
"echo $fish_user_paths"
]
)
.
decode
(
"utf-8"
)
if
POETRY_BIN
in
fish_user_paths
:
cmd
=
"set -U fish_user_paths (string match -v {} $fish_user_paths)"
.
format
(
POETRY_BIN
)
set_fish_user_path
=
[
"fish"
,
"-c"
,
"{}"
.
format
(
cmd
)]
subprocess
.
check_output
(
set_fish_user_path
)
def
remove_from_windows_path
(
self
):
path
=
self
.
get_windows_path_var
()
poetry_path
=
POETRY_BIN
if
poetry_path
in
path
:
path
=
path
.
replace
(
POETRY_BIN
+
";"
,
""
)
if
poetry_path
in
path
:
path
=
path
.
replace
(
POETRY_BIN
,
""
)
self
.
set_windows_path_var
(
path
)
def
remove_from_unix_path
(
self
):
# Updating any profile we can on UNIX systems
export_string
=
self
.
get_export_string
()
addition
=
"{}
\n
"
.
format
(
export_string
)
profiles
=
self
.
get_unix_profiles
()
for
profile
in
profiles
:
if
not
os
.
path
.
exists
(
profile
):
continue
with
open
(
profile
,
"r"
)
as
f
:
content
=
f
.
readlines
()
if
addition
not
in
content
:
continue
new_content
=
[]
for
line
in
content
:
if
line
==
addition
:
if
new_content
and
not
new_content
[
-
1
]
.
strip
():
new_content
=
new_content
[:
-
1
]
continue
new_content
.
append
(
line
)
with
open
(
profile
,
"w"
)
as
f
:
f
.
writelines
(
new_content
)
def
get_export_string
(
self
):
path
=
POETRY_BIN
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
)
export_string
=
'export PATH="{}:$PATH"'
.
format
(
path
)
return
export_string
def
get_unix_profiles
(
self
):
profiles
=
[
os
.
path
.
join
(
HOME
,
".profile"
)]
if
"zsh"
in
SHELL
:
zdotdir
=
os
.
getenv
(
"ZDOTDIR"
,
HOME
)
profiles
.
append
(
os
.
path
.
join
(
zdotdir
,
".zshrc"
))
bash_profile
=
os
.
path
.
join
(
HOME
,
".bash_profile"
)
if
os
.
path
.
exists
(
bash_profile
):
profiles
.
append
(
bash_profile
)
return
profiles
def
display_pre_message
(
self
):
if
WINDOWS
:
home
=
POETRY_BIN
.
replace
(
os
.
getenv
(
"USERPROFILE"
,
""
),
"
%
USERPROFILE
%
"
)
else
:
home
=
POETRY_BIN
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
)
kwargs
=
{
"poetry"
:
colorize
(
"info"
,
"Poetry"
),
"poetry_home_bin"
:
colorize
(
"comment"
,
home
),
}
if
not
self
.
_modify_path
:
kwargs
[
"platform_msg"
]
=
PRE_MESSAGE_NO_MODIFY_PATH
else
:
if
"fish"
in
SHELL
:
kwargs
[
"platform_msg"
]
=
PRE_MESSAGE_FISH
elif
WINDOWS
:
kwargs
[
"platform_msg"
]
=
PRE_MESSAGE_WINDOWS
else
:
profiles
=
[
colorize
(
"comment"
,
p
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
))
for
p
in
self
.
get_unix_profiles
()
]
kwargs
[
"platform_msg"
]
=
PRE_MESSAGE_UNIX
.
format
(
rcfiles
=
"
\n
"
.
join
(
profiles
),
plural
=
"s"
if
len
(
profiles
)
>
1
else
""
)
print
(
PRE_MESSAGE
.
format
(
**
kwargs
))
def
display_pre_uninstall_message
(
self
):
home_bin
=
POETRY_BIN
if
WINDOWS
:
home_bin
=
home_bin
.
replace
(
os
.
getenv
(
"USERPROFILE"
,
""
),
"
%
USERPROFILE
%
"
)
else
:
home_bin
=
home_bin
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
)
kwargs
=
{
"poetry"
:
colorize
(
"info"
,
"Poetry"
),
"poetry_home_bin"
:
colorize
(
"comment"
,
home_bin
),
}
print
(
PRE_UNINSTALL_MESSAGE
.
format
(
**
kwargs
))
def
display_post_message
(
self
,
version
):
print
(
""
)
kwargs
=
{
"poetry"
:
colorize
(
"info"
,
"Poetry"
),
"version"
:
colorize
(
"comment"
,
version
),
}
if
WINDOWS
:
message
=
POST_MESSAGE_WINDOWS
if
not
self
.
_modify_path
:
message
=
POST_MESSAGE_WINDOWS_NO_MODIFY_PATH
poetry_home_bin
=
POETRY_BIN
.
replace
(
os
.
getenv
(
"USERPROFILE"
,
""
),
"
%
USERPROFILE
%
"
)
elif
"fish"
in
SHELL
:
message
=
POST_MESSAGE_FISH
if
not
self
.
_modify_path
:
message
=
POST_MESSAGE_FISH_NO_MODIFY_PATH
poetry_home_bin
=
POETRY_BIN
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
)
else
:
message
=
POST_MESSAGE_UNIX
if
not
self
.
_modify_path
:
message
=
POST_MESSAGE_UNIX_NO_MODIFY_PATH
poetry_home_bin
=
POETRY_BIN
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
)
kwargs
[
"poetry_home_env"
]
=
colorize
(
"comment"
,
POETRY_ENV
.
replace
(
os
.
getenv
(
"HOME"
,
""
),
"$HOME"
)
)
kwargs
[
"poetry_home_bin"
]
=
colorize
(
"comment"
,
poetry_home_bin
)
print
(
message
.
format
(
**
kwargs
))
def
call
(
self
,
*
args
):
return
subprocess
.
check_output
(
args
,
stderr
=
subprocess
.
STDOUT
)
def
_get
(
self
,
url
):
request
=
Request
(
url
,
headers
=
{
"User-Agent"
:
"Python Poetry"
})
with
closing
(
urlopen
(
request
))
as
r
:
return
r
.
read
()
def
main
():
parser
=
argparse
.
ArgumentParser
(
description
=
"Installs the latest (or given) version of poetry"
)
parser
.
add_argument
(
"-p"
,
"--preview"
,
help
=
"install preview version"
,
dest
=
"preview"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--version"
,
help
=
"install named version"
,
dest
=
"version"
)
parser
.
add_argument
(
"-f"
,
"--force"
,
help
=
"install on top of existing version"
,
dest
=
"force"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--no-modify-path"
,
help
=
"do not modify $PATH"
,
dest
=
"no_modify_path"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"-y"
,
"--yes"
,
help
=
"accept all prompts"
,
dest
=
"accept_all"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--uninstall"
,
help
=
"uninstall poetry"
,
dest
=
"uninstall"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--file"
,
dest
=
"file"
,
action
=
"store"
,
help
=
(
"Install from a local file instead of fetching the latest version "
"of Poetry available online."
),
)
args
=
parser
.
parse_args
()
base_url
=
Installer
.
BASE_URL
if
args
.
file
is
None
:
try
:
urlopen
(
Installer
.
REPOSITORY_URL
)
except
HTTPError
as
e
:
if
e
.
code
==
404
:
base_url
=
Installer
.
FALLBACK_BASE_URL
else
:
raise
installer
=
Installer
(
version
=
args
.
version
or
os
.
getenv
(
"POETRY_VERSION"
),
preview
=
args
.
preview
or
string_to_bool
(
os
.
getenv
(
"POETRY_PREVIEW"
,
"0"
)),
force
=
args
.
force
,
modify_path
=
not
args
.
no_modify_path
,
accept_all
=
args
.
accept_all
or
string_to_bool
(
os
.
getenv
(
"POETRY_ACCEPT"
,
"0"
))
or
not
is_interactive
(),
file
=
args
.
file
,
base_url
=
base_url
,
)
if
args
.
uninstall
or
string_to_bool
(
os
.
getenv
(
"POETRY_UNINSTALL"
,
"0"
)):
return
installer
.
uninstall
()
return
installer
.
run
()
if
__name__
==
"__main__"
:
sys
.
exit
(
main
())
install-poetry.py
deleted
100644 → 0
View file @
3f37096a
"""
This script will install Poetry and its dependencies.
It does, in order:
- Creates a virtual environment using venv (or virtualenv zipapp) in the correct OS data dir which will be
- `
%
APPDATA
%
\\
pypoetry` on Windows
- ~/Library/Application Support/pypoetry on MacOS
- `${XDG_DATA_HOME}/pypoetry` (or `~/.local/share/pypoetry` if it's not set) on UNIX systems
- In `${POETRY_HOME}` if it's set.
- Installs the latest or given version of Poetry inside this virtual environment.
- Installs a `poetry` script in the Python user directory (or `${POETRY_HOME/bin}` if `POETRY_HOME` is set).
- On failure, the error log is written to poetry-installer-error-*.log and any previously existing environment
is restored.
"""
import
argparse
import
json
import
os
import
re
import
shutil
import
site
import
subprocess
import
sys
import
tempfile
from
contextlib
import
closing
from
contextlib
import
contextmanager
from
functools
import
cmp_to_key
from
io
import
UnsupportedOperation
from
pathlib
import
Path
from
typing
import
Optional
from
urllib.request
import
Request
from
urllib.request
import
urlopen
SHELL
=
os
.
getenv
(
"SHELL"
,
""
)
WINDOWS
=
sys
.
platform
.
startswith
(
"win"
)
or
(
sys
.
platform
==
"cli"
and
os
.
name
==
"nt"
)
MACOS
=
sys
.
platform
==
"darwin"
FOREGROUND_COLORS
=
{
"black"
:
30
,
"red"
:
31
,
"green"
:
32
,
"yellow"
:
33
,
"blue"
:
34
,
"magenta"
:
35
,
"cyan"
:
36
,
"white"
:
37
,
}
BACKGROUND_COLORS
=
{
"black"
:
40
,
"red"
:
41
,
"green"
:
42
,
"yellow"
:
43
,
"blue"
:
44
,
"magenta"
:
45
,
"cyan"
:
46
,
"white"
:
47
,
}
OPTIONS
=
{
"bold"
:
1
,
"underscore"
:
4
,
"blink"
:
5
,
"reverse"
:
7
,
"conceal"
:
8
}
def
style
(
fg
,
bg
,
options
):
codes
=
[]
if
fg
:
codes
.
append
(
FOREGROUND_COLORS
[
fg
])
if
bg
:
codes
.
append
(
BACKGROUND_COLORS
[
bg
])
if
options
:
if
not
isinstance
(
options
,
(
list
,
tuple
)):
options
=
[
options
]
for
option
in
options
:
codes
.
append
(
OPTIONS
[
option
])
return
"
\033
[{}m"
.
format
(
";"
.
join
(
map
(
str
,
codes
)))
STYLES
=
{
"info"
:
style
(
"cyan"
,
None
,
None
),
"comment"
:
style
(
"yellow"
,
None
,
None
),
"success"
:
style
(
"green"
,
None
,
None
),
"error"
:
style
(
"red"
,
None
,
None
),
"warning"
:
style
(
"yellow"
,
None
,
None
),
"b"
:
style
(
None
,
None
,
(
"bold"
,)),
}
def
is_decorated
():
if
WINDOWS
:
return
(
os
.
getenv
(
"ANSICON"
)
is
not
None
or
os
.
getenv
(
"ConEmuANSI"
)
==
"ON"
or
os
.
getenv
(
"Term"
)
==
"xterm"
)
if
not
hasattr
(
sys
.
stdout
,
"fileno"
):
return
False
try
:
return
os
.
isatty
(
sys
.
stdout
.
fileno
())
except
UnsupportedOperation
:
return
False
def
is_interactive
():
if
not
hasattr
(
sys
.
stdin
,
"fileno"
):
return
False
try
:
return
os
.
isatty
(
sys
.
stdin
.
fileno
())
except
UnsupportedOperation
:
return
False
def
colorize
(
style
,
text
):
if
not
is_decorated
():
return
text
return
"{}{}
\033
[0m"
.
format
(
STYLES
[
style
],
text
)
def
string_to_bool
(
value
):
value
=
value
.
lower
()
return
value
in
{
"true"
,
"1"
,
"y"
,
"yes"
}
def
data_dir
(
version
:
Optional
[
str
]
=
None
)
->
Path
:
if
os
.
getenv
(
"POETRY_HOME"
):
return
Path
(
os
.
getenv
(
"POETRY_HOME"
))
.
expanduser
()
if
WINDOWS
:
const
=
"CSIDL_APPDATA"
path
=
os
.
path
.
normpath
(
_get_win_folder
(
const
))
path
=
os
.
path
.
join
(
path
,
"pypoetry"
)
elif
MACOS
:
path
=
os
.
path
.
expanduser
(
"~/Library/Application Support/pypoetry"
)
else
:
path
=
os
.
getenv
(
"XDG_DATA_HOME"
,
os
.
path
.
expanduser
(
"~/.local/share"
))
path
=
os
.
path
.
join
(
path
,
"pypoetry"
)
if
version
:
path
=
os
.
path
.
join
(
path
,
version
)
return
Path
(
path
)
def
bin_dir
(
version
:
Optional
[
str
]
=
None
)
->
Path
:
if
os
.
getenv
(
"POETRY_HOME"
):
return
Path
(
os
.
getenv
(
"POETRY_HOME"
),
"bin"
)
.
expanduser
()
user_base
=
site
.
getuserbase
()
if
WINDOWS
:
bin_dir
=
os
.
path
.
join
(
user_base
,
"Scripts"
)
else
:
bin_dir
=
os
.
path
.
join
(
user_base
,
"bin"
)
return
Path
(
bin_dir
)
def
_get_win_folder_from_registry
(
csidl_name
):
import
winreg
as
_winreg
shell_folder_name
=
{
"CSIDL_APPDATA"
:
"AppData"
,
"CSIDL_COMMON_APPDATA"
:
"Common AppData"
,
"CSIDL_LOCAL_APPDATA"
:
"Local AppData"
,
}[
csidl_name
]
key
=
_winreg
.
OpenKey
(
_winreg
.
HKEY_CURRENT_USER
,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
,
)
dir
,
type
=
_winreg
.
QueryValueEx
(
key
,
shell_folder_name
)
return
dir
def
_get_win_folder_with_ctypes
(
csidl_name
):
import
ctypes
csidl_const
=
{
"CSIDL_APPDATA"
:
26
,
"CSIDL_COMMON_APPDATA"
:
35
,
"CSIDL_LOCAL_APPDATA"
:
28
,
}[
csidl_name
]
buf
=
ctypes
.
create_unicode_buffer
(
1024
)
ctypes
.
windll
.
shell32
.
SHGetFolderPathW
(
None
,
csidl_const
,
None
,
0
,
buf
)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char
=
False
for
c
in
buf
:
if
ord
(
c
)
>
255
:
has_high_char
=
True
break
if
has_high_char
:
buf2
=
ctypes
.
create_unicode_buffer
(
1024
)
if
ctypes
.
windll
.
kernel32
.
GetShortPathNameW
(
buf
.
value
,
buf2
,
1024
):
buf
=
buf2
return
buf
.
value
if
WINDOWS
:
try
:
from
ctypes
import
windll
# noqa: F401
_get_win_folder
=
_get_win_folder_with_ctypes
except
ImportError
:
_get_win_folder
=
_get_win_folder_from_registry
PRE_MESSAGE
=
"""# Welcome to {poetry}!
This will download and install the latest version of {poetry},
a dependency and package manager for Python.
It will add the `poetry` command to {poetry}'s bin directory, located at:
{poetry_home_bin}
You can uninstall at any time by executing this script with the --uninstall option,
and these changes will be reverted.
"""
POST_MESSAGE
=
"""{poetry} ({version}) is installed now. Great!
You can test that everything is set up by executing:
`{test_command}`
"""
POST_MESSAGE_NOT_IN_PATH
=
"""{poetry} ({version}) is installed now. Great!
To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
environment variable.
{configure_message}
Alternatively, you can call {poetry} explicitly with `{poetry_executable}`.
You can test that everything is set up by executing:
`{test_command}`
"""
POST_MESSAGE_CONFIGURE_UNIX
=
"""
Add `export PATH="{poetry_home_bin}:$PATH"` to your shell configuration file.
"""
POST_MESSAGE_CONFIGURE_FISH
=
"""
You can execute `set -U fish_user_paths {poetry_home_bin} $fish_user_paths`
"""
POST_MESSAGE_CONFIGURE_WINDOWS
=
""""""
class
PoetryInstallationError
(
RuntimeError
):
def
__init__
(
self
,
return_code
:
int
=
0
,
log
:
Optional
[
str
]
=
None
):
super
(
PoetryInstallationError
,
self
)
.
__init__
()
self
.
return_code
=
return_code
self
.
log
=
log
class
VirtualEnvironment
:
def
__init__
(
self
,
path
:
Path
)
->
None
:
self
.
_path
=
path
# str is required for compatibility with subprocess run on CPython <= 3.7 on
# Windows
self
.
_python
=
str
(
self
.
_path
.
joinpath
(
"Scripts/python.exe"
if
WINDOWS
else
"bin/python"
)
)
@property
def
path
(
self
):
return
self
.
_path
@classmethod
def
make
(
cls
,
target
:
Path
)
->
"VirtualEnvironment"
:
try
:
import
venv
builder
=
venv
.
EnvBuilder
(
clear
=
True
,
with_pip
=
True
,
symlinks
=
False
)
builder
.
ensure_directories
(
target
)
builder
.
create
(
target
)
except
ImportError
:
# fallback to using virtualenv package if venv is not available, eg: ubuntu
python_version
=
f
"{sys.version_info.major}.{sys.version_info.minor}"
virtualenv_bootstrap_url
=
(
f
"https://bootstrap.pypa.io/virtualenv/{python_version}/virtualenv.pyz"
)
with
tempfile
.
TemporaryDirectory
(
prefix
=
"poetry-installer"
)
as
temp_dir
:
virtualenv_pyz
=
Path
(
temp_dir
)
/
"virtualenv.pyz"
request
=
Request
(
virtualenv_bootstrap_url
,
headers
=
{
"User-Agent"
:
"Python Poetry"
}
)
virtualenv_pyz
.
write_bytes
(
urlopen
(
request
)
.
read
())
cls
.
run
(
sys
.
executable
,
virtualenv_pyz
,
"--clear"
,
"--always-copy"
,
target
)
# We add a special file so that Poetry can detect
# its own virtual environment
target
.
joinpath
(
"poetry_env"
)
.
touch
()
env
=
cls
(
target
)
# we do this here to ensure that outdated system default pip does not trigger
# older bugs
env
.
pip
(
"install"
,
"--disable-pip-version-check"
,
"--upgrade"
,
"pip"
)
return
env
@staticmethod
def
run
(
*
args
,
**
kwargs
)
->
subprocess
.
CompletedProcess
:
completed_process
=
subprocess
.
run
(
args
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
,
**
kwargs
,
)
if
completed_process
.
returncode
!=
0
:
raise
PoetryInstallationError
(
return_code
=
completed_process
.
returncode
,
log
=
completed_process
.
stdout
.
decode
(),
)
return
completed_process
def
python
(
self
,
*
args
,
**
kwargs
)
->
subprocess
.
CompletedProcess
:
return
self
.
run
(
self
.
_python
,
*
args
,
**
kwargs
)
def
pip
(
self
,
*
args
,
**
kwargs
)
->
subprocess
.
CompletedProcess
:
return
self
.
python
(
"-m"
,
"pip"
,
"--isolated"
,
*
args
,
**
kwargs
)
class
Cursor
:
def
__init__
(
self
)
->
None
:
self
.
_output
=
sys
.
stdout
def
move_up
(
self
,
lines
:
int
=
1
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[{}A"
.
format
(
lines
))
return
self
def
move_down
(
self
,
lines
:
int
=
1
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[{}B"
.
format
(
lines
))
return
self
def
move_right
(
self
,
columns
:
int
=
1
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[{}C"
.
format
(
columns
))
return
self
def
move_left
(
self
,
columns
:
int
=
1
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[{}D"
.
format
(
columns
))
return
self
def
move_to_column
(
self
,
column
:
int
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[{}G"
.
format
(
column
))
return
self
def
move_to_position
(
self
,
column
:
int
,
row
:
int
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[{};{}H"
.
format
(
row
+
1
,
column
))
return
self
def
save_position
(
self
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
7"
)
return
self
def
restore_position
(
self
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
8"
)
return
self
def
hide
(
self
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[?25l"
)
return
self
def
show
(
self
)
->
"Cursor"
:
self
.
_output
.
write
(
"
\x1b
[?25h
\x1b
[?0c"
)
return
self
def
clear_line
(
self
)
->
"Cursor"
:
"""
Clears all the output from the current line.
"""
self
.
_output
.
write
(
"
\x1b
[2K"
)
return
self
def
clear_line_after
(
self
)
->
"Cursor"
:
"""
Clears all the output from the current line after the current position.
"""
self
.
_output
.
write
(
"
\x1b
[K"
)
return
self
def
clear_output
(
self
)
->
"Cursor"
:
"""
Clears all the output from the cursors' current position
to the end of the screen.
"""
self
.
_output
.
write
(
"
\x1b
[0J"
)
return
self
def
clear_screen
(
self
)
->
"Cursor"
:
"""
Clears the entire screen.
"""
self
.
_output
.
write
(
"
\x1b
[2J"
)
return
self
class
Installer
:
METADATA_URL
=
"https://pypi.org/pypi/poetry/json"
VERSION_REGEX
=
re
.
compile
(
r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?"
"("
"[._-]?"
r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?"
"([.-]?dev)?"
")?"
r"(?:\+[^\s]+)?"
)
def
__init__
(
self
,
version
:
Optional
[
str
]
=
None
,
preview
:
bool
=
False
,
force
:
bool
=
False
,
accept_all
:
bool
=
False
,
git
:
Optional
[
str
]
=
None
,
path
:
Optional
[
str
]
=
None
,
)
->
None
:
self
.
_version
=
version
self
.
_preview
=
preview
self
.
_force
=
force
self
.
_accept_all
=
accept_all
self
.
_git
=
git
self
.
_path
=
path
self
.
_data_dir
=
data_dir
()
self
.
_bin_dir
=
bin_dir
()
self
.
_cursor
=
Cursor
()
def
allows_prereleases
(
self
)
->
bool
:
return
self
.
_preview
def
run
(
self
)
->
int
:
if
self
.
_git
:
version
=
self
.
_git
elif
self
.
_path
:
version
=
self
.
_path
else
:
version
,
current_version
=
self
.
get_version
()
if
version
is
None
:
return
0
self
.
display_pre_message
()
self
.
ensure_directories
()
def
_is_self_upgrade_supported
(
x
):
mx
=
self
.
VERSION_REGEX
.
match
(
x
)
if
mx
is
None
:
# the version is not semver, perhaps scm or file, we assume upgrade is
# supported
return
True
vx
=
tuple
(
int
(
p
)
for
p
in
mx
.
groups
()[:
3
])
+
(
mx
.
group
(
5
),)
return
vx
>=
(
1
,
1
,
7
)
if
version
and
not
_is_self_upgrade_supported
(
version
):
self
.
_write
(
colorize
(
"warning"
,
f
"You are installing {version}. When using the current installer,"
" this version does not support updating using the 'self update'"
" command. Please use 1.1.7 or later."
,
)
)
if
not
self
.
_accept_all
:
continue_install
=
input
(
"Do you want to continue? ([y]/n) "
)
or
"y"
if
continue_install
.
lower
()
in
{
"n"
,
"no"
}:
return
0
try
:
self
.
install
(
version
)
except
subprocess
.
CalledProcessError
as
e
:
raise
PoetryInstallationError
(
return_code
=
e
.
returncode
,
log
=
e
.
output
.
decode
()
)
self
.
_write
(
""
)
self
.
display_post_message
(
version
)
return
0
def
install
(
self
,
version
,
upgrade
=
False
):
"""
Installs Poetry in $POETRY_HOME.
"""
self
.
_write
(
"Installing {} ({})"
.
format
(
colorize
(
"info"
,
"Poetry"
),
colorize
(
"info"
,
version
)
)
)
with
self
.
make_env
(
version
)
as
env
:
self
.
install_poetry
(
version
,
env
)
self
.
make_bin
(
version
,
env
)
self
.
_data_dir
.
joinpath
(
"VERSION"
)
.
write_text
(
version
)
self
.
_install_comment
(
version
,
"Done"
)
return
0
def
uninstall
(
self
)
->
int
:
if
not
self
.
_data_dir
.
exists
():
self
.
_write
(
"{} is not currently installed."
.
format
(
colorize
(
"info"
,
"Poetry"
))
)
return
1
version
=
None
if
self
.
_data_dir
.
joinpath
(
"VERSION"
)
.
exists
():
version
=
self
.
_data_dir
.
joinpath
(
"VERSION"
)
.
read_text
()
.
strip
()
if
version
:
self
.
_write
(
"Removing {} ({})"
.
format
(
colorize
(
"info"
,
"Poetry"
),
colorize
(
"b"
,
version
)
)
)
else
:
self
.
_write
(
"Removing {}"
.
format
(
colorize
(
"info"
,
"Poetry"
)))
shutil
.
rmtree
(
str
(
self
.
_data_dir
))
for
script
in
[
"poetry"
,
"poetry.bat"
]:
if
self
.
_bin_dir
.
joinpath
(
script
)
.
exists
():
self
.
_bin_dir
.
joinpath
(
script
)
.
unlink
()
return
0
def
_install_comment
(
self
,
version
:
str
,
message
:
str
):
self
.
_overwrite
(
"Installing {} ({}): {}"
.
format
(
colorize
(
"info"
,
"Poetry"
),
colorize
(
"b"
,
version
),
colorize
(
"comment"
,
message
),
)
)
@contextmanager
def
make_env
(
self
,
version
:
str
)
->
VirtualEnvironment
:
env_path
=
self
.
_data_dir
.
joinpath
(
"venv"
)
env_path_saved
=
env_path
.
with_suffix
(
".save"
)
if
env_path
.
exists
():
self
.
_install_comment
(
version
,
"Saving existing environment"
)
if
env_path_saved
.
exists
():
shutil
.
rmtree
(
env_path_saved
)
shutil
.
move
(
env_path
,
env_path_saved
)
try
:
self
.
_install_comment
(
version
,
"Creating environment"
)
yield
VirtualEnvironment
.
make
(
env_path
)
except
Exception
as
e
:
if
env_path
.
exists
():
self
.
_install_comment
(
version
,
"An error occurred. Removing partial environment."
)
shutil
.
rmtree
(
env_path
)
if
env_path_saved
.
exists
():
self
.
_install_comment
(
version
,
"Restoring previously saved environment."
)
shutil
.
move
(
env_path_saved
,
env_path
)
raise
e
else
:
if
env_path_saved
.
exists
():
shutil
.
rmtree
(
env_path_saved
,
ignore_errors
=
True
)
def
make_bin
(
self
,
version
:
str
,
env
:
VirtualEnvironment
)
->
None
:
self
.
_install_comment
(
version
,
"Creating script"
)
self
.
_bin_dir
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
script
=
"poetry"
script_bin
=
"bin"
if
WINDOWS
:
script
=
"poetry.exe"
script_bin
=
"Scripts"
target_script
=
env
.
path
.
joinpath
(
script_bin
,
script
)
if
self
.
_bin_dir
.
joinpath
(
script
)
.
exists
():
self
.
_bin_dir
.
joinpath
(
script
)
.
unlink
()
try
:
self
.
_bin_dir
.
joinpath
(
script
)
.
symlink_to
(
target_script
)
except
OSError
:
# This can happen if the user
# does not have the correct permission on Windows
shutil
.
copy
(
target_script
,
self
.
_bin_dir
.
joinpath
(
script
))
def
install_poetry
(
self
,
version
:
str
,
env
:
VirtualEnvironment
)
->
None
:
self
.
_install_comment
(
version
,
"Installing Poetry"
)
if
self
.
_git
:
specification
=
"git+"
+
version
elif
self
.
_path
:
specification
=
version
else
:
specification
=
f
"poetry=={version}"
env
.
pip
(
"install"
,
specification
)
def
display_pre_message
(
self
)
->
None
:
kwargs
=
{
"poetry"
:
colorize
(
"info"
,
"Poetry"
),
"poetry_home_bin"
:
colorize
(
"comment"
,
self
.
_bin_dir
),
}
self
.
_write
(
PRE_MESSAGE
.
format
(
**
kwargs
))
def
display_post_message
(
self
,
version
:
str
)
->
None
:
if
WINDOWS
:
return
self
.
display_post_message_windows
(
version
)
if
SHELL
==
"fish"
:
return
self
.
display_post_message_fish
(
version
)
return
self
.
display_post_message_unix
(
version
)
def
display_post_message_windows
(
self
,
version
:
str
)
->
None
:
path
=
self
.
get_windows_path_var
()
message
=
POST_MESSAGE_NOT_IN_PATH
if
path
and
str
(
self
.
_bin_dir
)
in
path
:
message
=
POST_MESSAGE
self
.
_write
(
message
.
format
(
poetry
=
colorize
(
"info"
,
"Poetry"
),
version
=
colorize
(
"b"
,
version
),
poetry_home_bin
=
colorize
(
"comment"
,
self
.
_bin_dir
),
poetry_executable
=
colorize
(
"b"
,
self
.
_bin_dir
.
joinpath
(
"poetry"
)),
configure_message
=
POST_MESSAGE_CONFIGURE_WINDOWS
.
format
(
poetry_home_bin
=
colorize
(
"comment"
,
self
.
_bin_dir
)
),
test_command
=
colorize
(
"b"
,
"poetry --version"
),
)
)
def
get_windows_path_var
(
self
)
->
Optional
[
str
]:
import
winreg
with
winreg
.
ConnectRegistry
(
None
,
winreg
.
HKEY_CURRENT_USER
)
as
root
:
with
winreg
.
OpenKey
(
root
,
"Environment"
,
0
,
winreg
.
KEY_ALL_ACCESS
)
as
key
:
path
,
_
=
winreg
.
QueryValueEx
(
key
,
"PATH"
)
return
path
def
display_post_message_fish
(
self
,
version
:
str
)
->
None
:
fish_user_paths
=
subprocess
.
check_output
(
[
"fish"
,
"-c"
,
"echo $fish_user_paths"
]
)
.
decode
(
"utf-8"
)
message
=
POST_MESSAGE_NOT_IN_PATH
if
fish_user_paths
and
str
(
self
.
_bin_dir
)
in
fish_user_paths
:
message
=
POST_MESSAGE
self
.
_write
(
message
.
format
(
poetry
=
colorize
(
"info"
,
"Poetry"
),
version
=
colorize
(
"b"
,
version
),
poetry_home_bin
=
colorize
(
"comment"
,
self
.
_bin_dir
),
poetry_executable
=
colorize
(
"b"
,
self
.
_bin_dir
.
joinpath
(
"poetry"
)),
configure_message
=
POST_MESSAGE_CONFIGURE_FISH
.
format
(
poetry_home_bin
=
colorize
(
"comment"
,
self
.
_bin_dir
)
),
test_command
=
colorize
(
"b"
,
"poetry --version"
),
)
)
def
display_post_message_unix
(
self
,
version
:
str
)
->
None
:
paths
=
os
.
getenv
(
"PATH"
,
""
)
.
split
(
":"
)
message
=
POST_MESSAGE_NOT_IN_PATH
if
paths
and
str
(
self
.
_bin_dir
)
in
paths
:
message
=
POST_MESSAGE
self
.
_write
(
message
.
format
(
poetry
=
colorize
(
"info"
,
"Poetry"
),
version
=
colorize
(
"b"
,
version
),
poetry_home_bin
=
colorize
(
"comment"
,
self
.
_bin_dir
),
poetry_executable
=
colorize
(
"b"
,
self
.
_bin_dir
.
joinpath
(
"poetry"
)),
configure_message
=
POST_MESSAGE_CONFIGURE_UNIX
.
format
(
poetry_home_bin
=
colorize
(
"comment"
,
self
.
_bin_dir
)
),
test_command
=
colorize
(
"b"
,
"poetry --version"
),
)
)
def
ensure_directories
(
self
)
->
None
:
self
.
_data_dir
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
self
.
_bin_dir
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
def
get_version
(
self
):
current_version
=
None
if
self
.
_data_dir
.
joinpath
(
"VERSION"
)
.
exists
():
current_version
=
self
.
_data_dir
.
joinpath
(
"VERSION"
)
.
read_text
()
.
strip
()
self
.
_write
(
colorize
(
"info"
,
"Retrieving Poetry metadata"
))
metadata
=
json
.
loads
(
self
.
_get
(
self
.
METADATA_URL
)
.
decode
())
def
_compare_versions
(
x
,
y
):
mx
=
self
.
VERSION_REGEX
.
match
(
x
)
my
=
self
.
VERSION_REGEX
.
match
(
y
)
vx
=
tuple
(
int
(
p
)
for
p
in
mx
.
groups
()[:
3
])
+
(
mx
.
group
(
5
),)
vy
=
tuple
(
int
(
p
)
for
p
in
my
.
groups
()[:
3
])
+
(
my
.
group
(
5
),)
if
vx
<
vy
:
return
-
1
elif
vx
>
vy
:
return
1
return
0
self
.
_write
(
""
)
releases
=
sorted
(
metadata
[
"releases"
]
.
keys
(),
key
=
cmp_to_key
(
_compare_versions
)
)
if
self
.
_version
and
self
.
_version
not
in
releases
:
self
.
_write
(
colorize
(
"error"
,
"Version {} does not exist."
.
format
(
self
.
_version
))
)
return
None
,
None
version
=
self
.
_version
if
not
version
:
for
release
in
reversed
(
releases
):
m
=
self
.
VERSION_REGEX
.
match
(
release
)
if
m
.
group
(
5
)
and
not
self
.
allows_prereleases
():
continue
version
=
release
break
if
current_version
==
version
and
not
self
.
_force
:
self
.
_write
(
"The latest version ({}) is already installed."
.
format
(
colorize
(
"b"
,
version
)
)
)
return
None
,
current_version
return
version
,
current_version
def
_write
(
self
,
line
)
->
None
:
sys
.
stdout
.
write
(
line
+
"
\n
"
)
def
_overwrite
(
self
,
line
)
->
None
:
if
not
is_decorated
():
return
self
.
_write
(
line
)
self
.
_cursor
.
move_up
()
self
.
_cursor
.
clear_line
()
self
.
_write
(
line
)
def
_get
(
self
,
url
):
request
=
Request
(
url
,
headers
=
{
"User-Agent"
:
"Python Poetry"
})
with
closing
(
urlopen
(
request
))
as
r
:
return
r
.
read
()
def
main
():
parser
=
argparse
.
ArgumentParser
(
description
=
"Installs the latest (or given) version of poetry"
)
parser
.
add_argument
(
"-p"
,
"--preview"
,
help
=
"install preview version"
,
dest
=
"preview"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--version"
,
help
=
"install named version"
,
dest
=
"version"
)
parser
.
add_argument
(
"-f"
,
"--force"
,
help
=
"install on top of existing version"
,
dest
=
"force"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"-y"
,
"--yes"
,
help
=
"accept all prompts"
,
dest
=
"accept_all"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--uninstall"
,
help
=
"uninstall poetry"
,
dest
=
"uninstall"
,
action
=
"store_true"
,
default
=
False
,
)
parser
.
add_argument
(
"--path"
,
dest
=
"path"
,
action
=
"store"
,
help
=
(
"Install from a given path (file or directory) instead of "
"fetching the latest version of Poetry available online."
),
)
parser
.
add_argument
(
"--git"
,
dest
=
"git"
,
action
=
"store"
,
help
=
(
"Install from a git repository instead of fetching the latest version "
"of Poetry available online."
),
)
args
=
parser
.
parse_args
()
installer
=
Installer
(
version
=
args
.
version
or
os
.
getenv
(
"POETRY_VERSION"
),
preview
=
args
.
preview
or
string_to_bool
(
os
.
getenv
(
"POETRY_PREVIEW"
,
"0"
)),
force
=
args
.
force
,
accept_all
=
args
.
accept_all
or
string_to_bool
(
os
.
getenv
(
"POETRY_ACCEPT"
,
"0"
))
or
not
is_interactive
(),
path
=
args
.
path
,
git
=
args
.
git
,
)
if
args
.
uninstall
or
string_to_bool
(
os
.
getenv
(
"POETRY_UNINSTALL"
,
"0"
)):
return
installer
.
uninstall
()
try
:
return
installer
.
run
()
except
PoetryInstallationError
as
e
:
installer
.
_write
(
colorize
(
"error"
,
"Poetry installation failed."
))
if
e
.
log
is
not
None
:
import
traceback
_
,
path
=
tempfile
.
mkstemp
(
suffix
=
".log"
,
prefix
=
"poetry-installer-error-"
,
dir
=
str
(
Path
.
cwd
()),
text
=
True
,
)
installer
.
_write
(
colorize
(
"error"
,
f
"See {path} for error logs."
))
text
=
(
f
"{e.log}
\n
"
f
"Traceback:
\n\n
{''.join(traceback.format_tb(e.__traceback__))}"
)
Path
(
path
)
.
write_text
(
text
)
return
e
.
return_code
if
__name__
==
"__main__"
:
sys
.
stdout
.
write
(
colorize
(
"warning"
,
"The canonical source for Poetry's installation script is now"
" https://install.python-poetry.org. Please update your usage to reflect"
" this.
\n
"
,
)
)
sys
.
exit
(
main
())
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