From 9c4533b829fc0a4256a58105d4cb5d6734a6997f Mon Sep 17 00:00:00 2001 From: d3an Date: Thu, 14 Jan 2021 01:05:56 -0500 Subject: [PATCH] Added poetry dependency management, updated dockerfile --- .gitignore | 3 +- Dockerfile | 27 +- README.md | 16 +- deploy/get-poetry.py | 1086 ++++++++++++++++++++++++++++++++++++++++++ poetry.lock | 457 ++++++++++++++++++ pyproject.toml | 21 + requirements.txt | 7 - 7 files changed, 1596 insertions(+), 21 deletions(-) create mode 100644 deploy/get-poetry.py create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index e02accc..cf73e08 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__/ *.py[cod] -.idea +.idea/ +.env diff --git a/Dockerfile b/Dockerfile index 4c66dfc..b22597b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,19 @@ -FROM python:3 -ENV PYTHONUNBUFFERED 1 -ENV DJANGO_SETTINGS_MODULE uwpath_backend.settings -RUN mkdir /code -WORKDIR /code -COPY requirements.txt /code/ -RUN pip install -r requirements.txt -COPY . /code/ -ENV PATH /code/:$PATH +FROM ubuntu:18.04 as base + +COPY . /opt/uwpath.backend +WORKDIR /opt/uwpath.backend + +RUN apt update +RUN apt install git ssh -y +RUN apt install wget curl vim libssl-dev ipython -y + +FROM base as python38 +RUN apt update && apt install python3.8 python3.8-dev python3-pip -y + +FROM python38 as uwpath + +RUN pip3 install requests && python3.8 deploy/get-poetry.py -y && . ~/.poetry/env && poetry export -f requirements.txt -o requirements.txt +RUN pip3 install -r requirements.txt + +ENV PATH /opt/uwpath.backend/:$PATH EXPOSE 8000 diff --git a/README.md b/README.md index c12bf36..b6e7911 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,16 @@ # Backend -Acts at the backend for UWPath. +Acts as the backend for UWPath. -## Set up -Please refer to [Wiki](https://github.com/UW-Path/Backend/wiki/Developer:-Set-Up) for instructions to set up restful API +## Developer Setup -## Accesing prototype +Please refer to [Wiki](https://github.com/UW-Path/Backend/wiki/Developer:-Set-Up) for instructions to set up the API. + +This project currently uses [Poetry](https://python-poetry.org/) for dependency management. +Please install it using the following command: + +```commandline +curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 - +``` + +## Accessing prototype A prototype is built with Django. To access the prototype, please pull the [website](https://github.com/UW-Path/Backend/tree/django_website) branch. diff --git a/deploy/get-poetry.py b/deploy/get-poetry.py new file mode 100644 index 0000000..73b818c --- /dev/null +++ b/deploy/get-poetry.py @@ -0,0 +1,1086 @@ +""" +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 "ON" == os.getenv("ConEmuANSI") + or "xterm" == os.getenv("Term") + ) + + 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 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): + 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 + + 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, 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, 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 = ["python", "python3"] + 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\d+)\.(?P\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, 0o755) + + 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", + "\nUnable 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", + "\nPATH 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, + u"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()) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..99c232a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,457 @@ +[[package]] +category = "dev" +description = "Disable App Nap on macOS >= 10.9" +marker = "sys_platform == \"darwin\"" +name = "appnope" +optional = false +python-versions = "*" +version = "0.1.2" + +[[package]] +category = "main" +description = "ASGI specs, helper code, and adapters" +name = "asgiref" +optional = false +python-versions = ">=3.5" +version = "3.3.1" + +[package.extras] +tests = ["pytest", "pytest-asyncio"] + +[[package]] +category = "dev" +description = "Specifications for callback functions passed in to an API" +name = "backcall" +optional = false +python-versions = "*" +version = "0.2.0" + +[[package]] +category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.4" + +[[package]] +category = "dev" +description = "Decorators for Humans" +name = "decorator" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.2" + +[[package]] +category = "main" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +name = "django" +optional = false +python-versions = ">=3.6" +version = "3.0.7" + +[package.dependencies] +asgiref = ">=3.2,<4.0" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=16.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +category = "main" +description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +name = "django-cors-headers" +optional = false +python-versions = ">=3.5" +version = "3.3.0" + +[package.dependencies] +Django = ">=2.0" + +[[package]] +category = "main" +description = "Web APIs for Django, made easy." +name = "djangorestframework" +optional = false +python-versions = ">=3.5" +version = "3.11.0" + +[package.dependencies] +django = ">=1.11" + +[[package]] +category = "main" +description = "A Django REST framework API adapter for the JSON API spec." +name = "djangorestframework-jsonapi" +optional = false +python-versions = ">=3.5" +version = "3.0.0" + +[package.dependencies] +django = ">=1.11" +djangorestframework = ">=3.10" +inflection = ">=0.3.0" + +[package.extras] +django-filter = ["django-filter (>=2.0)"] +django-polymorphic = ["django-polymorphic (>=2.0)"] + +[[package]] +category = "main" +description = "WSGI HTTP Server for UNIX" +name = "gunicorn" +optional = false +python-versions = ">=3.4" +version = "20.0.4" + +[package.dependencies] +setuptools = ">=3.0" + +[package.extras] +eventlet = ["eventlet (>=0.9.7)"] +gevent = ["gevent (>=0.13)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +category = "main" +description = "A port of Ruby on Rails inflector to Python" +name = "inflection" +optional = false +python-versions = ">=3.5" +version = "0.5.1" + +[[package]] +category = "dev" +description = "IPython: Productive Interactive Computing" +name = "ipython" +optional = false +python-versions = ">=3.7" +version = "7.19.0" + +[package.dependencies] +appnope = "*" +backcall = "*" +colorama = "*" +decorator = "*" +jedi = ">=0.10" +pexpect = ">4.3" +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] + +[[package]] +category = "dev" +description = "Vestigial utilities from IPython" +name = "ipython-genutils" +optional = false +python-versions = "*" +version = "0.2.0" + +[[package]] +category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +name = "jedi" +optional = false +python-versions = ">=3.6" +version = "0.18.0" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (3.8.3)", "mypy (0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] + +[[package]] +category = "dev" +description = "A Python Parser" +name = "parso" +optional = false +python-versions = ">=3.6" +version = "0.8.1" + +[package.extras] +qa = ["flake8 (3.8.3)", "mypy (0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "sys_platform != \"win32\"" +name = "pexpect" +optional = false +python-versions = "*" +version = "4.8.0" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" +name = "pickleshare" +optional = false +python-versions = "*" +version = "0.7.5" + +[[package]] +category = "main" +description = "postgres is a high-value abstraction over psycopg2." +name = "postgres" +optional = false +python-versions = "*" +version = "3.0.0" + +[package.dependencies] +psycopg2-binary = ">=2.8" +psycopg2-pool = "*" + +[[package]] +category = "dev" +description = "Library for building powerful interactive command lines in Python" +name = "prompt-toolkit" +optional = false +python-versions = ">=3.6.1" +version = "3.0.10" + +[package.dependencies] +wcwidth = "*" + +[[package]] +category = "main" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +name = "psycopg2-binary" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +version = "2.8.6" + +[[package]] +category = "main" +description = "Proper pooling of psycopg2 connections" +name = "psycopg2-pool" +optional = false +python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*, != 3.4.*" +version = "1.1" + +[package.dependencies] +psycopg2-binary = "*" + +[[package]] +category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "sys_platform != \"win32\"" +name = "ptyprocess" +optional = false +python-versions = "*" +version = "0.7.0" + +[[package]] +category = "dev" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=3.5" +version = "2.7.4" + +[[package]] +category = "main" +description = "World timezone definitions, modern and historical" +name = "pytz" +optional = false +python-versions = "*" +version = "2020.5" + +[[package]] +category = "main" +description = "A non-validating SQL parser." +name = "sqlparse" +optional = false +python-versions = ">=3.5" +version = "0.4.1" + +[[package]] +category = "dev" +description = "Traitlets Python configuration system" +name = "traitlets" +optional = false +python-versions = ">=3.7" +version = "5.0.5" + +[package.dependencies] +ipython-genutils = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +category = "dev" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" +optional = false +python-versions = "*" +version = "0.2.5" + +[metadata] +content-hash = "0f60a33155249513e2a11a79eaa30afa605d39902cf6f0a145a1920de1e5599b" +lock-version = "1.0" +python-versions = "^3.8" + +[metadata.files] +appnope = [ + {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, + {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, +] +asgiref = [ + {file = "asgiref-3.3.1-py3-none-any.whl", hash = "sha256:5ee950735509d04eb673bd7f7120f8fa1c9e2df495394992c73234d526907e17"}, + {file = "asgiref-3.3.1.tar.gz", hash = "sha256:7162a3cb30ab0609f1a4c95938fd73e8604f63bdba516a7f7d64b83ff09478f0"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, +] +django = [ + {file = "Django-3.0.7-py3-none-any.whl", hash = "sha256:e1630333248c9b3d4e38f02093a26f1e07b271ca896d73097457996e0fae12e8"}, + {file = "Django-3.0.7.tar.gz", hash = "sha256:5052b34b34b3425233c682e0e11d658fd6efd587d11335a0203d827224ada8f2"}, +] +django-cors-headers = [ + {file = "django-cors-headers-3.3.0.tar.gz", hash = "sha256:73d654950b5f5e7e4f67c05183d2169d4f7518ceb87734eb0d68f9e43be59f1c"}, + {file = "django_cors_headers-3.3.0-py3-none-any.whl", hash = "sha256:48d267c10d11d8e17805bf896071c0a3e8efb6f79f6634a90e6faac4c2f8a1a0"}, +] +djangorestframework = [ + {file = "djangorestframework-3.11.0-py3-none-any.whl", hash = "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4"}, + {file = "djangorestframework-3.11.0.tar.gz", hash = "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"}, +] +djangorestframework-jsonapi = [ + {file = "djangorestframework-jsonapi-3.0.0.tar.gz", hash = "sha256:61d28ac49274815b1735d87b7ab93f38b481916ddfa7231509de297fd27e5b3b"}, + {file = "djangorestframework_jsonapi-3.0.0-py2.py3-none-any.whl", hash = "sha256:1610aef92e211f3904fb8d51ad1f3cd6f1256f617169eb63524bb053430d98b2"}, +] +gunicorn = [ + {file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"}, + {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"}, +] +inflection = [ + {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, + {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, +] +ipython = [ + {file = "ipython-7.19.0-py3-none-any.whl", hash = "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f"}, + {file = "ipython-7.19.0.tar.gz", hash = "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +jedi = [ + {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, + {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, +] +parso = [ + {file = "parso-0.8.1-py2.py3-none-any.whl", hash = "sha256:15b00182f472319383252c18d5913b69269590616c947747bc50bf4ac768f410"}, + {file = "parso-0.8.1.tar.gz", hash = "sha256:8519430ad07087d4c997fda3a7918f7cfa27cb58972a8c89c2a0295a1c940e9e"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +postgres = [ + {file = "postgres-3.0.0-py2.py3-none-any.whl", hash = "sha256:85649aed35fc109c8413ffa5d167ff003f76e978e726f65340bf949ab7cd22ab"}, + {file = "postgres-3.0.0.tar.gz", hash = "sha256:ada2608527d56058ac2f72b5b0671a4893e04207d63059279ce9196436b98637"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.10-py3-none-any.whl", hash = "sha256:ac329c69bd8564cb491940511957312c7b8959bb5b3cf3582b406068a51d5bb7"}, + {file = "prompt_toolkit-3.0.10.tar.gz", hash = "sha256:b8b3d0bde65da350290c46a8f54f336b3cbf5464a4ac11239668d986852e79d5"}, +] +psycopg2-binary = [ + {file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"}, + {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"}, + {file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"}, + {file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"}, + {file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"}, + {file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"}, + {file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, + {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win32.whl", hash = "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056"}, + {file = "psycopg2_binary-2.8.6-cp39-cp39-win_amd64.whl", hash = "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6"}, +] +psycopg2-pool = [ + {file = "psycopg2-pool-1.1.tar.gz", hash = "sha256:de88615c24ddfc7ee314c566976cfed4bbb009211255f2773ea09e5f938ff400"}, + {file = "psycopg2_pool-1.1-py2.py3-none-any.whl", hash = "sha256:529c4100db56a3522cf680ce8d89e71511e37c7e0b1f120ce0dde0f13c6e5844"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +pygments = [ + {file = "Pygments-2.7.4-py3-none-any.whl", hash = "sha256:bc9591213a8f0e0ca1a5e68a479b4887fdc3e75d0774e5c71c31920c427de435"}, + {file = "Pygments-2.7.4.tar.gz", hash = "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"}, +] +pytz = [ + {file = "pytz-2020.5-py2.py3-none-any.whl", hash = "sha256:16962c5fb8db4a8f63a26646d8886e9d769b6c511543557bc84e9569fb9a9cb4"}, + {file = "pytz-2020.5.tar.gz", hash = "sha256:180befebb1927b16f6b57101720075a984c019ac16b1b7575673bea42c6c3da5"}, +] +sqlparse = [ + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, +] +traitlets = [ + {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, + {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..956ff56 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "uwpath.backend" +version = "0.0.1" +description = "" +authors = ["Your Name "] + +[tool.poetry.dependencies] +python = "^3.8" +Django = "3.0.7" +djangorestframework = "3.11.0" +djangorestframework-jsonapi = "3.0.0" +django-cors-headers = "3.3.0" +postgres = "^3.0.0" +gunicorn = "^20.0.4" + +[tool.poetry.dev-dependencies] +ipython = "^7.19.0" + +[build-system] +requires = ["poetry>=1.0"] +build-backend = "poetry.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8f748bc..0000000 --- a/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -Django==3.0.7 -django-rest-framework==0.1.0 -djangorestframework==3.11.0 -djangorestframework-jsonapi==3.0.0 -django-cors-headers==3.3.0 -postgres==3.0.0 -gunicorn==20.0.4