diff --git a/.appveyor.yml b/.appveyor.yml index 4b72bce610d..10039084d88 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,6 +7,8 @@ environment: - PYTHON: "C:/Python27-x64" - PYTHON: "C:/Python35-x64" - PYTHON: "C:/Python36-x64" + - PYTHON: "C:/Python37-x64" + install: - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" @@ -15,8 +17,8 @@ install: # about it being out of date. - "python -m pip install --disable-pip-version-check --user --upgrade pip" - # Create poetry virtualenv - - "python -m pip install poetry --pre" + # Installing Poetry + - "python -m pip install poetry --pre -U" # Install dependencies - "poetry install -v" diff --git a/.gitignore b/.gitignore index cdfa7189fc7..e5dd3e40d51 100644 --- a/.gitignore +++ b/.gitignore @@ -29,9 +29,9 @@ setup.cfg MANIFEST.in /setup.py /docs/site/* -pyproject.lock /tests/fixtures/simple_project/setup.py /tests/fixtures/project_with_extras/setup.py .mypy_cache .venv +/releases/* diff --git a/.travis.yml b/.travis.yml index f84063b3e56..1ec9607321e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,26 +7,25 @@ stages: cache: pip: true directories: - - $HOME/.cache/pypoetry - - $HOME/.cache/pre-commit + - "$HOME/.cache/pypoetry" + - "$HOME/.cache/pre-commit" install: - - pip install poetry --pre + - pip install pip -U + - pip install poetry --pre -U - poetry install -v script: pytest -q tests/ jobs: include: - - python: "2.7" - - python: "3.4" - - python: "3.5" - - python: "3.6" - - - stage: linting - python: "3.6" - install: - - pip install pre-commit - - pre-commit install-hooks - script: - - pre-commit run --all-files + - python: '2.7' + - python: '3.5' + - python: '3.6' + - stage: linting + python: '3.6' + install: + - pip install pre-commit + - pre-commit install-hooks + script: + - pre-commit run --all-files diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..b4f016e45ca --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +# This file is part of Poetry +# https://github.com/sdispater/poetry + +# Licensed under the MIT license: +# http://www.opensource.org/licenses/MIT-license +# Copyright (c) 2018 Sébastien Eustace + +POETRY_RELEASE := $$(sed -n -E "s/__version__ = '(.+)'/\1/p" poetry/__version__.py) + +# lists all available targets +list: + @sh -c "$(MAKE) -p no_targets__ | \ + awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {\ + split(\$$1,A,/ /);for(i in A)print A[i]\ + }' | grep -v '__\$$' | grep -v 'make\[1\]' | grep -v 'Makefile' | sort" +# required for list +no_targets__: + +# install all dependencies +setup: setup-python + +# test your application (tests in the tests/ directory) +test: + @py.test --cov=poetry --cov-config .coveragerc tests/ -sq + +release: build linux_release osx_release + +build: + @poetry build + @python sonnet make:release + +publish: + @poetry publish + +wheel: + @poetry build -v + +linux_release: + docker pull quay.io/pypa/manylinux1_x86_64 + docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/make-linux-release.sh + +# run tests against all supported python versions +tox: + @tox diff --git a/docs/docs/index.md b/docs/docs/index.md index ed17f4373ed..a5f28cce992 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -20,30 +20,70 @@ recommended way of installing `poetry`. curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python ``` -Alternatively, you can download the `get-poetry.py` file and execute it separately. +!!! note -If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py`: + You only need to install Poetry once. It will automatically pick up the current + Python version and use it to [create virtualenvs](/docs/basic-usage/#poetry-and-virtualenvs) accordingly. -```bash -python get-poetry.py --preview -``` +The installer installs the `poetry` tool to Poetry's `bin` directory. +On Unix it is located at `$HOME/.poetry/bin` and on Windows at `%USERPROFILE%\.poetry\bin`. -Similarly, if you want to install a specific version, you can use `--version`: +This directory will be in your `$PATH` environment variable, +which means you can run them from the shell without further configuration. +Open a new shell and type the following: ```bash -python get-poetry.py --version 0.7.0 +poetry --version ``` +If you see something like `Poetry 0.11.4` then you are ready to use Poetry. +If you decide Poetry isn't your thing, you can completely remove it from your system +by running the installer again with the `--uninstall` option. + !!!note - Using `pip` to install `poetry` is also possible. - + Alternatively, you can download the `get-poetry.py` file and execute it separately. + + If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py`: + + ```bash + python get-poetry.py --preview + ``` + + Similarly, if you want to install a specific version, you can use `--version`: + ```bash - pip install --user poetry - ``` - - Be aware, however, that it will also install poetry's dependencies - which might cause conflicts. + python get-poetry.py --version 0.7.0 + ``` + + Note that the installer does not support Poetry releases < 12.0. + +### Alternative installation methods (not recommended) + +#### Installing with `pip` + +Using `pip` to install Poetry is possible. + +```bash +pip install --user poetry +``` + +!!!warning + + Be aware that it will also install Poetry's dependencies + which might cause conflicts with other packages. + +#### Installing with `pipsi` + +Using [`pipsi`](https://github.com/mitsuhiko/pipsi) to install Poetry is also possible. + +```bash +pipsi install poetry +``` + +Make sure your installed version of `pipsi` is at least version `0.10`, +otherwise Poetry will not function properly. You can get it from its +[Github repository](https://github.com/mitsuhiko/pipsi). ## Updating `poetry` @@ -67,6 +107,11 @@ to `self:update`. poetry self:update 0.8.0 ``` +!!!note + + The `self:update` command will only work if you used the recommended + installer to install Poetry. + ## Enable tab completion for Bash, Fish, or Zsh @@ -91,7 +136,7 @@ poetry completions zsh > ~/.zfunc/_poetry !!! note You may need to restart your shell in order for the changes to take effect. - + For `zsh`, you must then add the following line in your `~/.zshrc` before `compinit`: ```bash diff --git a/get-poetry.py b/get-poetry.py index 0281cf0c967..cf66c1631ac 100644 --- a/get-poetry.py +++ b/get-poetry.py @@ -4,38 +4,60 @@ It does, in order: - - Downloads the latest stable version of poetry. - - Checks if the _vendor directory is empty. - - If the _vendor directory is not empty, empties it. - - Installs all dependencies in the _vendor directory + - 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. -This ensure that poetry will look for its dependencies inside -the _vendor directory without tampering with the base system. +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. -Note, however, that installing poetry via pip will still work, -since if poetry does not find the dependencies in the _vendor -directory, it will look for them in the base system. +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 contextmanager -from email.parser import Parser from functools import cmp_to_key -from glob import glob +from gzip import GzipFile +from io import UnsupportedOperation try: + from urllib.error import HTTPError from urllib.request import urlopen except ImportError: + from urllib2 import HTTPError 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 + + +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") + FOREGROUND_COLORS = { "black": 30, @@ -85,6 +107,7 @@ def style(fg, bg, options): "info": style("green", None, None), "comment": style("yellow", None, None), "error": style("red", None, None), + "warning": style("yellow", None, None), } @@ -127,9 +150,120 @@ def temporary_directory(*args, **kwargs): shutil.rmtree(name) +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.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 = """#!/usr/bin/env python +import glob +import sys +import os + +lib = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "lib")) + +sys.path.insert(0, lib) + +if __name__ == "__main__": + from poetry.console import main + + main() +""" + +BAT = "@echo off\r\npython %USERPROFILE%/.poetry/bin/poetry %*\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 with `poetry self:uninstall`, +or 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_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_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_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( "v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?" @@ -141,15 +275,59 @@ class Installer: "(?:\+[^\s]+)?" ) - def __init__(self, version=None, preview=False): + BASE_URL = "https://github.com/sdispater/poetry/releases/download/" + + def __init__( + self, + version=None, + preview=False, + force=False, + accept_all=False, + base_url=BASE_URL, + ): self._version = version self._preview = preview + self._force = force + self._modify_path = True + self._accept_all = accept_all + self._base_url = base_url def allows_prereleases(self): return self._preview def run(self): - print(colorize("info", "Retrieving metadata")) + 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) + except subprocess.CalledProcessError as e: + print(colorize("error", "An error has occured: {}".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): + print(colorize("info", "Retrieving Poetry metadata")) r = urlopen(self.METADATA_URL) metadata = json.loads(r.read().decode()) @@ -175,9 +353,9 @@ def _compare_versions(x, y): ) if self._version and self._version not in releases: - print(colorize("error", "Version {} does not exist".format(self._version))) + print(colorize("error", "Version {} does not exist.".format(self._version))) - return 1 + return None, None version = self._version if not version: @@ -190,132 +368,409 @@ def _compare_versions(x, y): break - try: - import poetry + current_version = None + if os.path.exists(POETRY_LIB): + with open(os.path.join(POETRY_LIB, "poetry", "__version__.py")) as f: + version_content = f.read() - poetry_version = poetry.__version__ - except ImportError: - poetry_version = None + 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 poetry_version == version: + if current_version == version and not self._force: print("Latest version already installed.") - return 0 + 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 simple press the Enter key to keave 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 customize_install(self): + if not self._accept_all: + print("Before we start, please answer the following questions.") + print("You may simple press the Enter key to keave unchanged.") + + modify_path = input("Modify PATH variable? ([y]/n) ") or "y" + if modify_path.lower() in {"n", "no"}: + self._modify_path = False + + print("") + + def ensure_home(self): + """ + Ensures that $POETRY_HOME exists or create it. + """ + if not os.path.exists(POETRY_HOME): + os.mkdir(POETRY_HOME, 0o755) + + 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): + """ + Installs Poetry in $POETRY_HOME. + """ 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: - return self.install(version) - except subprocess.CalledProcessError as e: - print(colorize("error", "An error has occured: {}".format(str(e)))) - print(e.output.decode()) + self._make_lib(version) + except Exception: + if not os.path.exists(POETRY_LIB_BACKUP): + raise - return e.returncode + shutil.copytree(POETRY_LIB_BACKUP, POETRY_LIB) + shutil.rmtree(POETRY_LIB_BACKUP) - def install(self, version): - # Most of the work will be delegated to pip - with temporary_directory(prefix="poetry-installer-") as dir: - dist = os.path.join(dir, "dist") - print(" - Getting dependencies") - try: - self.call( - self.CURRENT_PYTHON, - "-m", - "pip", - "install", - "poetry=={}".format(version), - "--target", - dist, - ) - except subprocess.CalledProcessError as e: - if "must supply either home or prefix/exec-prefix" in e.output.decode(): - # Homebrew Python and possible other installations - # We workaround this issue by temporarily changing - # the --user directory - original_user = os.getenv("PYTHONUSERBASE") - os.environ["PYTHONUSERBASE"] = dir - self.call( - self.CURRENT_PYTHON, - "-m", - "pip", - "install", - "poetry=={}".format(version), - "--user", - "--ignore-installed", - ) + raise + finally: + if os.path.exists(POETRY_LIB_BACKUP): + shutil.rmtree(POETRY_LIB_BACKUP) - if original_user is not None: - os.environ["PYTHONUSERBASE"] = original_user - else: - del os.environ["PYTHONUSERBASE"] - - # Finding site-package directory - lib = os.path.join(dir, "lib") - lib_python = list(glob(os.path.join(lib, "python*")))[0] - site_packages = os.path.join(lib_python, "site-packages") - shutil.copytree(site_packages, dist) - else: - raise - - print(" - Vendorizing dependencies") - - poetry_dir = os.path.join(dist, "poetry") - vendor_dir = os.path.join(poetry_dir, "_vendor") - - # Everything, except poetry itself, should - # be put in the _vendor directory - for file in glob(os.path.join(dist, "*")): - if ( - os.path.basename(file).startswith("poetry") - or os.path.basename(file) == "__pycache__" - ): - continue + def _make_lib(self, version): + # We get the payload from the remote host + url = self._base_url + "{}/".format(version) + name = "poetry-{}-{}.tar.gz".format(version, sys.platform) + checksum = "poetry-{}-{}.sha256sum".format(version, sys.platform) - dest = os.path.join(vendor_dir, os.path.basename(file)) - if os.path.isdir(file): - shutil.copytree(file, dest) - shutil.rmtree(file) - else: - shutil.copy(file, dest) - os.unlink(file) + try: + r = urlopen(url + "{}".format(checksum)) + except HTTPError as e: + if e.code == 404: + raise RuntimeError("Could not find {} file".format(checksum)) - wheel_data = os.path.join( - dist, "poetry-{}.dist-info".format(version), "WHEEL" - ) - with open(wheel_data) as f: - wheel_data = Parser().parsestr(f.read()) + raise - tag = wheel_data["Tag"] + checksum = r.read().decode() - # Repack everything and install - print(" - Installing {}".format(colorize("info", "poetry"))) + try: + r = urlopen(_url + "{}".format(name)) + except HTTPError as e: + if e.code == 404: + raise RuntimeError("Could not find {} file".format(name)) - shutil.make_archive( - os.path.join(dir, "poetry-{}-{}".format(version, tag)), - format="zip", - root_dir=str(dist), - ) + raise + + meta = r.info() + size = int(meta["Content-Length"]) + current = 0 + block_size = 8192 - os.rename( - os.path.join(dir, "poetry-{}-{}.zip".format(version, tag)), - os.path.join(dir, "poetry-{}-{}.whl".format(version, tag)), + 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() + ) + ) + + gz = GzipFile(tar, mode="rb") + try: + with tarfile.TarFile(tar, fileobj=gz, format=tarfile.PAX_FORMAT) as f: + f.extractall(POETRY_LIB) + finally: + gz.close() + + def make_bin(self): + if not os.path.exists(POETRY_BIN): + os.mkdir(POETRY_BIN, 0o755) + + ext = "" + if WINDOWS: + with open(os.path.join(POETRY_BIN, "poetry.bat"), "w") as f: + f.write(BAT) + + with open(os.path.join(POETRY_BIN, "poetry"), "w") as f: + f.write(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(self.get_export_string()) + + def update_path(self): + """ + Tries to update the $PATH automatically. + """ + if WINDOWS: + return self.add_to_windows_path() + + # Updating any profile we can on UNIX systems + export_string = self.get_export_string() - self.call( - self.CURRENT_PYTHON, - "-m", - "pip", - "install", - "--upgrade", - "--no-deps", - os.path.join(dir, "poetry-{}-{}.whl".format(version, tag)), + addition = "\n{}".format(export_string) + + updated = [] + 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(addition) + + updated.append(os.path.relpath(profile, HOME)) + + 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 + from ctypes.wintypes import HWND + from ctypes.wintypes import LPARAM + from ctypes.wintypes import LPVOID + from ctypes.wintypes import UINT + from ctypes.wintypes import WPARAM + + 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, + u"Environment", + SMTO_ABORTIFHUNG, + 5000, + ctypes.byref(result), + ) + + def remove_from_path(self): + if WINDOWS: + return self.remove_from_windows_path() + + return self.remove_from_unix_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): + pass + + 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")] + + shell = os.getenv("SHELL") + if "zsh" in shell: + zdotdir = os.getenv("ZDOTDIR", HOME) + profiles.append(os.path.join(zdotdir, ".zprofile")) + + 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 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): + kwargs = { + "poetry": colorize("info", "Poetry"), + "poetry_home_bin": colorize( + "comment", + POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME").replace( + os.getenv("USERPROFILE", ""), "%USERPROFILE%" + ), + ), + } + + print(PRE_UNINSTALL_MESSAGE.format(**kwargs)) + + def display_post_message(self, version): print("") - print( - "{} ({}) successfully installed!".format( - colorize("info", "poetry"), colorize("comment", version) + + 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%" ) - ) + 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) @@ -329,14 +784,28 @@ def main(): "-p", "--preview", dest="preview", action="store_true", default=False ) parser.add_argument("--version", dest="version") + parser.add_argument( + "-f", "--force", dest="force", action="store_true", default=False + ) + parser.add_argument( + "-y", "--yes", dest="accept_all", action="store_true", default=False + ) + parser.add_argument( + "--uninstall", dest="uninstall", action="store_true", default=False + ) args = parser.parse_args() installer = Installer( version=args.version or os.getenv("POETRY_VERSION"), preview=args.preview or os.getenv("POETRY_PREVIEW"), + force=args.force, + accept_all=args.accept_all, ) + if args.uninstall: + return installer.uninstall() + return installer.run() diff --git a/make-linux-release.sh b/make-linux-release.sh new file mode 100755 index 00000000000..5bd8a98a457 --- /dev/null +++ b/make-linux-release.sh @@ -0,0 +1,14 @@ +#!/bin/bash +PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m" + +cd /io +/opt/python/cp37-cp37m/bin/pip install poetry --pre -U +/opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false +/opt/python/cp37-cp37m/bin/poetry install --no-dev +/opt/python/cp37-cp37m/bin/python sonnet make:release \ + -P "2.7:/opt/python/cp27-cp27m/bin/python" \ + -P "3.4:/opt/python/cp34-cp34m/bin/python" \ + -P "3.5:/opt/python/cp35-cp35m/bin/python" \ + -P "3.6:/opt/python/cp36-cp36m/bin/python" \ + -P "3.7:/opt/python/cp37-cp37m/bin/python" +cd - diff --git a/poetry/__init__.py b/poetry/__init__.py index 5a0e6815d07..ae18b1eb4d6 100644 --- a/poetry/__init__.py +++ b/poetry/__init__.py @@ -3,8 +3,11 @@ _ROOT = os.path.dirname(os.path.realpath(__file__)) _VENDOR = os.path.join(_ROOT, "_vendor") +_CURRENT_VENDOR = os.path.join( + _VENDOR, "py{}".format(".".join(str(v) for v in sys.version_info[:2])) +) # Add vendored dependencies to path. -sys.path.insert(0, _VENDOR) +sys.path.insert(0, _CURRENT_VENDOR) from .__version__ import __version__ # noqa diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py index 7f492d619d9..55f599bfbbb 100644 --- a/poetry/console/commands/self/update.py +++ b/poetry/console/commands/self/update.py @@ -1,11 +1,19 @@ +import hashlib import os import shutil import subprocess import sys - -from email.parser import Parser +import tarfile from functools import cmp_to_key +from gzip import GzipFile + +try: + from urllib.error import HTTPError + from urllib.request import urlopen +except ImportError: + from urllib2 import HTTPError + from urllib2 import urlopen from ..command import Command @@ -19,12 +27,41 @@ class SelfUpdateCommand(Command): { --preview : Install prereleases. } """ + BASE_URL = "https://poetry.eustace.io" + + @property + def home(self): + from poetry.utils._compat import Path + from poetry.utils.appdirs import expanduser + + home = Path(expanduser("~")) + + return home / ".poetry" + + @property + def lib(self): + return self.home / "lib" + + @property + def lib_backup(self): + return self.home / "lib-backup" + def handle(self): from poetry.__version__ import __version__ from poetry.repositories.pypi_repository import PyPiRepository from poetry.semver import Version + from poetry.utils._compat import Path from poetry.utils._compat import decode + current = Path(__file__) + try: + current.relative_to(self.home) + except ValueError: + raise RuntimeError( + "Poetry was not installed with the recommended installer. " + "Cannot update automatically." + ) + version = self.argument("version") if not version: version = ">=" + __version__ @@ -83,98 +120,103 @@ def handle(self): return e.returncode def update(self, release): - from poetry.utils._compat import Path - from poetry.utils.helpers import temporary_directory - version = release.version self.line("Updating to {}".format(version)) - prefix = sys.prefix - base_prefix = getattr(sys, "base_prefix", None) - real_prefix = getattr(sys, "real_prefix", None) - - prefix_poetry = self._bin_path(Path(prefix), "poetry") - if prefix_poetry.exists(): - pip = self._bin_path(prefix_poetry.parent.parent, "pip").resolve() - elif ( - base_prefix - and base_prefix != prefix - and self._bin_path(Path(base_prefix), "poetry").exists() - ): - pip = self._bin_path(Path(base_prefix), "pip") - elif real_prefix: - pip = self._bin_path(Path(real_prefix), "pip") - else: - pip = self._bin_path(Path(prefix), "pip") - - if not pip.exists(): - raise RuntimeError("Unable to determine poetry's path") - - with temporary_directory(prefix="poetry-update-") as temp_dir: - temp_dir = Path(temp_dir) - dist = temp_dir / "dist" - self.line(" - Getting dependencies") - self.process( - str(pip), - "install", - "-U", - "poetry=={}".format(release.version), - "--target", - str(dist), - ) - - self.line(" - Vendorizing dependencies") - - poetry_dir = dist / "poetry" - vendor_dir = poetry_dir / "_vendor" - - # Everything, except poetry itself, should - # be put in the _vendor directory - for file in dist.glob("*"): - if file.name.startswith("poetry"): - continue + if self.lib_backup.exists(): + shutil.rmtree(str(self.lib_backup)) - dest = vendor_dir / file.name - if file.is_dir(): - shutil.copytree(str(file), str(dest)) - shutil.rmtree(str(file)) - else: - shutil.copy(str(file), str(dest)) - os.unlink(str(file)) + # Backup the current installation + if self.lib.exists(): + shutil.copytree(str(self.lib), str(self.lib_backup)) + shutil.rmtree(str(self.lib)) - wheel_data = dist / "poetry-{}.dist-info".format(version) / "WHEEL" - with wheel_data.open() as f: - wheel_data = Parser().parsestr(f.read()) + try: + self._update(version) + except Exception: + if not self.lib_backup.exists(): + raise + + shutil.copytree(str(self.lib_backup), str(self.lib)) + shutil.rmtree(str(self.lib_backup)) + + raise + finally: + if self.lib_backup.exists(): + shutil.rmtree(str(self.lib_backup)) + + self.line("") + self.line( + "Poetry ({}) is installed now. Great!".format( + version + ) + ) - tag = wheel_data["Tag"] + def _update(self, version): + from poetry.utils.helpers import temporary_directory - # Repack everything and install - self.line(" - Updating poetry") + checksum = "poetry-{}-{}.sha256sum".format(version, sys.platform) - shutil.make_archive( - str(temp_dir / "poetry-{}-{}".format(version, tag)), - format="zip", - root_dir=str(dist), - ) + try: + r = urlopen(self.BASE_URL + "/releases/{}".format(checksum)) + except HTTPError as e: + if e.code == 404: + raise RuntimeError("Could not find {} file".format(checksum)) - os.rename( - str(temp_dir / "poetry-{}-{}.zip".format(version, tag)), - str(temp_dir / "poetry-{}-{}.whl".format(version, tag)), - ) + raise - self.process( - str(pip), - "install", - "--upgrade", - "--no-deps", - str(temp_dir / "poetry-{}-{}.whl".format(version, tag)), - ) + checksum = r.read().decode() - self.line("") - self.line( - "poetry ({}) " - "successfully installed!".format(version) - ) + # We get the payload from the remote host + name = "poetry-{}-{}.tar.gz".format(version, sys.platform) + try: + r = urlopen(self.BASE_URL + "/releases/{}".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 + + bar = self.progress_bar(max=size) + bar.set_format(" - Downloading {} %percent%%".format(name)) + bar.start() + + sha = hashlib.sha256() + with temporary_directory(prefix="poetry-updater-") 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) + + bar.set_progress(current) + + bar.finish() + + # Checking hashes + if checksum != sha.hexdigest(): + raise RuntimeError( + "Hashes for {} do not match: {} != {}".format( + name, checksum, sha.hexdigest() + ) + ) + + gz = GzipFile(tar, mode="rb") + try: + with tarfile.TarFile(tar, fileobj=gz, format=tarfile.PAX_FORMAT) as f: + f.extractall(str(self.lib)) + finally: + gz.close() def process(self, *args): return subprocess.check_output(list(args), stderr=subprocess.STDOUT) diff --git a/pyproject.lock b/pyproject.lock new file mode 100644 index 00000000000..a9b9caee58f --- /dev/null +++ b/pyproject.lock @@ -0,0 +1,799 @@ +[[package]] +category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +platform = "*" +python-versions = "*" +version = "1.4.3" + +[package.requirements] +python = ">=3.6,<4.0" + +[[package]] +category = "dev" +description = "A few extensions to pyyaml." +name = "aspy.yaml" +optional = false +platform = "all" +python-versions = "*" +version = "1.1.1" + +[package.dependencies] +pyyaml = "*" + +[[package]] +category = "dev" +description = "Atomic file writes." +name = "atomicwrites" +optional = false +platform = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.2.1" + +[[package]] +category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" +optional = false +platform = "*" +python-versions = "*" +version = "18.2.0" + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +platform = "*" +python-versions = ">=3.6" +version = "18.6b4" + +[package.dependencies] +appdirs = "*" +attrs = ">=17.4.0" +click = ">=6.5" +toml = ">=0.9.4" + +[package.requirements] +python = ">=3.6,<4.0" + +[[package]] +category = "main" +description = "httplib2 caching for requests" +name = "cachecontrol" +optional = false +platform = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.12.5" + +[package.dependencies] +lockfile = ">=0.9" +msgpack = "*" +requests = "*" + +[[package]] +category = "dev" +description = "A decorator for caching properties in classes." +name = "cached-property" +optional = false +platform = "*" +python-versions = "*" +version = "1.4.3" + +[[package]] +category = "main" +description = "Cachy provides a simple yet effective caching library." +name = "cachy" +optional = false +platform = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.2.0" + +[[package]] +category = "main" +description = "Python package for providing Mozilla's CA Bundle." +name = "certifi" +optional = false +platform = "*" +python-versions = "*" +version = "2018.8.24" + +[[package]] +category = "dev" +description = "Validate configuration and produce human readable error messages." +name = "cfgv" +optional = false +platform = "*" +python-versions = "*" +version = "1.1.0" + +[package.dependencies] +six = "*" + +[[package]] +category = "main" +description = "Universal encoding detector for Python 2 and 3" +name = "chardet" +optional = false +platform = "*" +python-versions = "*" +version = "3.0.4" + +[[package]] +category = "main" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +name = "cleo" +optional = false +platform = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.6.8" + +[package.dependencies] +pastel = ">=0.1.0,<0.2.0" +pylev = ">=1.3,<2.0" + +[[package]] +category = "dev" +description = "A simple wrapper around optparse for powerful command line utilities." +name = "click" +optional = false +platform = "*" +python-versions = "*" +version = "6.7" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +name = "colorama" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "0.3.9" + +[package.requirements] +platform = "win32" + +[[package]] +category = "dev" +description = "Code coverage measurement for Python" +name = "coverage" +optional = false +platform = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" +version = "4.5.1" + +[[package]] +category = "main" +description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4" +name = "enum34" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "1.1.6" + +[package.requirements] +python = ">=2.7,<2.8" + +[[package]] +category = "dev" +description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+" +name = "funcsigs" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "1.0.2" + +[package.requirements] +python = "<3.0" + +[[package]] +category = "main" +description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy." +name = "functools32" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "3.2.3-2" + +[package.requirements] +python = ">=2.7,<2.8" + +[[package]] +category = "main" +description = "HTML parser based on the WHATWG HTML specification" +name = "html5lib" +optional = false +platform = "*" +python-versions = "*" +version = "1.0.1" + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[[package]] +category = "dev" +description = "File identification library for Python" +name = "identify" +optional = false +platform = "*" +python-versions = "*" +version = "1.1.4" + +[[package]] +category = "main" +description = "Internationalized Domain Names in Applications (IDNA)" +name = "idna" +optional = false +platform = "*" +python-versions = "*" +version = "2.7" + +[[package]] +category = "dev" +description = "A small but fast and easy to use stand-alone template engine written in pure python." +name = "jinja2" +optional = false +platform = "*" +python-versions = "*" +version = "2.10" + +[package.dependencies] +MarkupSafe = ">=0.23" + +[[package]] +category = "main" +description = "An implementation of JSON Schema validation for Python" +name = "jsonschema" +optional = false +platform = "*" +python-versions = "*" +version = "2.6.0" + +[package.dependencies] +[package.dependencies.functools32] +python = ">=2.7,<2.8" +version = "*" + +[[package]] +category = "dev" +description = "Python LiveReload is an awesome tool for web developers" +name = "livereload" +optional = false +platform = "*" +python-versions = "*" +version = "2.5.2" + +[package.dependencies] +six = "*" +tornado = "*" + +[[package]] +category = "main" +description = "Platform-independent file locking module" +name = "lockfile" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "0.12.2" + +[[package]] +category = "dev" +description = "Python implementation of Markdown." +name = "markdown" +optional = false +platform = "*" +python-versions = "*" +version = "2.6.11" + +[[package]] +category = "dev" +description = "Implements a XML/HTML/XHTML Markup safe string for Python" +name = "markupsafe" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "1.0" + +[[package]] +category = "dev" +description = "Project documentation with Markdown." +name = "mkdocs" +optional = false +platform = "*" +python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "1.0.3" + +[package.dependencies] +Jinja2 = ">=2.7.1" +Markdown = ">=2.3.1" +PyYAML = ">=3.10" +click = ">=3.3" +livereload = ">=2.5.1" +tornado = ">=5.0" + +[[package]] +category = "dev" +description = "Rolling backport of unittest.mock for all Pythons" +name = "mock" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "2.0.0" + +[package.dependencies] +pbr = ">=0.11" +six = ">=1.9" + +[package.dependencies.funcsigs] +python = "<3.3" +version = ">=1" + +[package.requirements] +python = "<3.0" + +[[package]] +category = "dev" +description = "More routines for operating on iterables, beyond itertools" +name = "more-itertools" +optional = false +platform = "*" +python-versions = "*" +version = "4.3.0" + +[package.dependencies] +six = ">=1.0.0,<2.0.0" + +[[package]] +category = "main" +description = "MessagePack (de)serializer." +name = "msgpack" +optional = false +platform = "*" +python-versions = "*" +version = "0.5.6" + +[[package]] +category = "dev" +description = "Node.js virtual environment builder" +name = "nodeenv" +optional = false +platform = "any" +python-versions = "*" +version = "1.3.2" + +[[package]] +category = "main" +description = "Bring colors to your terminal." +name = "pastel" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "main" +description = "Object-oriented filesystem paths" +name = "pathlib2" +optional = false +platform = "*" +python-versions = "*" +version = "2.3.2" + +[package.dependencies] +six = "*" + +[package.dependencies.scandir] +python = "<3.5" +version = "*" + +[package.requirements] +python = "<3.6" + +[[package]] +category = "dev" +description = "Python Build Reasonableness" +name = "pbr" +optional = false +platform = "*" +python-versions = "*" +version = "4.2.0" + +[package.requirements] +python = "<3.0" + +[[package]] +category = "main" +description = "Query metadatdata from sdists / bdists / installed packages." +name = "pkginfo" +optional = false +platform = "Unix" +python-versions = "*" +version = "1.4.2" + +[[package]] +category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" +optional = false +platform = "unix" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.7.1" + +[[package]] +category = "dev" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +name = "pre-commit" +optional = false +platform = "*" +python-versions = "*" +version = "1.11.0" + +[package.dependencies] +"aspy.yaml" = "*" +cached-property = "*" +cfgv = ">=1.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = "*" +six = "*" +toml = "*" +virtualenv = "*" + +[[package]] +category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" +optional = false +platform = "unix" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.6.0" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +platform = "any" +python-versions = "*" +version = "2.2.0" + +[[package]] +category = "dev" +description = "Pygments Github custom lexers." +name = "pygments-github-lexers" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "0.0.5" + +[package.dependencies] +pygments = ">=2.0.2" + +[[package]] +category = "main" +description = "A pure Python Levenshtein implementation that's not freaking GPL'd." +name = "pylev" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "1.3.0" + +[[package]] +category = "dev" +description = "Extension pack for Python Markdown." +name = "pymdown-extensions" +optional = false +platform = "*" +python-versions = "*" +version = "4.12" + +[package.dependencies] +Markdown = ">=2.6.10" + +[[package]] +category = "main" +description = "Python parsing module" +name = "pyparsing" +optional = false +platform = "UNKNOWN" +python-versions = "*" +version = "2.2.0" + +[[package]] +category = "main" +description = "Persistent/Functional/Immutable data structures" +name = "pyrsistent" +optional = false +platform = "*" +python-versions = "*" +version = "0.14.4" + +[package.dependencies] +six = "*" + +[[package]] +category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" +optional = false +platform = "unix" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.7.4" + +[package.dependencies] +atomicwrites = ">=1.0" +attrs = ">=17.4.0" +more-itertools = ">=4.0.0" +pluggy = ">=0.7" +py = ">=1.5.0" +setuptools = "*" +six = ">=1.10.0" + +[package.dependencies.colorama] +platform = "win32" +version = "*" + +[package.dependencies.funcsigs] +python = "<3.0" +version = "*" + +[package.dependencies.pathlib2] +python = "<3.6" +version = ">=2.2.0" + +[[package]] +category = "dev" +description = "Pytest plugin for measuring coverage." +name = "pytest-cov" +optional = false +platform = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" + +[package.dependencies] +coverage = ">=4.4" +pytest = ">=2.9" + +[[package]] +category = "dev" +description = "Thin-wrapper around the mock package for easier use with py.test" +name = "pytest-mock" +optional = false +platform = "any" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.10.0" + +[package.dependencies] +pytest = ">=2.7" + +[package.dependencies.mock] +python = "<3.0" +version = "*" + +[[package]] +category = "dev" +description = "YAML parser and emitter for Python" +name = "pyyaml" +optional = false +platform = "Any" +python-versions = "*" +version = "3.13" + +[[package]] +category = "main" +description = "Python HTTP for Humans." +name = "requests" +optional = false +platform = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.19.1" + +[package.dependencies] +certifi = ">=2017.4.17" +chardet = ">=3.0.2,<3.1.0" +idna = ">=2.5,<2.8" +urllib3 = ">=1.21.1,<1.24" + +[[package]] +category = "main" +description = "A utility belt for advanced users of python-requests" +name = "requests-toolbelt" +optional = false +platform = "*" +python-versions = "*" +version = "0.8.0" + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +category = "main" +description = "scandir, a better directory iterator and faster os.walk()" +name = "scandir" +optional = false +platform = "*" +python-versions = "*" +version = "1.9.0" + +[package.requirements] +python = "<3.5" + +[[package]] +category = "main" +description = "Tool to Detect Surrounding Shell" +name = "shellingham" +optional = false +platform = "*" +python-versions = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3" +version = "1.2.4" + +[[package]] +category = "main" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +platform = "*" +python-versions = "*" +version = "1.11.0" + +[[package]] +category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +platform = "*" +python-versions = "*" +version = "0.9.4" + +[[package]] +category = "main" +description = "Style preserving TOML library" +name = "tomlkit" +optional = false +platform = "*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.4.4" + +[package.dependencies] +[package.dependencies.enum34] +python = ">=2.7,<2.8" +version = ">=1.1,<2.0" + +[package.dependencies.typing] +python = ">=2.7,<2.8 || >=3.4,<3.5" +version = ">=3.6,<4.0" + +[[package]] +category = "dev" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +name = "tornado" +optional = false +platform = "*" +python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*" +version = "5.1" + +[[package]] +category = "dev" +description = "virtualenv-based automation of test activities" +name = "tox" +optional = false +platform = "unix" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "3.2.1" + +[package.dependencies] +pluggy = ">=0.3.0,<1" +py = ">=1.4.17,<2" +setuptools = ">=30.0.0" +six = ">=1.0.0,<2" +virtualenv = ">=1.11.2" + +[[package]] +category = "main" +description = "Type Hints for Python" +name = "typing" +optional = false +platform = "*" +python-versions = "*" +version = "3.6.6" + +[package.requirements] +python = ">=2.7,<2.8 || >=3.4,<3.5" + +[[package]] +category = "main" +description = "HTTP library with thread-safe connection pooling, file post, and more." +name = "urllib3" +optional = false +platform = "*" +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" +version = "1.23" + +[[package]] +category = "main" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +platform = "*" +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" +version = "16.0.0" + +[[package]] +category = "main" +description = "Character encoding aliases for legacy web content" +name = "webencodings" +optional = false +platform = "*" +python-versions = "*" +version = "0.5.1" + +[metadata] +content-hash = "5c5e11cb29f8ef5ddf0c3bf98c849597799c71d99cd78157807f439a788036e7" +platform = "*" +python-versions = "~2.7 || ^3.4" + +[metadata.hashes] +appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] +"aspy.yaml" = ["04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", "0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa"] +atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"] +attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"] +black = ["22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", "4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"] +cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"] +cached-property = ["630fdbf0f4ac7d371aa866016eba1c3ac43e9032246748d4994e67cb05f99bc4", "f1f9028757dc40b4cb0fd2234bd7b61a302d7b84c683cb8c2c529238a24b8938"] +cachy = ["b71513e5a38ce90c1280c02b7d8d6bb3fdf64666c9cc0584f2479afea097d56c", "b71e8e7ddb5b386e23e81befdfac8a93885406139b8681bedc17b3444fcb8fca"] +certifi = ["376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", "456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"] +cfgv = ["73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", "d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa"] +chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"] +cleo = ["85a63076b72ca376fb06668be1fc7758dc16740b394783d5cc65200c4b32f71b", "9b7f79f1aa470a025c0d28c76aa225ee9e65028d32f80032e871aa3500df61b8"] +click = ["29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", "f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"] +colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"] +coverage = ["03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", "0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", "104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", "10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", "15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", "198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", "23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", "28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", "2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", "2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", "337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", "3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", "3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", "3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", "3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", "4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", "56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", "5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", "69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", "6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", "701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", "7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", "76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", "7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", "7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", "7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", "8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", "9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", "9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", "ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", "b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", "be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", "c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", "de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", "e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", "e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", "f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"] +enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"] +funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"] +functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"] +html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"] +identify = ["49845e70fc6b1ec3694ab930a2c558912d7de24548eebcd448f65567dc757c43", "68daab16a3db364fa204591f97dc40bfffd1a7739f27788a4895b4d8fd3516e5"] +idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"] +jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"] +jsonschema = ["000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", "6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"] +livereload = ["583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", "dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"] +lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"] +markdown = ["9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", "a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81"] +markupsafe = ["a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"] +mkdocs = ["50bf14ba9fdef391ae3e416ce66f1c598949d6e9cf2e9d7ed69abe3cf368539a", "ce8c238231d8df02e9f66390b5ff153f52682326d83cac412d7f44957b704fbb"] +mock = ["5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", "b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"] +more-itertools = ["c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", "c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", "fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"] +msgpack = ["0b3b1773d2693c70598585a34ca2715873ba899565f0a7c9a1545baef7e7fbdc", "0bae5d1538c5c6a75642f75a1781f3ac2275d744a92af1a453c150da3446138b", "0ee8c8c85aa651be3aa0cd005b5931769eaa658c948ce79428766f1bd46ae2c3", "1369f9edba9500c7a6489b70fdfac773e925342f4531f1e3d4c20ac3173b1ae0", "22d9c929d1d539f37da3d1b0e16270fa9d46107beab8c0d4d2bddffffe895cee", "2ff43e3247a1e11d544017bb26f580a68306cec7a6257d8818893c1fda665f42", "31a98047355d34d047fcdb55b09cb19f633cf214c705a765bd745456c142130c", "8767eb0032732c3a0da92cbec5ac186ef89a3258c6edca09161472ca0206c45f", "8acc8910218555044e23826980b950e96685dc48124a290c86f6f41a296ea172", "ab189a6365be1860a5ecf8159c248f12d33f79ea799ae9695fa6a29896dcf1d4", "cfd6535feb0f1cf1c7cdb25773e965cc9f92928244a8c3ef6f8f8a8e1f7ae5c4", "e274cd4480d8c76ec467a85a9c6635bbf2258f0649040560382ab58cabb44bcf", "f86642d60dca13e93260187d56c2bef2487aa4d574a669e8ceefcf9f4c26fd00", "f8a57cbda46a94ed0db55b73e6ab0c15e78b4ede8690fa491a0e55128d552bb0", "fcea97a352416afcbccd7af9625159d80704a25c519c251c734527329bb20d0e"] +nodeenv = ["aa040ab5189bae17d272175609010be6c5b589ec4b8dbd832cc50c9e9cb7496f"] +pastel = ["3108af417ec0fa6d0a620e676ec4f02c839ca13e10611586e5d2174b46aa0bc3", "d1fee8079534f99f1805a044fef946d23eee6d6a7cd34292c30e6c16be9a80b9"] +pathlib2 = ["8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", "d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"] +pbr = ["1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", "b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"] +pkginfo = ["5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", "a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"] +pluggy = ["6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"] +pre-commit = ["18ef2892ff6b6518945bc7dcf423e3c969033a4ca132b6a1ae0d52eb2e51ea27", "88d59872610a7069d937b6868632ba534187bda58c4665de12b25c8c549ddd0e"] +py = ["06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"] +pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"] +pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"] +pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"] +pymdown-extensions = ["20f2ae1067ab850cab92fcf57487267a7fd1365a7b1e7c5394e1e0778455eec1", "7d3fcbb4c5d70a78d1f4c2c7eef02dbe7e1ba08b06cb72e08b3d1027eb77458b"] +pyparsing = ["0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", "281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", "8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", "9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", "b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", "e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", "fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"] +pyrsistent = ["4024f838472cba9ea1ccbc638e0bcafec2efda28594a9905177ec365f1a95fea"] +pytest = ["2d7c49e931316cc7d1638a3e5f54f5d7b4e5225972b3c9838f3584788d27f349", "ad0c7db7b5d4081631e0155f5c61b80ad76ce148551aaafe3a718d65a7508b18"] +pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"] +pytest-mock = ["53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928", "d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"] +pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"] +requests = ["63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", "ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"] +requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", "f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"] +scandir = ["04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", "1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", "1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", "346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", "44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", "61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", "a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", "c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", "c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", "c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", "f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"] +shellingham = ["869c760320b6d4cd88ac5bdc2a6a40c042fb388c5fcd056f8a07a82d9859cc40", "c09c563a2e185ec3d64e43c286dbba3150fc182d96cd29ff5b002f3d3c3f5076"] +six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"] +toml = ["8e86bd6ce8cc11b9620cb637466453d94f5d57ad86f17e98a98d1f73e3baab2d"] +tomlkit = ["8ab16e93162fc44d3ad83d2aa29a7140b8f7d996ae1790a73b9a7aed6fb504ac", "ca181cee7aee805d455628f7c94eb8ae814763769a93e69157f250fe4ebe1926"] +tornado = ["1c0816fc32b7d31b98781bd8ebc7a9726d7dce67407dc353a2e66e697e138448", "4f66a2172cb947387193ca4c2c3e19131f1c70fa8be470ddbbd9317fd0801582", "5327ba1a6c694e0149e7d9126426b3704b1d9d520852a3e4aa9fc8fe989e4046", "6a7e8657618268bb007646b9eae7661d0b57f13efc94faa33cd2588eae5912c9", "a9b14804783a1d77c0bd6c66f7a9b1196cbddfbdf8bceb64683c5ae60bd1ec6f", "c58757e37c4a3172949c99099d4d5106e4d7b63aa0617f9bb24bfbff712c7866", "d8984742ce86c0855cccecd5c6f54a9f7532c983947cff06f3a0e2115b47f85c"] +tox = ["37cf240781b662fb790710c6998527e65ca6851eace84d1595ee71f7af4e85f7", "eb61aa5bcce65325538686f09848f04ef679b5cd9b83cc491272099b28739600"] +typing = ["4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d", "57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", "a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a"] +urllib3 = ["a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"] +virtualenv = ["2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", "ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"] +webencodings = ["a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", "b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"] diff --git a/sonnet b/sonnet new file mode 100755 index 00000000000..a550bde6c1c --- /dev/null +++ b/sonnet @@ -0,0 +1,190 @@ +#!/usr/bin/env python +import hashlib +import os +import shutil +import subprocess +import sys +import tarfile + +from gzip import GzipFile + +from cleo import Application +from cleo import Command + +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") + + +class MakeReleaseCommand(Command): + """ + Makes a self-contained package of Poetry. + + make:release + {--P|python=?* : Python version to use} + """ + + PYTHON = { + "2.7": "python2.7", + "3.4": "python3.4", + "3.5": "python3.5", + "3.6": "python3.6", + "3.7": "python3.7", + } + + def handle(self): + pythons = self.PYTHON + if self.option("python"): + pythons = {} + for python in self.option("python"): + parts = python.split(":", 1) + if len(parts) == 1: + pythons[parts[0]] = self.PYTHON[parts[0]] + + version, python = parts + pythons[version] = python + + self.check_system(pythons) + + from poetry import __version__ + from poetry.poetry import Poetry + from poetry.puzzle import Solver + from poetry.repositories.pool import Pool + from poetry.repositories.repository import Repository + from poetry.utils._compat import Path + from poetry.utils.helpers import temporary_directory + + project = Poetry.create(Path.cwd()) + package = project.package + del package.dev_requires[:] + + # We only use the lock file to resolve the dependencies + pool = Pool() + pool.add_repository(project.locker.locked_repository(with_dev_reqs=True)) + + with temporary_directory() as tmp_dir: + # Copy poetry to tmp dir + poetry_dir = os.path.join(tmp_dir, "poetry") + shutil.copytree( + os.path.join(os.path.dirname(__file__), "poetry"), poetry_dir + ) + for version, python in sorted(pythons.items()): + self.line( + "Preparing files for Python {}".format( + version + ) + ) + with package.with_python_versions("^{}".format(version)): + solver = Solver( + package, pool, Repository(), Repository(), self.output + ) + ops = solver.solve() + + self.vendorize_for_python( + python, [op.package for op in ops], poetry_dir, version + ) + + self.line("") + + self.line("Packaging files") + with temporary_directory() as tmp_dir2: + base_name = "poetry-{}-{}".format(__version__, sys.platform) + name = "{}.tar.gz".format(base_name) + gz = GzipFile(os.path.join(tmp_dir2, name), mode="wb") + try: + with tarfile.TarFile( + os.path.join(tmp_dir2, name), + mode="w", + fileobj=gz, + format=tarfile.PAX_FORMAT, + ) as tar: + for root, dirs, files in os.walk(tmp_dir): + for f in files: + if f.endswith(".pyc"): + continue + + path = os.path.join(os.path.realpath(root), f) + relpath = os.path.relpath( + path, os.path.realpath(tmp_dir) + ) + tar_info = tar.gettarinfo(str(path), arcname=relpath) + + if tar_info.isreg(): + with open(path, "rb") as f: + tar.addfile(tar_info, f) + else: + tar.addfile(tar_info) # Symlinks & ? + finally: + gz.close() + + releases_dir = os.path.join(os.path.dirname(__file__), "releases") + if not os.path.exists(releases_dir): + os.mkdir(releases_dir) + + shutil.copyfile( + os.path.join(tmp_dir2, name), os.path.join(releases_dir, name) + ) + + # Compute hash + sha = hashlib.sha256() + with open(os.path.join(releases_dir, name), "rb") as f: + while True: + buffer = f.read(8192) + if not buffer: + break + + sha.update(buffer) + + with open( + os.path.join(releases_dir, "{}.sha256sum".format(base_name)), "w" + ) as f: + f.write(sha.hexdigest()) + + self.line("Built {}".format(name)) + + def check_system(self, pythons): + for version, python in sorted(pythons.items()): + try: + subprocess.check_output( + [python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS + ) + except subprocess.CalledProcessError: + raise RuntimeError("Python {} is not available".format(version)) + + def vendorize_for_python(self, python, packages, dest, python_version): + vendor_dir = os.path.join(dest, "_vendor", "py{}".format(python_version)) + + bar = self.progress_bar(max=len(packages)) + bar.set_format("%message% %current%/%max%") + bar.set_message( + "Vendorizing dependencies for Python {}".format( + python_version + ) + ) + bar.start() + for package in packages: + subprocess.check_output( + [ + python, + "-m", + "pip", + "install", + package.name, + "--no-deps", + "--target", + vendor_dir, + ], + stderr=subprocess.STDOUT, + shell=WINDOWS, + ) + bar.advance() + + bar.finish() + + self.line("") + + +app = Application("Sonnet") + +app.add(MakeReleaseCommand()) + +if __name__ == "__main__": + app.run()