From c75e53bdf3df2108d545f03f0cfa7f899450aac4 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 7 Mar 2022 14:55:14 +0000 Subject: [PATCH 1/7] Blank flask-restful project with sensible defaults --- py-prototype/.python-version | 1 + py-prototype/Makefile | 24 + py-prototype/app.py | 20 + py-prototype/get-poetry.py | 1118 +++++++++++++++++ py-prototype/poetry.lock | 554 ++++++++ py-prototype/py_prototype.egg-info/PKG-INFO | 11 + .../py_prototype.egg-info/SOURCES.txt | 7 + .../dependency_links.txt | 1 + .../py_prototype.egg-info/top_level.txt | 1 + py-prototype/py_prototype/__init__.py | 1 + py-prototype/pyproject.toml | 20 + py-prototype/readme.md | 6 + py-prototype/tests/__init__.py | 0 py-prototype/tests/test_py_prototype.py | 5 + 14 files changed, 1769 insertions(+) create mode 100644 py-prototype/.python-version create mode 100644 py-prototype/Makefile create mode 100644 py-prototype/app.py create mode 100644 py-prototype/get-poetry.py create mode 100644 py-prototype/poetry.lock create mode 100644 py-prototype/py_prototype.egg-info/PKG-INFO create mode 100644 py-prototype/py_prototype.egg-info/SOURCES.txt create mode 100644 py-prototype/py_prototype.egg-info/dependency_links.txt create mode 100644 py-prototype/py_prototype.egg-info/top_level.txt create mode 100644 py-prototype/py_prototype/__init__.py create mode 100644 py-prototype/pyproject.toml create mode 100644 py-prototype/readme.md create mode 100644 py-prototype/tests/__init__.py create mode 100644 py-prototype/tests/test_py_prototype.py diff --git a/py-prototype/.python-version b/py-prototype/.python-version new file mode 100644 index 0000000..7b59a5c --- /dev/null +++ b/py-prototype/.python-version @@ -0,0 +1 @@ +3.10.2 diff --git a/py-prototype/Makefile b/py-prototype/Makefile new file mode 100644 index 0000000..4827d54 --- /dev/null +++ b/py-prototype/Makefile @@ -0,0 +1,24 @@ +## +# Python prototype +# + +default: lints run + +run: + @poetry run python app.py + +watch: + @poetry run watchmedo auto-restart --ignore-directories --patterns="*.py" --ignore-patterns="*#*" --recursive make + +lints: + clear + @poetry run black app.py + @poetry run flake8 app.py + @poetry run mypy app.py + +setup: + @which poetry || curl -sSL https://install.python-poetry.org | python3 - + @poetry self update --preview + @poetry update + +# end diff --git a/py-prototype/app.py b/py-prototype/app.py new file mode 100644 index 0000000..b8c3d5a --- /dev/null +++ b/py-prototype/app.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +from typing import Dict + +from flask import Flask +from flask_restful import Resource, Api # type: ignore + +app = Flask(__name__) +api = Api(app) + + +class HelloWorld(Resource): + def get(self) -> Dict[str, str]: + return {"hello": "forms"} + + +api.add_resource(HelloWorld, "/") + +if __name__ == "__main__": + app.run(debug=True) diff --git a/py-prototype/get-poetry.py b/py-prototype/get-poetry.py new file mode 100644 index 0000000..f7450f7 --- /dev/null +++ b/py-prototype/get-poetry.py @@ -0,0 +1,1118 @@ +""" +This script will install Poetry and its dependencies +in isolation from the rest of the system. + +It does, in order: + + - Downloads the latest stable (or pre-release) version of poetry. + - Downloads all its dependencies in the poetry/_vendor directory. + - Copies it and all extra files in $POETRY_HOME. + - Updates the PATH in a system-specific way. + +There will be a `poetry` script that will be installed in $POETRY_HOME/bin +which will act as the poetry command but is slightly different in the sense +that it will use the current Python installation. + +What this means is that one Poetry installation can serve for multiple +Python versions. +""" +import argparse +import hashlib +import json +import os +import platform +import re +import shutil +import stat +import subprocess +import sys +import tarfile +import tempfile + +from contextlib import closing +from contextlib import contextmanager +from functools import cmp_to_key +from gzip import GzipFile +from io import UnsupportedOperation +from io import open + + +try: + from urllib.error import HTTPError + from urllib.request import Request + from urllib.request import urlopen +except ImportError: + from urllib2 import HTTPError + from urllib2 import Request + from urllib2 import urlopen + +try: + input = raw_input +except NameError: + pass + + +try: + try: + import winreg + except ImportError: + import _winreg as winreg +except ImportError: + winreg = None + +try: + u = unicode +except NameError: + u = str + +SHELL = os.getenv("SHELL", "") +WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") + + +FOREGROUND_COLORS = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, +} + +BACKGROUND_COLORS = { + "black": 40, + "red": 41, + "green": 42, + "yellow": 43, + "blue": 44, + "magenta": 45, + "cyan": 46, + "white": 47, +} + +OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8} + + +def style(fg, bg, options): + codes = [] + + if fg: + codes.append(FOREGROUND_COLORS[fg]) + + if bg: + codes.append(BACKGROUND_COLORS[bg]) + + if options: + if not isinstance(options, (list, tuple)): + options = [options] + + for option in options: + codes.append(OPTIONS[option]) + + return "\033[{}m".format(";".join(map(str, codes))) + + +STYLES = { + "info": style("green", None, None), + "comment": style("yellow", None, None), + "error": style("red", None, None), + "warning": style("yellow", None, None), +} + + +def is_decorated(): + if platform.system().lower() == "windows": + return ( + os.getenv("ANSICON") is not None + or os.getenv("ConEmuANSI") == "ON" + or os.getenv("Term") == "xterm" + ) + + if not hasattr(sys.stdout, "fileno"): + return False + + try: + return os.isatty(sys.stdout.fileno()) + except UnsupportedOperation: + return False + + +def is_interactive(): + if not hasattr(sys.stdin, "fileno"): + return False + + try: + return os.isatty(sys.stdin.fileno()) + except UnsupportedOperation: + return False + + +def colorize(style, text): + if not is_decorated(): + return text + + return "{}{}\033[0m".format(STYLES[style], text) + + +@contextmanager +def temporary_directory(*args, **kwargs): + try: + from tempfile import TemporaryDirectory + except ImportError: + name = tempfile.mkdtemp(*args, **kwargs) + + yield name + + shutil.rmtree(name) + else: + with TemporaryDirectory(*args, **kwargs) as name: + yield name + + +def string_to_bool(value): + value = value.lower() + + return value in {"true", "1", "y", "yes"} + + +def expanduser(path): + """ + Expand ~ and ~user constructions. + + Includes a workaround for http://bugs.python.org/issue14768 + """ + expanded = os.path.expanduser(path) + if path.startswith("~/") and expanded.startswith("//"): + expanded = expanded[1:] + + return expanded + + +HOME = expanduser("~") +POETRY_HOME = os.environ.get("POETRY_HOME") or os.path.join(HOME, ".poetry") +POETRY_BIN = os.path.join(POETRY_HOME, "bin") +POETRY_ENV = os.path.join(POETRY_HOME, "env") +POETRY_LIB = os.path.join(POETRY_HOME, "lib") +POETRY_LIB_BACKUP = os.path.join(POETRY_HOME, "lib-backup") + + +BIN = """# -*- coding: utf-8 -*- +import glob +import sys +import os + +lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib")) +vendors = os.path.join(lib, "poetry", "_vendor") +current_vendors = os.path.join( + vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2])) +) + +sys.path.insert(0, lib) +sys.path.insert(0, current_vendors) + +if __name__ == "__main__": + from poetry.console import main + + main() +""" + +BAT = u('@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n') + + +PRE_MESSAGE = """# Welcome to {poetry}! + +This will download and install the latest version of {poetry}, +a dependency and package manager for Python. + +It will add the `poetry` command to {poetry}'s bin directory, located at: + +{poetry_home_bin} + +{platform_msg} + +You can uninstall at any time by executing this script with the --uninstall option, +and these changes will be reverted. +""" + +PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go! + +This will uninstall {poetry}. + +It will remove the `poetry` command from {poetry}'s bin directory, located at: + +{poetry_home_bin} + +This will also remove {poetry} from your system's PATH. +""" + + +PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by +modifying the profile file{plural} located at: + +{rcfiles}""" + + +PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by +modifying the `fish_user_paths` universal variable.""" + +PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by +modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" + +PRE_MESSAGE_NO_MODIFY_PATH = """This path needs to be in your `PATH` environment variable, +but will not be added automatically.""" + +POST_MESSAGE_UNIX = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` +environment variable. Next time you log in this will be done +automatically. + +To configure your current shell run `source {poetry_home_env}` +""" + +POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great! + +{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH` +environment variable by modifying the `fish_user_paths` universal variable. +""" + +POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! + +To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` +environment variable. Future applications will automatically have the +correct environment, but you may need to restart your current shell. +""" + +POST_MESSAGE_UNIX_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` +environment variable. + +To configure your current shell run `source {poetry_home_env}` +""" + +POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need {poetry}'s bin directory ({poetry_home_bin}) +in your `PATH` environment variable, which you can add by running +the following command: + + set -U fish_user_paths {poetry_home_bin} $fish_user_paths +""" + +POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! + +To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` +environment variable. This has not been done automatically. +""" + + +class Installer: + + CURRENT_PYTHON = sys.executable + CURRENT_PYTHON_VERSION = sys.version_info[:2] + METADATA_URL = "https://pypi.org/pypi/poetry/json" + VERSION_REGEX = re.compile( + r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?" + "(" + "[._-]?" + r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?" + "([.-]?dev)?" + ")?" + r"(?:\+[^\s]+)?" + ) + + REPOSITORY_URL = "https://github.com/python-poetry/poetry" + BASE_URL = REPOSITORY_URL + "/releases/download/" + FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download/" + + def __init__( + self, + version=None, + preview=False, + force=False, + modify_path=True, + accept_all=False, + file=None, + base_url=BASE_URL, + ): + self._version = version + self._preview = preview + self._force = force + self._modify_path = modify_path + self._accept_all = accept_all + self._offline_file = file + self._base_url = base_url + + def allows_prereleases(self): + return self._preview + + def run(self): + version, current_version = self.get_version() + + if version is None: + return 0 + + self.customize_install() + self.display_pre_message() + self.ensure_home() + + try: + self.install( + version, upgrade=current_version is not None, file=self._offline_file + ) + except subprocess.CalledProcessError as e: + print(colorize("error", "An error has 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 + + def _is_supported(x): + mx = self.VERSION_REGEX.match(x) + vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) + return vx < (1, 2, 0) + + if not _is_supported(version): + print( + colorize( + "error", + "Version {version} does not support this installation method." + " Please specify a version prior to 1.2.0a1 explicitly using the" + " '--version' option.\nPlease see" + " https://python-poetry.org/blog/announcing-poetry-1-2-0a1.html#deprecation-of-the-get-poetry-py-script" + " for more information.".format(version=version), + ) + ) + return None, None + + print( + colorize( + "warning", + "This installer is deprecated. Poetry versions installed using this" + " script will not be able to use 'self update' command to upgrade to" + " 1.2.0a1 or later.", + ) + ) + + current_version = None + if os.path.exists(POETRY_LIB): + with open( + os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8" + ) as f: + version_content = f.read() + + current_version_re = re.match( + '(?ms).*__version__ = "(.+)".*', version_content + ) + if not current_version_re: + print( + colorize( + "warning", + "Unable to get the current Poetry version. Assuming None", + ) + ) + else: + current_version = current_version_re.group(1) + + if current_version == version and not self._force: + print("Latest version already installed.") + return None, current_version + + return version, current_version + + def customize_install(self): + if not self._accept_all: + print("Before we start, please answer the following questions.") + print("You may simply press the Enter key to leave unchanged.") + + modify_path = input("Modify PATH variable? ([y]/n) ") or "y" + if modify_path.lower() in {"n", "no"}: + self._modify_path = False + + print("") + + def customize_uninstall(self): + if not self._accept_all: + print() + + uninstall = ( + input("Are you sure you want to uninstall Poetry? (y/[n]) ") or "n" + ) + if uninstall.lower() not in {"y", "yes"}: + return False + + print("") + + return True + + def ensure_home(self): + """ + Ensures that $POETRY_HOME exists or create it. + """ + if not os.path.exists(POETRY_HOME): + os.mkdir(POETRY_HOME, 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 = ["python3", "python"] + if WINDOWS: + allowed_executables += ["py.exe -3", "py.exe -2"] + + # \d in regex ensures we can convert to int later + version_matcher = re.compile(r"^Python (?P\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, + "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/py-prototype/poetry.lock b/py-prototype/poetry.lock new file mode 100644 index 0000000..14683fe --- /dev/null +++ b/py-prototype/poetry.lock @@ -0,0 +1,554 @@ +[[package]] +name = "aniso8601" +version = "9.0.1" +description = "A library for parsing ISO 8601 strings." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "flask" +version = "2.0.3" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +click = ">=7.1.2" +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-restful" +version = "0.3.9" +description = "Simple framework for creating REST APIs" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +aniso8601 = ">=0.82" +Flask = ">=0.8" +pytz = "*" +six = ">=1.3.0" + +[package.extras] +docs = ["sphinx"] + +[[package]] +name = "itsdangerous" +version = "2.1.0" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jinja2" +version = "3.0.3" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.0" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "more-itertools" +version = "8.12.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "mypy" +version = "0.931" +description = "Optional static typing for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytz" +version = "2021.3" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "watchdog" +version = "2.1.6" +description = "Filesystem events monitoring" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "werkzeug" +version = "2.0.3" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +watchdog = ["watchdog"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.10.2" +content-hash = "604334b44a78a7ea234239ee91b15608ad6cd2f10c2f893606a470e9032a8ca1" + +[metadata.files] +aniso8601 = [ + {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, + {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +click = [ + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +flask = [ + {file = "Flask-2.0.3-py3-none-any.whl", hash = "sha256:59da8a3170004800a2837844bfa84d49b022550616070f7cb1a659682b2e7c9f"}, + {file = "Flask-2.0.3.tar.gz", hash = "sha256:e1120c228ca2f553b470df4a5fa927ab66258467526069981b3eb0a91902687d"}, +] +flask-restful = [ + {file = "Flask-RESTful-0.3.9.tar.gz", hash = "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e"}, + {file = "Flask_RESTful-0.3.9-py2.py3-none-any.whl", hash = "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2"}, +] +itsdangerous = [ + {file = "itsdangerous-2.1.0-py3-none-any.whl", hash = "sha256:29285842166554469a56d427addc0843914172343784cb909695fdbe90a3e129"}, + {file = "itsdangerous-2.1.0.tar.gz", hash = "sha256:d848fcb8bc7d507c4546b448574e8a44fc4ea2ba84ebf8d783290d53e81992f5"}, +] +jinja2 = [ + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3028252424c72b2602a323f70fbf50aa80a5d3aa616ea6add4ba21ae9cc9da4c"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:290b02bab3c9e216da57c1d11d2ba73a9f73a614bbdcc027d299a60cdfabb11a"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e104c0c2b4cd765b4e83909cde7ec61a1e313f8a75775897db321450e928cce"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c3be29abb6b34052fd26fc7a8e0a49b1ee9d282e3665e8ad09a0a68faee5b3"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204730fd5fe2fe3b1e9ccadb2bd18ba8712b111dcabce185af0b3b5285a7c989"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3b64c65328cb4cd252c94f83e66e3d7acf8891e60ebf588d7b493a55a1dbf26"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96de1932237abe0a13ba68b63e94113678c379dca45afa040a17b6e1ad7ed076"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75bb36f134883fdbe13d8e63b8675f5f12b80bb6627f7714c7d6c5becf22719f"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-win32.whl", hash = "sha256:4056f752015dfa9828dce3140dbadd543b555afb3252507348c493def166d454"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d4e702eea4a2903441f2735799d217f4ac1b55f7d8ad96ab7d4e25417cb0827c"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f0eddfcabd6936558ec020130f932d479930581171368fd728efcfb6ef0dd357"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddea4c352a488b5e1069069f2f501006b1a4362cb906bee9a193ef1245a7a61"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c86c9643cceb1d87ca08cdc30160d1b7ab49a8a21564868921959bd16441b8"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a0abef2ca47b33fb615b491ce31b055ef2430de52c5b3fb19a4042dbc5cadb"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:736895a020e31b428b3382a7887bfea96102c529530299f426bf2e636aacec9e"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:679cbb78914ab212c49c67ba2c7396dc599a8479de51b9a87b174700abd9ea49"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84ad5e29bf8bab3ad70fd707d3c05524862bddc54dc040982b0dbcff36481de7"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:8da5924cb1f9064589767b0f3fc39d03e3d0fb5aa29e0cb21d43106519bd624a"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:454ffc1cbb75227d15667c09f164a0099159da0c1f3d2636aa648f12675491ad"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:142119fb14a1ef6d758912b25c4e803c3ff66920635c44078666fe7cc3f8f759"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2a5a856019d2833c56a3dcac1b80fe795c95f401818ea963594b345929dffa7"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1fb9b2eec3c9714dd936860850300b51dbaa37404209c8d4cb66547884b7ed"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62c0285e91414f5c8f621a17b69fc0088394ccdaa961ef469e833dbff64bd5ea"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc3150f85e2dbcf99e65238c842d1cfe69d3e7649b19864c1cc043213d9cd730"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f02cf7221d5cd915d7fa58ab64f7ee6dd0f6cddbb48683debf5d04ae9b1c2cc1"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5653619b3eb5cbd35bfba3c12d575db2a74d15e0e1c08bf1db788069d410ce8"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d2f5d97fcbd004c03df8d8fe2b973fe2b14e7bfeb2cfa012eaa8759ce9a762f"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-win32.whl", hash = "sha256:3cace1837bc84e63b3fd2dfce37f08f8c18aeb81ef5cf6bb9b51f625cb4e6cd8"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fabbe18087c3d33c5824cb145ffca52eccd053061df1d79d4b66dafa5ad2a5ea"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:023af8c54fe63530545f70dd2a2a7eed18d07a9a77b94e8bf1e2ff7f252db9a3"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d66624f04de4af8bbf1c7f21cc06649c1c69a7f84109179add573ce35e46d448"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c532d5ab79be0199fa2658e24a02fce8542df196e60665dd322409a03db6a52c"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ec74fada3841b8c5f4c4f197bea916025cb9aa3fe5abf7d52b655d042f956"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c653fde75a6e5eb814d2a0a89378f83d1d3f502ab710904ee585c38888816c"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:961eb86e5be7d0973789f30ebcf6caab60b844203f4396ece27310295a6082c7"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:598b65d74615c021423bd45c2bc5e9b59539c875a9bdb7e5f2a6b92dfcfc268d"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:599941da468f2cf22bf90a84f6e2a65524e87be2fce844f96f2dd9a6c9d1e635"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-win32.whl", hash = "sha256:e6f7f3f41faffaea6596da86ecc2389672fa949bd035251eab26dc6697451d05"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:b8811d48078d1cf2a6863dafb896e68406c5f513048451cd2ded0473133473c7"}, + {file = "MarkupSafe-2.1.0.tar.gz", hash = "sha256:80beaf63ddfbc64a0452b841d8036ca0611e049650e20afcb882f5d3c266d65f"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +more-itertools = [ + {file = "more-itertools-8.12.0.tar.gz", hash = "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064"}, + {file = "more_itertools-8.12.0-py3-none-any.whl", hash = "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b"}, +] +mypy = [ + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +pytz = [ + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typing-extensions = [ + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, +] +watchdog = [ + {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b"}, + {file = "watchdog-2.1.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542"}, + {file = "watchdog-2.1.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669"}, + {file = "watchdog-2.1.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04"}, + {file = "watchdog-2.1.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6"}, + {file = "watchdog-2.1.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9"}, + {file = "watchdog-2.1.6-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_aarch64.whl", hash = "sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_armv7l.whl", hash = "sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_i686.whl", hash = "sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_s390x.whl", hash = "sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2"}, + {file = "watchdog-2.1.6-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15"}, + {file = "watchdog-2.1.6-py3-none-win32.whl", hash = "sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d"}, + {file = "watchdog-2.1.6-py3-none-win_amd64.whl", hash = "sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5"}, + {file = "watchdog-2.1.6-py3-none-win_ia64.whl", hash = "sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923"}, + {file = "watchdog-2.1.6.tar.gz", hash = "sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +werkzeug = [ + {file = "Werkzeug-2.0.3-py3-none-any.whl", hash = "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8"}, + {file = "Werkzeug-2.0.3.tar.gz", hash = "sha256:b863f8ff057c522164b6067c9e28b041161b4be5ba4d0daceeaa50a163822d3c"}, +] diff --git a/py-prototype/py_prototype.egg-info/PKG-INFO b/py-prototype/py_prototype.egg-info/PKG-INFO new file mode 100644 index 0000000..df6ac04 --- /dev/null +++ b/py-prototype/py_prototype.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 1.2 +Name: py-prototype +Version: 0.1.0 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: Tristram Oaten +Author-email: tris@oat.sh +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Requires-Python: >=3.8,<4.0 diff --git a/py-prototype/py_prototype.egg-info/SOURCES.txt b/py-prototype/py_prototype.egg-info/SOURCES.txt new file mode 100644 index 0000000..d109200 --- /dev/null +++ b/py-prototype/py_prototype.egg-info/SOURCES.txt @@ -0,0 +1,7 @@ +README.rst +setup.py +py_prototype/__init__.py +py_prototype.egg-info/PKG-INFO +py_prototype.egg-info/SOURCES.txt +py_prototype.egg-info/dependency_links.txt +py_prototype.egg-info/top_level.txt \ No newline at end of file diff --git a/py-prototype/py_prototype.egg-info/dependency_links.txt b/py-prototype/py_prototype.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/py-prototype/py_prototype.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/py-prototype/py_prototype.egg-info/top_level.txt b/py-prototype/py_prototype.egg-info/top_level.txt new file mode 100644 index 0000000..e23e64e --- /dev/null +++ b/py-prototype/py_prototype.egg-info/top_level.txt @@ -0,0 +1 @@ +py_prototype diff --git a/py-prototype/py_prototype/__init__.py b/py-prototype/py_prototype/__init__.py new file mode 100644 index 0000000..3dc1f76 --- /dev/null +++ b/py-prototype/py_prototype/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1.0" diff --git a/py-prototype/pyproject.toml b/py-prototype/pyproject.toml new file mode 100644 index 0000000..5248a54 --- /dev/null +++ b/py-prototype/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "py-prototype" +version = "0.1.0" +description = "" +authors = ["Tristram Oaten "] + +[tool.poetry.dependencies] +python = "^3.10.2" +mypy = "^0.931" +flask-restful = "^0.3.9" +flake8 = "^4.0.1" +watchdog = "^2.1.6" +PyYAML = "^6.0" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" + +[build-system] +requires = ["poetry>=0.12"] +build-backend = "poetry.masonry.api" diff --git a/py-prototype/readme.md b/py-prototype/readme.md new file mode 100644 index 0000000..25402a1 --- /dev/null +++ b/py-prototype/readme.md @@ -0,0 +1,6 @@ +## Python prototype + +- `make setup` installs (and/or updates) poetry and then dev deps +- `make watch` run check and the dev server + + * [ ] If you are new to poetry, you can run pip/whathaveyou commands inside `poetry shell`. diff --git a/py-prototype/tests/__init__.py b/py-prototype/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/py-prototype/tests/test_py_prototype.py b/py-prototype/tests/test_py_prototype.py new file mode 100644 index 0000000..6372766 --- /dev/null +++ b/py-prototype/tests/test_py_prototype.py @@ -0,0 +1,5 @@ +from py_prototype import __version__ + + +def test_version(): + assert __version__ == "0.1.0" From 9f61992caf3044d9d307473735d771327a53128d Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 7 Mar 2022 15:24:47 +0000 Subject: [PATCH 2/7] Delete get-poetry.py --- py-prototype/get-poetry.py | 1118 ------------------------------------ 1 file changed, 1118 deletions(-) delete mode 100644 py-prototype/get-poetry.py diff --git a/py-prototype/get-poetry.py b/py-prototype/get-poetry.py deleted file mode 100644 index f7450f7..0000000 --- a/py-prototype/get-poetry.py +++ /dev/null @@ -1,1118 +0,0 @@ -""" -This script will install Poetry and its dependencies -in isolation from the rest of the system. - -It does, in order: - - - Downloads the latest stable (or pre-release) version of poetry. - - Downloads all its dependencies in the poetry/_vendor directory. - - Copies it and all extra files in $POETRY_HOME. - - Updates the PATH in a system-specific way. - -There will be a `poetry` script that will be installed in $POETRY_HOME/bin -which will act as the poetry command but is slightly different in the sense -that it will use the current Python installation. - -What this means is that one Poetry installation can serve for multiple -Python versions. -""" -import argparse -import hashlib -import json -import os -import platform -import re -import shutil -import stat -import subprocess -import sys -import tarfile -import tempfile - -from contextlib import closing -from contextlib import contextmanager -from functools import cmp_to_key -from gzip import GzipFile -from io import UnsupportedOperation -from io import open - - -try: - from urllib.error import HTTPError - from urllib.request import Request - from urllib.request import urlopen -except ImportError: - from urllib2 import HTTPError - from urllib2 import Request - from urllib2 import urlopen - -try: - input = raw_input -except NameError: - pass - - -try: - try: - import winreg - except ImportError: - import _winreg as winreg -except ImportError: - winreg = None - -try: - u = unicode -except NameError: - u = str - -SHELL = os.getenv("SHELL", "") -WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt") - - -FOREGROUND_COLORS = { - "black": 30, - "red": 31, - "green": 32, - "yellow": 33, - "blue": 34, - "magenta": 35, - "cyan": 36, - "white": 37, -} - -BACKGROUND_COLORS = { - "black": 40, - "red": 41, - "green": 42, - "yellow": 43, - "blue": 44, - "magenta": 45, - "cyan": 46, - "white": 47, -} - -OPTIONS = {"bold": 1, "underscore": 4, "blink": 5, "reverse": 7, "conceal": 8} - - -def style(fg, bg, options): - codes = [] - - if fg: - codes.append(FOREGROUND_COLORS[fg]) - - if bg: - codes.append(BACKGROUND_COLORS[bg]) - - if options: - if not isinstance(options, (list, tuple)): - options = [options] - - for option in options: - codes.append(OPTIONS[option]) - - return "\033[{}m".format(";".join(map(str, codes))) - - -STYLES = { - "info": style("green", None, None), - "comment": style("yellow", None, None), - "error": style("red", None, None), - "warning": style("yellow", None, None), -} - - -def is_decorated(): - if platform.system().lower() == "windows": - return ( - os.getenv("ANSICON") is not None - or os.getenv("ConEmuANSI") == "ON" - or os.getenv("Term") == "xterm" - ) - - if not hasattr(sys.stdout, "fileno"): - return False - - try: - return os.isatty(sys.stdout.fileno()) - except UnsupportedOperation: - return False - - -def is_interactive(): - if not hasattr(sys.stdin, "fileno"): - return False - - try: - return os.isatty(sys.stdin.fileno()) - except UnsupportedOperation: - return False - - -def colorize(style, text): - if not is_decorated(): - return text - - return "{}{}\033[0m".format(STYLES[style], text) - - -@contextmanager -def temporary_directory(*args, **kwargs): - try: - from tempfile import TemporaryDirectory - except ImportError: - name = tempfile.mkdtemp(*args, **kwargs) - - yield name - - shutil.rmtree(name) - else: - with TemporaryDirectory(*args, **kwargs) as name: - yield name - - -def string_to_bool(value): - value = value.lower() - - return value in {"true", "1", "y", "yes"} - - -def expanduser(path): - """ - Expand ~ and ~user constructions. - - Includes a workaround for http://bugs.python.org/issue14768 - """ - expanded = os.path.expanduser(path) - if path.startswith("~/") and expanded.startswith("//"): - expanded = expanded[1:] - - return expanded - - -HOME = expanduser("~") -POETRY_HOME = os.environ.get("POETRY_HOME") or os.path.join(HOME, ".poetry") -POETRY_BIN = os.path.join(POETRY_HOME, "bin") -POETRY_ENV = os.path.join(POETRY_HOME, "env") -POETRY_LIB = os.path.join(POETRY_HOME, "lib") -POETRY_LIB_BACKUP = os.path.join(POETRY_HOME, "lib-backup") - - -BIN = """# -*- coding: utf-8 -*- -import glob -import sys -import os - -lib = os.path.normpath(os.path.join(os.path.realpath(__file__), "../..", "lib")) -vendors = os.path.join(lib, "poetry", "_vendor") -current_vendors = os.path.join( - vendors, "py{}".format(".".join(str(v) for v in sys.version_info[:2])) -) - -sys.path.insert(0, lib) -sys.path.insert(0, current_vendors) - -if __name__ == "__main__": - from poetry.console import main - - main() -""" - -BAT = u('@echo off\r\n{python_executable} "{poetry_bin}" %*\r\n') - - -PRE_MESSAGE = """# Welcome to {poetry}! - -This will download and install the latest version of {poetry}, -a dependency and package manager for Python. - -It will add the `poetry` command to {poetry}'s bin directory, located at: - -{poetry_home_bin} - -{platform_msg} - -You can uninstall at any time by executing this script with the --uninstall option, -and these changes will be reverted. -""" - -PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go! - -This will uninstall {poetry}. - -It will remove the `poetry` command from {poetry}'s bin directory, located at: - -{poetry_home_bin} - -This will also remove {poetry} from your system's PATH. -""" - - -PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by -modifying the profile file{plural} located at: - -{rcfiles}""" - - -PRE_MESSAGE_FISH = """This path will then be added to your `PATH` environment variable by -modifying the `fish_user_paths` universal variable.""" - -PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by -modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key.""" - -PRE_MESSAGE_NO_MODIFY_PATH = """This path needs to be in your `PATH` environment variable, -but will not be added automatically.""" - -POST_MESSAGE_UNIX = """{poetry} ({version}) is installed now. Great! - -To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` -environment variable. Next time you log in this will be done -automatically. - -To configure your current shell run `source {poetry_home_env}` -""" - -POST_MESSAGE_FISH = """{poetry} ({version}) is installed now. Great! - -{poetry}'s bin directory ({poetry_home_bin}) has been added to your `PATH` -environment variable by modifying the `fish_user_paths` universal variable. -""" - -POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great! - -To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` -environment variable. Future applications will automatically have the -correct environment, but you may need to restart your current shell. -""" - -POST_MESSAGE_UNIX_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! - -To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH` -environment variable. - -To configure your current shell run `source {poetry_home_env}` -""" - -POST_MESSAGE_FISH_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! - -To get started you need {poetry}'s bin directory ({poetry_home_bin}) -in your `PATH` environment variable, which you can add by running -the following command: - - set -U fish_user_paths {poetry_home_bin} $fish_user_paths -""" - -POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great! - -To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH` -environment variable. This has not been done automatically. -""" - - -class Installer: - - CURRENT_PYTHON = sys.executable - CURRENT_PYTHON_VERSION = sys.version_info[:2] - METADATA_URL = "https://pypi.org/pypi/poetry/json" - VERSION_REGEX = re.compile( - r"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?" - "(" - "[._-]?" - r"(?:(stable|beta|b|rc|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*)?)?" - "([.-]?dev)?" - ")?" - r"(?:\+[^\s]+)?" - ) - - REPOSITORY_URL = "https://github.com/python-poetry/poetry" - BASE_URL = REPOSITORY_URL + "/releases/download/" - FALLBACK_BASE_URL = "https://github.com/sdispater/poetry/releases/download/" - - def __init__( - self, - version=None, - preview=False, - force=False, - modify_path=True, - accept_all=False, - file=None, - base_url=BASE_URL, - ): - self._version = version - self._preview = preview - self._force = force - self._modify_path = modify_path - self._accept_all = accept_all - self._offline_file = file - self._base_url = base_url - - def allows_prereleases(self): - return self._preview - - def run(self): - version, current_version = self.get_version() - - if version is None: - return 0 - - self.customize_install() - self.display_pre_message() - self.ensure_home() - - try: - self.install( - version, upgrade=current_version is not None, file=self._offline_file - ) - except subprocess.CalledProcessError as e: - print(colorize("error", "An error has 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 - - def _is_supported(x): - mx = self.VERSION_REGEX.match(x) - vx = tuple(int(p) for p in mx.groups()[:3]) + (mx.group(5),) - return vx < (1, 2, 0) - - if not _is_supported(version): - print( - colorize( - "error", - "Version {version} does not support this installation method." - " Please specify a version prior to 1.2.0a1 explicitly using the" - " '--version' option.\nPlease see" - " https://python-poetry.org/blog/announcing-poetry-1-2-0a1.html#deprecation-of-the-get-poetry-py-script" - " for more information.".format(version=version), - ) - ) - return None, None - - print( - colorize( - "warning", - "This installer is deprecated. Poetry versions installed using this" - " script will not be able to use 'self update' command to upgrade to" - " 1.2.0a1 or later.", - ) - ) - - current_version = None - if os.path.exists(POETRY_LIB): - with open( - os.path.join(POETRY_LIB, "poetry", "__version__.py"), encoding="utf-8" - ) as f: - version_content = f.read() - - current_version_re = re.match( - '(?ms).*__version__ = "(.+)".*', version_content - ) - if not current_version_re: - print( - colorize( - "warning", - "Unable to get the current Poetry version. Assuming None", - ) - ) - else: - current_version = current_version_re.group(1) - - if current_version == version and not self._force: - print("Latest version already installed.") - return None, current_version - - return version, current_version - - def customize_install(self): - if not self._accept_all: - print("Before we start, please answer the following questions.") - print("You may simply press the Enter key to leave unchanged.") - - modify_path = input("Modify PATH variable? ([y]/n) ") or "y" - if modify_path.lower() in {"n", "no"}: - self._modify_path = False - - print("") - - def customize_uninstall(self): - if not self._accept_all: - print() - - uninstall = ( - input("Are you sure you want to uninstall Poetry? (y/[n]) ") or "n" - ) - if uninstall.lower() not in {"y", "yes"}: - return False - - print("") - - return True - - def ensure_home(self): - """ - Ensures that $POETRY_HOME exists or create it. - """ - if not os.path.exists(POETRY_HOME): - os.mkdir(POETRY_HOME, 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 = ["python3", "python"] - if WINDOWS: - allowed_executables += ["py.exe -3", "py.exe -2"] - - # \d in regex ensures we can convert to int later - version_matcher = re.compile(r"^Python (?P\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, - "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()) From e1ff861b86c37970c198c6e2f59ae940e4e7b19a Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Mon, 7 Mar 2022 15:25:53 +0000 Subject: [PATCH 3/7] fix readme formatting --- py-prototype/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-prototype/readme.md b/py-prototype/readme.md index 25402a1..b594eb9 100644 --- a/py-prototype/readme.md +++ b/py-prototype/readme.md @@ -3,4 +3,4 @@ - `make setup` installs (and/or updates) poetry and then dev deps - `make watch` run check and the dev server - * [ ] If you are new to poetry, you can run pip/whathaveyou commands inside `poetry shell`. +- If you are new to poetry, you can run pip/whathaveyou commands inside `poetry shell`. From 172f1ce75b18d42d07b2c137e3d26e356d178242 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 8 Mar 2022 11:38:48 +0000 Subject: [PATCH 4/7] add /published stub Co-authored-by: alice-carr --- py-prototype/app.py | 32 ++++++++-- py-prototype/server.rb | 141 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 py-prototype/server.rb diff --git a/py-prototype/app.py b/py-prototype/app.py index b8c3d5a..b01608c 100644 --- a/py-prototype/app.py +++ b/py-prototype/app.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from typing import Dict +from typing import Dict, List from flask import Flask from flask_restful import Resource, Api # type: ignore @@ -9,12 +9,34 @@ api = Api(app) -class HelloWorld(Resource): - def get(self) -> Dict[str, str]: - return {"hello": "forms"} +def forms_for_user(user) -> List[Dict[str, str]]: + """ + Query postgres for all the forms for this username + """ + return [{"key": "b", "display_name": "c"}] -api.add_resource(HelloWorld, "/") +class Published(Resource): + # def get(self) -> Dict[str, str]: + # return {"hello": "forms"} + + def get(self): + user = "Alice" + forms = [] + + for form in forms_for_user(user): + forms.append( + { + "Key": form["key"], + "DisplayName": form["display_name"], + "FeedbackForm": False, + } + ) + + return forms + + +api.add_resource(Published, "/published") if __name__ == "__main__": app.run(debug=True) diff --git a/py-prototype/server.rb b/py-prototype/server.rb new file mode 100644 index 0000000..06d98c7 --- /dev/null +++ b/py-prototype/server.rb @@ -0,0 +1,141 @@ +require "sinatra" +require "json" +require "pry" +require 'jwt' +require_relative "./db/database" + +class Server < Sinatra::Base + before do + content_type :json + @database = Database.new.connect + end + + after do + @database.disconnect + end + + post "/publish" do + user = authenticated_user + + request_body = request.body.read + request = JSON.parse(request_body) + + id = request['id'] + config = {} + config = request['configuration'] if request['configuration'] + + if form_exists_for_user?(user, id) + @database[:forms].where( + username: user, + key: id + ).update( + form: Sequel.pg_json(config) + ) + else + @database[:forms].insert( + username: user, + key: id, + display_name: id, + form: Sequel.pg_json(config) + ) + end + + config.to_json + end + + def form_exists_for_user?(user, key) + !@database[:forms].where(username: user, key: key).all.empty? + end + + get "/published" do + user = authenticated_user + forms = [] + + forms_for_user(user).each do |form| + forms << { + "Key": form[:key], + "DisplayName": form[:display_name], + "FeedbackForm": false + } + end + + forms.to_json + end + + get "/published/:id" do + api_key = authenticated_user + user = api_key unless api_key.nil? || api_key.empty? + + form = @database[:forms].where(username: user, key: params['id']).first + + if form.nil? + response.status = 404 + return {}.to_json + end + + { + id: form[:key], + values: form[:form] + }.to_json + end + + get "/seed/:user" do + seed_data_for_user(params['user']) + + @database[:forms].where(username: params['user']).select(:key).all.to_json + end + + get "/login" do + content_type :html + + erb :login + end + + post "/login" do + content_type :html + + payload = { user: params['name'] } + token = JWT.encode payload, nil, 'none' + + redirect "#{ENV['DESIGNER_URL']}/app/auth?token=#{token}" + end + + private + + def authenticated_user + token = request.env['HTTP_X_API_KEY'] + begin + decoded_token = JWT.decode token, nil, false + return decoded_token[0]["user"] + rescue + return nil + end + end + + def forms_for_user(user) + forms = @database[:forms].where(username: user).all + + if forms.empty? + seed_data_for_user(user) + return @database[:forms].where(username: user).all + end + + forms + end + + def seed_data_for_user(user) + forms = Dir.entries("./example_forms").select { |filename| File.file?("./example_forms/#{filename}") } + forms.map do |filename| + File.open("./example_forms/#{filename}") do |f| + file_content = f.read + @database[:forms].insert( + username: user, + key: filename, + display_name: filename, + form: file_content + ) + end + end + end + end +end From acdfee30eb7f84118e7533c3c65f6a51032bfee6 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 8 Mar 2022 12:15:19 +0000 Subject: [PATCH 5/7] /published from db works --- py-prototype/Makefile | 2 +- py-prototype/app.py | 44 ++++++++- py-prototype/poetry.lock | 176 +++++++++++++++++++++++++++++++++++- py-prototype/pyproject.toml | 3 + 4 files changed, 221 insertions(+), 4 deletions(-) diff --git a/py-prototype/Makefile b/py-prototype/Makefile index 4827d54..9af441c 100644 --- a/py-prototype/Makefile +++ b/py-prototype/Makefile @@ -13,7 +13,7 @@ watch: lints: clear @poetry run black app.py - @poetry run flake8 app.py + @poetry run flake8 app.py --max-line-length 88 @poetry run mypy app.py setup: diff --git a/py-prototype/app.py b/py-prototype/app.py index b01608c..58e66af 100644 --- a/py-prototype/app.py +++ b/py-prototype/app.py @@ -2,18 +2,57 @@ from typing import Dict, List +from sqlalchemy import ( # type: ignore + Table, + Column, + Integer, + String, + MetaData, + create_engine, +) +from sqlalchemy_json import MutableJson # type: ignore from flask import Flask from flask_restful import Resource, Api # type: ignore app = Flask(__name__) api = Api(app) +meta = MetaData() + +db = create_engine( + "postgresql://postgres:password@localhost/postgres", echo=True +).connect() + +forms = Table( + "forms", + meta, + Column("id", Integer, primary_key=True), + Column("username", String), + Column("key", String), + Column("display_name", String), + Column("form", MutableJson), +) + def forms_for_user(user) -> List[Dict[str, str]]: """ Query postgres for all the forms for this username """ - return [{"key": "b", "display_name": "c"}] + rows = [] + for row in db.execute(forms.select().where(forms.c.username == user)): + rows.append( + { + "id": row[0], + "username": row[1], + "key": row[2], + "display_name": row[3], + "form": row[4], + } + ) + return rows + + +print(forms_for_user("tris")) class Published(Resource): @@ -21,7 +60,7 @@ class Published(Resource): # return {"hello": "forms"} def get(self): - user = "Alice" + user = "tris" forms = [] for form in forms_for_user(user): @@ -40,3 +79,4 @@ def get(self): if __name__ == "__main__": app.run(debug=True) + diff --git a/py-prototype/poetry.lock b/py-prototype/poetry.lock index 14683fe..a18cda2 100644 --- a/py-prototype/poetry.lock +++ b/py-prototype/poetry.lock @@ -98,6 +98,17 @@ six = ">=1.3.0" [package.extras] docs = ["sphinx"] +[[package]] +name = "greenlet" +version = "1.1.2" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] + [[package]] name = "itsdangerous" version = "2.1.0" @@ -191,6 +202,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] dev = ["pre-commit", "tox"] +[[package]] +name = "psycopg2" +version = "2.9.3" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "py" version = "1.11.0" @@ -272,6 +291,50 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sqlalchemy" +version = "1.4.32" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3)"] +mariadb_connector = ["mariadb (>=1.0.1)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "sqlalchemy-json" +version = "0.4.0" +description = "JSON type with nested change tracking for SQLAlchemy" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" +sqlalchemy = ">=0.7" + [[package]] name = "tomli" version = "2.0.1" @@ -321,7 +384,7 @@ watchdog = ["watchdog"] [metadata] lock-version = "1.1" python-versions = "^3.10.2" -content-hash = "604334b44a78a7ea234239ee91b15608ad6cd2f10c2f893606a470e9032a8ca1" +content-hash = "3bdc5ced7c8303e6980a1814406aa6848c47c5ef9fb35ce49fd2f26efa2f7632" [metadata.files] aniso8601 = [ @@ -356,6 +419,63 @@ flask-restful = [ {file = "Flask-RESTful-0.3.9.tar.gz", hash = "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e"}, {file = "Flask_RESTful-0.3.9-py2.py3-none-any.whl", hash = "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2"}, ] +greenlet = [ + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, +] itsdangerous = [ {file = "itsdangerous-2.1.0-py3-none-any.whl", hash = "sha256:29285842166554469a56d427addc0843914172343784cb909695fdbe90a3e129"}, {file = "itsdangerous-2.1.0.tar.gz", hash = "sha256:d848fcb8bc7d507c4546b448574e8a44fc4ea2ba84ebf8d783290d53e81992f5"}, @@ -448,6 +568,19 @@ pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] +psycopg2 = [ + {file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"}, + {file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"}, + {file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"}, + {file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"}, + {file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"}, + {file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"}, + {file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"}, +] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, @@ -511,6 +644,47 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.32-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:4b2bcab3a914715d332ca783e9bda13bc570d8b9ef087563210ba63082c18c16"}, + {file = "SQLAlchemy-1.4.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:159c2f69dd6efd28e894f261ffca1100690f28210f34cfcd70b895e0ea7a64f3"}, + {file = "SQLAlchemy-1.4.32-cp27-cp27m-win_amd64.whl", hash = "sha256:d7e483f4791fbda60e23926b098702340504f7684ce7e1fd2c1bf02029288423"}, + {file = "SQLAlchemy-1.4.32-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4aa96e957141006181ca58e792e900ee511085b8dae06c2d08c00f108280fb8a"}, + {file = "SQLAlchemy-1.4.32-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:576684771456d02e24078047c2567025f2011977aa342063468577d94e194b00"}, + {file = "SQLAlchemy-1.4.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff677fa4522dafb5a5e2c0cf909790d5d367326321aeabc0dffc9047cb235bd"}, + {file = "SQLAlchemy-1.4.32-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8679f9aba5ac22e7bce54ccd8a77641d3aea3e2d96e73e4356c887ebf8ff1082"}, + {file = "SQLAlchemy-1.4.32-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7046f7aa2db445daccc8424f50b47a66c4039c9f058246b43796aa818f8b751"}, + {file = "SQLAlchemy-1.4.32-cp310-cp310-win32.whl", hash = "sha256:bedd89c34ab62565d44745212814e4b57ef1c24ad4af9b29c504ce40f0dc6558"}, + {file = "SQLAlchemy-1.4.32-cp310-cp310-win_amd64.whl", hash = "sha256:199dc6d0068753b6a8c0bd3aceb86a3e782df118260ebc1fa981ea31ee054674"}, + {file = "SQLAlchemy-1.4.32-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:8e1e5d96b744a4f91163290b01045430f3f32579e46d87282449e5b14d27d4ac"}, + {file = "SQLAlchemy-1.4.32-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edfcf93fd92e2f9eef640b3a7a40db20fe3c1d7c2c74faa41424c63dead61b76"}, + {file = "SQLAlchemy-1.4.32-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04164e0063feb7aedd9d073db0fd496edb244be40d46ea1f0d8990815e4b8c34"}, + {file = "SQLAlchemy-1.4.32-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba59761c19b800bc2e1c9324da04d35ef51e4ee9621ff37534bc2290d258f71"}, + {file = "SQLAlchemy-1.4.32-cp36-cp36m-win32.whl", hash = "sha256:708973b5d9e1e441188124aaf13c121e5b03b6054c2df59b32219175a25aa13e"}, + {file = "SQLAlchemy-1.4.32-cp36-cp36m-win_amd64.whl", hash = "sha256:316270e5867566376e69a0ac738b863d41396e2b63274616817e1d34156dff0e"}, + {file = "SQLAlchemy-1.4.32-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:9a0195af6b9050c9322a97cf07514f66fe511968e623ca87b2df5e3cf6349615"}, + {file = "SQLAlchemy-1.4.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e4a3c0c3c596296b37f8427c467c8e4336dc8d50f8ed38042e8ba79507b2c9"}, + {file = "SQLAlchemy-1.4.32-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bca714d831e5b8860c3ab134c93aec63d1a4f493bed20084f54e3ce9f0a3bf99"}, + {file = "SQLAlchemy-1.4.32-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9a680d9665f88346ed339888781f5236347933906c5a56348abb8261282ec48"}, + {file = "SQLAlchemy-1.4.32-cp37-cp37m-win32.whl", hash = "sha256:9cb5698c896fa72f88e7ef04ef62572faf56809093180771d9be8d9f2e264a13"}, + {file = "SQLAlchemy-1.4.32-cp37-cp37m-win_amd64.whl", hash = "sha256:8b9a395122770a6f08ebfd0321546d7379f43505882c7419d7886856a07caa13"}, + {file = "SQLAlchemy-1.4.32-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:3f88a4ee192142eeed3fe173f673ea6ab1f5a863810a9d85dbf6c67a9bd08f97"}, + {file = "SQLAlchemy-1.4.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd93162615870c976dba43963a24bb418b28448fef584f30755990c134a06a55"}, + {file = "SQLAlchemy-1.4.32-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a2e73508f939175363d8a4be9dcdc84cf16a92578d7fa86e6e4ca0e6b3667b2"}, + {file = "SQLAlchemy-1.4.32-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfec934aac7f9fa95fc82147a4ba5db0a8bdc4ebf1e33b585ab8860beb10232f"}, + {file = "SQLAlchemy-1.4.32-cp38-cp38-win32.whl", hash = "sha256:bb42f9b259c33662c6a9b866012f6908a91731a419e69304e1261ba3ab87b8d1"}, + {file = "SQLAlchemy-1.4.32-cp38-cp38-win_amd64.whl", hash = "sha256:7ff72b3cc9242d1a1c9b84bd945907bf174d74fc2519efe6184d6390a8df478b"}, + {file = "SQLAlchemy-1.4.32-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5dc9801ae9884e822ba942ca493642fb50f049c06b6dbe3178691fce48ceb089"}, + {file = "SQLAlchemy-1.4.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4607d2d16330757818c9d6fba322c2e80b4b112ff24295d1343a80b876eb0ed"}, + {file = "SQLAlchemy-1.4.32-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:20e9eba7fd86ef52e0df25bea83b8b518dfdf0bce09b336cfe51671f52aaaa3f"}, + {file = "SQLAlchemy-1.4.32-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:290cbdf19129ae520d4bdce392648c6fcdbee763bc8f750b53a5ab51880cb9c9"}, + {file = "SQLAlchemy-1.4.32-cp39-cp39-win32.whl", hash = "sha256:1bbac3e8293b34c4403d297e21e8f10d2a57756b75cff101dc62186adec725f5"}, + {file = "SQLAlchemy-1.4.32-cp39-cp39-win_amd64.whl", hash = "sha256:b3f1d9b3aa09ab9adc7f8c4b40fc3e081eb903054c9a6f9ae1633fe15ae503b4"}, + {file = "SQLAlchemy-1.4.32.tar.gz", hash = "sha256:6fdd2dc5931daab778c2b65b03df6ae68376e028a3098eb624d0909d999885bc"}, +] +sqlalchemy-json = [ + {file = "sqlalchemy-json-0.4.0.tar.gz", hash = "sha256:d8e72cac50724a17cc137c98bec5cb5990e9f1e8fc3eb30dd225fb47c087ea27"}, + {file = "sqlalchemy_json-0.4.0-py2.py3-none-any.whl", hash = "sha256:0f52f24301aa3b5ea240b622facc489eff2e7bfddde931ba988bfabc306b1778"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, diff --git a/py-prototype/pyproject.toml b/py-prototype/pyproject.toml index 5248a54..fdb81cd 100644 --- a/py-prototype/pyproject.toml +++ b/py-prototype/pyproject.toml @@ -11,6 +11,9 @@ flask-restful = "^0.3.9" flake8 = "^4.0.1" watchdog = "^2.1.6" PyYAML = "^6.0" +SQLAlchemy = "^1.4.32" +sqlalchemy-json = "^0.4.0" +psycopg2 = "^2.9.3" [tool.poetry.dev-dependencies] pytest = "^5.2" From 435a62f3e0d4b4f0383f5ff34e788a0a3bac0003 Mon Sep 17 00:00:00 2001 From: Tristram Oaten Date: Tue, 15 Mar 2022 11:16:06 +0000 Subject: [PATCH 6/7] post to publish --- py-prototype/app.py | 91 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/py-prototype/app.py b/py-prototype/app.py index 58e66af..e237a4b 100644 --- a/py-prototype/app.py +++ b/py-prototype/app.py @@ -1,7 +1,9 @@ #!/usr/bin/env python3 from typing import Dict, List +import json +import sqlalchemy # type: ignore from sqlalchemy import ( # type: ignore Table, Column, @@ -11,7 +13,7 @@ create_engine, ) from sqlalchemy_json import MutableJson # type: ignore -from flask import Flask +from flask import Flask, request from flask_restful import Resource, Api # type: ignore app = Flask(__name__) @@ -40,6 +42,7 @@ def forms_for_user(user) -> List[Dict[str, str]]: """ rows = [] for row in db.execute(forms.select().where(forms.c.username == user)): + print(row[0]) rows.append( { "id": row[0], @@ -52,14 +55,61 @@ def forms_for_user(user) -> List[Dict[str, str]]: return rows -print(forms_for_user("tris")) +def form_by_id_for_user(form_id: str, user: str) -> Dict[str, object]: + """ + Query postgres for form by id for this username + """ + row = db.execute( + forms.select().where( + sqlalchemy.and_(forms.c.username == user, forms.c.key == form_id) + ) + ).fetchone() + if not row: + return {} + form = { + "id": row[0], + "username": row[1], + "key": row[2], + "display_name": row[3], + "form": row[4], + } + return form + + +def form_exists_for_user(user: str, form_id: str) -> bool: + """ + Query postgres for form by id for this username + """ + + row = db.execute( + forms.select().where( + sqlalchemy.and_(forms.c.username == user, forms.c.key == form_id) + ) + ).fetchall() + + return len(row) > 0 + + +def update_form_for_user(user: str, form: str): + db.execute( + forms.update() + .where(forms.c.username == user, forms.c.key == json.loads(form)["id"]) + .values(form=form) + ) + + +# TODO +def insert_form_for_user(user: str, form: str): + db.execute( + forms.update() + .where(forms.c.username == user, forms.c.key == json.loads(form)["id"]) + .values(form=form) + ) -class Published(Resource): - # def get(self) -> Dict[str, str]: - # return {"hello": "forms"} - def get(self): +class Published(Resource): + def get(self) -> List[Dict[str, object]]: user = "tris" forms = [] @@ -75,8 +125,35 @@ def get(self): return forms +class Publish(Resource): + """ + curl http://localhost:5000/publish \ + -d "data={'id': 'report-a-terrorist', 'configuration': '{}'}" -X POST + """ + + def post(self): + user = "tris" + request_body = json.loads(request.form["data"]) + + form_id = request_body["id"] + config = request_body["configuration"] if request_body["configuration"] else {} + if form_exists_for_user(user, form_id): + update_form_for_user(user, config) + else: + insert_form_for_user(user, config) + return config + + +class PublishedId(Resource): + def get(self, form_id) -> Dict[str, object]: + user = "tris" + + return form_by_id_for_user(form_id, user) + + api.add_resource(Published, "/published") +api.add_resource(PublishedId, "/published/") +api.add_resource(Publish, "/publish") if __name__ == "__main__": app.run(debug=True) - From ded94e34a04170d80ae6d8112c403e76350b83cd Mon Sep 17 00:00:00 2001 From: alice-carr Date: Mon, 28 Mar 2022 16:37:00 +0100 Subject: [PATCH 7/7] add logic to update and insert form for user --- py-prototype/app.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/py-prototype/app.py b/py-prototype/app.py index e237a4b..84f6e68 100644 --- a/py-prototype/app.py +++ b/py-prototype/app.py @@ -84,27 +84,25 @@ def form_exists_for_user(user: str, form_id: str) -> bool: row = db.execute( forms.select().where( - sqlalchemy.and_(forms.c.username == user, forms.c.key == form_id) + sqlalchemy.and_(forms.c.username == user, forms.c.id == form_id) ) ).fetchall() return len(row) > 0 -def update_form_for_user(user: str, form: str): +def update_form_for_user(user: str, form: str, form_id: str): db.execute( forms.update() - .where(forms.c.username == user, forms.c.key == json.loads(form)["id"]) - .values(form=form) + .where(forms.c.username == user, forms.c.key == form["key"]) + .values(form=form, username=user, id=form_id) ) -# TODO -def insert_form_for_user(user: str, form: str): +def insert_form_for_user(user: str, form: str, form_id: str): db.execute( - forms.update() - .where(forms.c.username == user, forms.c.key == json.loads(form)["id"]) - .values(form=form) + forms.insert() + .values(form=form, username=user, id=form_id) ) @@ -138,9 +136,9 @@ def post(self): form_id = request_body["id"] config = request_body["configuration"] if request_body["configuration"] else {} if form_exists_for_user(user, form_id): - update_form_for_user(user, config) + update_form_for_user(user, config, form_id) else: - insert_form_for_user(user, config) + insert_form_for_user(user, config, form_id) return config