1- """
2- This script will install Poetry and its dependencies.
3-
4- It does, in order:
5-
6- - Creates a virtual environment using venv (or virtualenv zipapp) in the correct OS data dir which will be
7- - `%APPDATA%\\ pypoetry` on Windows
8- - ~/Library/Application Support/pypoetry on MacOS
9- - `${XDG_DATA_HOME}/pypoetry` (or `~/.local/share/pypoetry` if it's not set) on UNIX systems
10- - In `${POETRY_HOME}` if it's set.
11- - Installs the latest or given version of Poetry inside this virtual environment.
12- - Installs a `poetry` script in the Python user directory (or `${POETRY_HOME/bin}` if `POETRY_HOME` is set).
13- - On failure, the error log is written to poetry-installer-error-*.log and any previously existing environment
14- is restored.
1+ #!/usr/bin/env python3
2+ r"""
3+ This script will install Poetry and its dependencies in an isolated fashion.
4+
5+ It will perform the following steps:
6+ * Create a new virtual environment using the built-in venv module, or the virtualenv zipapp if venv is unavailable.
7+ This will be created at a platform-specific path (or `$POETRY_HOME` if `$POETRY_HOME` is set:
8+ - `~/Library/Application Support/pypoetry` on macOS
9+ - `$XDG_DATA_HOME/pypoetry` on Linux/Unix (`$XDG_DATA_HOME` is `~/.local/share` if unset)
10+ - `%APPDATA%\pypoetry` on Windows
11+ * Update pip inside the virtual environment to avoid bugs in older versions.
12+ * Install the latest (or a given) version of Poetry inside this virtual environment using pip.
13+ * Install a `poetry` script into a platform-specific path (or `$POETRY_HOME/bin` if `$POETRY_HOME` is set):
14+ - `~/.local/bin` on Unix
15+ - `%APPDATA%\Python\Scripts` on Windows
16+ * Attempt to inform the user if they need to add this bin directory to their `$PATH`, as well as how to do so.
17+ * Upon failure, write an error log to `poetry-installer-error-<hash>.log and restore any previous environment.
18+
19+ This script performs minimal magic, and should be relatively stable. However, it is optimized for interactive developer
20+ use and trivial pipelines. If you are considering using this script in production, you should consider manually-managed
21+ installs, or use of pipx as alternatives to executing arbitrary, unversioned code from the internet. If you prefer this
22+ script to alternatives, consider maintaining a local copy as part of your infrastructure.
23+
24+ For full documentation, visit https://python-poetry.org/docs/#installation.
1525"""
1626
1727import argparse
1828import json
1929import os
2030import re
2131import shutil
22- import site
2332import subprocess
2433import sys
2534import sysconfig
@@ -134,38 +143,29 @@ def string_to_bool(value):
134143 return value in {"true" , "1" , "y" , "yes" }
135144
136145
137- def data_dir (version : Optional [ str ] = None ) -> Path :
146+ def data_dir () -> Path :
138147 if os .getenv ("POETRY_HOME" ):
139148 return Path (os .getenv ("POETRY_HOME" )).expanduser ()
140149
141150 if WINDOWS :
142- const = "CSIDL_APPDATA"
143- path = os .path .normpath (_get_win_folder (const ))
144- path = os .path .join (path , "pypoetry" )
151+ base_dir = Path (_get_win_folder ("CSIDL_APPDATA" ))
145152 elif MACOS :
146- path = os . path . expanduser ("~/Library/Application Support/pypoetry" )
153+ base_dir = Path ("~/Library/Application Support" ). expanduser ( )
147154 else :
148- path = os .getenv ("XDG_DATA_HOME" , os .path .expanduser ("~/.local/share" ))
149- path = os .path .join (path , "pypoetry" )
150-
151- if version :
152- path = os .path .join (path , version )
155+ base_dir = Path (os .getenv ("XDG_DATA_HOME" , "~/.local/share" )).expanduser ()
153156
154- return Path (path )
157+ base_dir = base_dir .resolve ()
158+ return base_dir / "pypoetry"
155159
156160
157- def bin_dir (version : Optional [ str ] = None ) -> Path :
161+ def bin_dir () -> Path :
158162 if os .getenv ("POETRY_HOME" ):
159- return Path (os .getenv ("POETRY_HOME" ), "bin" ).expanduser ()
160-
161- user_base = site .getuserbase ()
163+ return Path (os .getenv ("POETRY_HOME" )).expanduser () / "bin"
162164
163165 if WINDOWS and not MINGW :
164- bin_dir = os . path . join ( user_base , " Scripts")
166+ return Path ( _get_win_folder ( "CSIDL_APPDATA" )) / "Python/ Scripts"
165167 else :
166- bin_dir = os .path .join (user_base , "bin" )
167-
168- return Path (bin_dir )
168+ return Path ("~/.local/bin" ).expanduser ()
169169
170170
171171def _get_win_folder_from_registry (csidl_name ):
@@ -181,9 +181,9 @@ def _get_win_folder_from_registry(csidl_name):
181181 _winreg .HKEY_CURRENT_USER ,
182182 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" ,
183183 )
184- dir , type = _winreg .QueryValueEx (key , shell_folder_name )
184+ path , _ = _winreg .QueryValueEx (key , shell_folder_name )
185185
186- return dir
186+ return path
187187
188188
189189def _get_win_folder_with_ctypes (csidl_name ):
@@ -477,9 +477,26 @@ def __init__(
477477 self ._accept_all = accept_all
478478 self ._git = git
479479 self ._path = path
480- self ._data_dir = data_dir ()
481- self ._bin_dir = bin_dir ()
480+
482481 self ._cursor = Cursor ()
482+ self ._bin_dir = None
483+ self ._data_dir = None
484+
485+ @property
486+ def bin_dir (self ) -> Path :
487+ if not self ._bin_dir :
488+ self ._bin_dir = bin_dir ()
489+ return self ._bin_dir
490+
491+ @property
492+ def data_dir (self ) -> Path :
493+ if not self ._data_dir :
494+ self ._data_dir = data_dir ()
495+ return self ._data_dir
496+
497+ @property
498+ def version_file (self ) -> Path :
499+ return self .data_dir .joinpath ("VERSION" )
483500
484501 def allows_prereleases (self ) -> bool :
485502 return self ._preview
@@ -536,7 +553,7 @@ def _is_self_upgrade_supported(x):
536553
537554 return 0
538555
539- def install (self , version , upgrade = False ):
556+ def install (self , version ):
540557 """
541558 Installs Poetry in $POETRY_HOME.
542559 """
@@ -549,22 +566,22 @@ def install(self, version, upgrade=False):
549566 with self .make_env (version ) as env :
550567 self .install_poetry (version , env )
551568 self .make_bin (version , env )
552- self ._data_dir . joinpath ( "VERSION" ) .write_text (version )
569+ self .version_file .write_text (version )
553570 self ._install_comment (version , "Done" )
554571
555572 return 0
556573
557574 def uninstall (self ) -> int :
558- if not self ._data_dir .exists ():
575+ if not self .data_dir .exists ():
559576 self ._write (
560577 "{} is not currently installed." .format (colorize ("info" , "Poetry" ))
561578 )
562579
563580 return 1
564581
565582 version = None
566- if self ._data_dir . joinpath ( "VERSION" ) .exists ():
567- version = self ._data_dir . joinpath ( "VERSION" ) .read_text ().strip ()
583+ if self .version_file .exists ():
584+ version = self .version_file .read_text ().strip ()
568585
569586 if version :
570587 self ._write (
@@ -575,10 +592,10 @@ def uninstall(self) -> int:
575592 else :
576593 self ._write ("Removing {}" .format (colorize ("info" , "Poetry" )))
577594
578- shutil .rmtree (str (self ._data_dir ))
595+ shutil .rmtree (str (self .data_dir ))
579596 for script in ["poetry" , "poetry.bat" , "poetry.exe" ]:
580- if self ._bin_dir .joinpath (script ).exists ():
581- self ._bin_dir .joinpath (script ).unlink ()
597+ if self .bin_dir .joinpath (script ).exists ():
598+ self .bin_dir .joinpath (script ).unlink ()
582599
583600 return 0
584601
@@ -593,7 +610,7 @@ def _install_comment(self, version: str, message: str):
593610
594611 @contextmanager
595612 def make_env (self , version : str ) -> VirtualEnvironment :
596- env_path = self ._data_dir .joinpath ("venv" )
613+ env_path = self .data_dir .joinpath ("venv" )
597614 env_path_saved = env_path .with_suffix (".save" )
598615
599616 if env_path .exists ():
@@ -625,20 +642,20 @@ def make_env(self, version: str) -> VirtualEnvironment:
625642
626643 def make_bin (self , version : str , env : VirtualEnvironment ) -> None :
627644 self ._install_comment (version , "Creating script" )
628- self ._bin_dir .mkdir (parents = True , exist_ok = True )
645+ self .bin_dir .mkdir (parents = True , exist_ok = True )
629646
630647 script = "poetry.exe" if WINDOWS else "poetry"
631648 target_script = env .bin_path .joinpath (script )
632649
633- if self ._bin_dir .joinpath (script ).exists ():
634- self ._bin_dir .joinpath (script ).unlink ()
650+ if self .bin_dir .joinpath (script ).exists ():
651+ self .bin_dir .joinpath (script ).unlink ()
635652
636653 try :
637- self ._bin_dir .joinpath (script ).symlink_to (target_script )
654+ self .bin_dir .joinpath (script ).symlink_to (target_script )
638655 except OSError :
639656 # This can happen if the user
640657 # does not have the correct permission on Windows
641- shutil .copy (target_script , self ._bin_dir .joinpath (script ))
658+ shutil .copy (target_script , self .bin_dir .joinpath (script ))
642659
643660 def install_poetry (self , version : str , env : VirtualEnvironment ) -> None :
644661 self ._install_comment (version , "Installing Poetry" )
@@ -655,7 +672,7 @@ def install_poetry(self, version: str, env: VirtualEnvironment) -> None:
655672 def display_pre_message (self ) -> None :
656673 kwargs = {
657674 "poetry" : colorize ("info" , "Poetry" ),
658- "poetry_home_bin" : colorize ("comment" , self ._bin_dir ),
675+ "poetry_home_bin" : colorize ("comment" , self .bin_dir ),
659676 }
660677 self ._write (PRE_MESSAGE .format (** kwargs ))
661678
@@ -672,17 +689,17 @@ def display_post_message_windows(self, version: str) -> None:
672689 path = self .get_windows_path_var ()
673690
674691 message = POST_MESSAGE_NOT_IN_PATH
675- if path and str (self ._bin_dir ) in path :
692+ if path and str (self .bin_dir ) in path :
676693 message = POST_MESSAGE
677694
678695 self ._write (
679696 message .format (
680697 poetry = colorize ("info" , "Poetry" ),
681698 version = colorize ("b" , version ),
682- poetry_home_bin = colorize ("comment" , self ._bin_dir ),
683- poetry_executable = colorize ("b" , self ._bin_dir .joinpath ("poetry" )),
699+ poetry_home_bin = colorize ("comment" , self .bin_dir ),
700+ poetry_executable = colorize ("b" , self .bin_dir .joinpath ("poetry" )),
684701 configure_message = POST_MESSAGE_CONFIGURE_WINDOWS .format (
685- poetry_home_bin = colorize ("comment" , self ._bin_dir )
702+ poetry_home_bin = colorize ("comment" , self .bin_dir )
686703 ),
687704 test_command = colorize ("b" , "poetry --version" ),
688705 )
@@ -703,17 +720,17 @@ def display_post_message_fish(self, version: str) -> None:
703720 ).decode ("utf-8" )
704721
705722 message = POST_MESSAGE_NOT_IN_PATH
706- if fish_user_paths and str (self ._bin_dir ) in fish_user_paths :
723+ if fish_user_paths and str (self .bin_dir ) in fish_user_paths :
707724 message = POST_MESSAGE
708725
709726 self ._write (
710727 message .format (
711728 poetry = colorize ("info" , "Poetry" ),
712729 version = colorize ("b" , version ),
713- poetry_home_bin = colorize ("comment" , self ._bin_dir ),
714- poetry_executable = colorize ("b" , self ._bin_dir .joinpath ("poetry" )),
730+ poetry_home_bin = colorize ("comment" , self .bin_dir ),
731+ poetry_executable = colorize ("b" , self .bin_dir .joinpath ("poetry" )),
715732 configure_message = POST_MESSAGE_CONFIGURE_FISH .format (
716- poetry_home_bin = colorize ("comment" , self ._bin_dir )
733+ poetry_home_bin = colorize ("comment" , self .bin_dir )
717734 ),
718735 test_command = colorize ("b" , "poetry --version" ),
719736 )
@@ -723,30 +740,30 @@ def display_post_message_unix(self, version: str) -> None:
723740 paths = os .getenv ("PATH" , "" ).split (":" )
724741
725742 message = POST_MESSAGE_NOT_IN_PATH
726- if paths and str (self ._bin_dir ) in paths :
743+ if paths and str (self .bin_dir ) in paths :
727744 message = POST_MESSAGE
728745
729746 self ._write (
730747 message .format (
731748 poetry = colorize ("info" , "Poetry" ),
732749 version = colorize ("b" , version ),
733- poetry_home_bin = colorize ("comment" , self ._bin_dir ),
734- poetry_executable = colorize ("b" , self ._bin_dir .joinpath ("poetry" )),
750+ poetry_home_bin = colorize ("comment" , self .bin_dir ),
751+ poetry_executable = colorize ("b" , self .bin_dir .joinpath ("poetry" )),
735752 configure_message = POST_MESSAGE_CONFIGURE_UNIX .format (
736- poetry_home_bin = colorize ("comment" , self ._bin_dir )
753+ poetry_home_bin = colorize ("comment" , self .bin_dir )
737754 ),
738755 test_command = colorize ("b" , "poetry --version" ),
739756 )
740757 )
741758
742759 def ensure_directories (self ) -> None :
743- self ._data_dir .mkdir (parents = True , exist_ok = True )
744- self ._bin_dir .mkdir (parents = True , exist_ok = True )
760+ self .data_dir .mkdir (parents = True , exist_ok = True )
761+ self .bin_dir .mkdir (parents = True , exist_ok = True )
745762
746763 def get_version (self ):
747764 current_version = None
748- if self ._data_dir . joinpath ( "VERSION" ) .exists ():
749- current_version = self ._data_dir . joinpath ( "VERSION" ) .read_text ().strip ()
765+ if self .version_file .exists ():
766+ current_version = self .version_file .read_text ().strip ()
750767
751768 self ._write (colorize ("info" , "Retrieving Poetry metadata" ))
752769
0 commit comments