Compare commits

...

13 Commits
v0.3.9 ... main

Author SHA1 Message Date
38facc0adc
accidental merge 2020-11-13 18:24:33 +00:00
b53a6d4006
update eol notice 2020-11-13 18:23:48 +00:00
8e070dbe2a
Merge pull request #38 from hrfee/dependabot/pip/cryptography-3.2
Bump cryptography from 3.0 to 3.2
2020-10-31 21:42:23 +00:00
dependabot[bot]
d34cf0c40d
Bump cryptography from 3.0 to 3.2
Bumps [cryptography](https://github.com/pyca/cryptography) from 3.0 to 3.2.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/3.0...3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-27 22:42:19 +00:00
6ec214f644
Move og readme to separate file
officially deprecated now i guess.
2020-09-14 12:35:56 +01:00
a4feaa5d64
add deprecation notice to program 2020-08-31 14:13:53 +01:00
237c575441
make jfa-go mention bigger
you should try it out.
2020-08-18 14:34:26 +01:00
9185b59d16
Dockerfile builds from source
Uses multi-stage build, like jfa-go. I'm hoping this'll shrink the image
aswell.
2020-08-17 12:07:28 +01:00
43c0631f9b
add non interactive modes to build scripts 2020-08-17 11:45:42 +01:00
3d10a8fe06
fix email generation on windows 2020-08-04 18:14:39 +01:00
8d265879cc
actually fixed windows scss build 2020-08-04 18:12:18 +01:00
a38045cefb
potential fixes for windows
hopefully fixes scss and email generation on windows by fixing runcmd()
and (optionally) reading npm bin location from the 'npm bin' command.
also, config path is cast to string before being passed to configparser.
2020-08-04 01:29:29 +01:00
d5609f3870 update readme, fix setup.js setting toggle 2020-08-02 02:33:34 +01:00
13 changed files with 1083 additions and 1065 deletions

27
Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM python:3.8.2-buster AS build
COPY . /opt/build
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN cd /opt/build \
&& rm -rf dist \
&& apt install nodejs \
&& ~/.poetry/bin/poetry update \
&& pip install libsass \
&& python scss/get_node_deps.py \
&& python scss/compile.py -y \
&& python mail/generate.py -y \
&& ~/.poetry/bin/poetry build -f wheel
FROM python:3.8.2-buster
COPY --from=build /opt/build/dist /opt/dist
RUN pip install /opt/dist/*.whl
RUN sed -i 's#id="pwrJfPath" placeholder="Folder"#id="pwrJfPath" value="/jf" disabled#g' /usr/local/lib/python3.8/site-packages/jellyfin_accounts/data/templates/setup.html
CMD [ "python3.8", "/usr/local/bin/jf-accounts", "-d", "/data" ]

View File

@ -1,90 +1,6 @@
# ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/bs5/images/jellyfin-accounts-banner-wide.svg) ## 👀 ➡️: Try [jfa-go](https://github.com/hrfee/jfa-go), a rewrite in Go. It's faster and has more features.
###### I won't be updating this version any more, and switching should be easy.
A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin). **please don't open any new issues.**
* Provides a web interface for creating/sending invites
* Sends out emails when a user requests a password reset
* Uses a basic python jellyfin API client for communication with the server.
* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress)
* Frontend uses [Bootstrap](https://v5.getbootstrap.com)
* Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja)
## Interface
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/jfa.gif" width="100%"></img>
</p>
<p align="center"> You can find the old README [here](https://github.com/hrfee/jellyfin-accounts/blob/main/README.old.md).
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/admin.png" width="48%" style="margin-right: 1.5%;" alt="Admin page"></img>
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/create.png" width="48%" style="margin-left: 1.5%;" alt="Account creation page"></img>
</p>
## Get it
### Requirements
* This should work anywhere Python does, i've tried to not use anything OS-specific. Drop an issue if there's a problem, of course.
```
* python >= 3.6
* flask
* flask_httpauth
* jinja2
* requests
* itsdangerous
* passlib
* pyOpenSSL
* waitress
* pytz
* python-dateutil
* watchdog
* packaging
```
### Install
Usually as simple as:
```
pip install jellyfin-accounts
```
If not, or if you want to use docker, see [install](https://github.com/hrfee/jellyfin-accounts/wiki/Install).
## Usage
* Passing no arguments will run the server
```
usage: jf-accounts [-h] [-c CONFIG] [-d DATA] [--host HOST] [-p PORT] [-g]
jellyfin-accounts
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
specifies path to configuration file.
-d DATA, --data DATA specifies directory to store data in. defaults to
~/.jf-accounts.
--host HOST address to host web ui on.
-p PORT, --port PORT port to host web ui on.
-g, --get_defaults tool to grab a JF users policy (access, perms, etc.)
and homescreen layout and output it as json to be used
as a user template.
```
## Setup
#### New user template
* You may want to restrict a user from accessing certain libraries (e.g 4K Movies), display their account on the login screen by default, or set a default homecrseen layout. Jellyfin stores these settings in the user's policy, configuration and displayPreferences.
* Make a temporary account and configure it, then in the web UI, go into "Settings => Set new account defaults". Choose the account, and its configuration will be stored for future use.
#### Emails/Password Resets
* When someone initiates forget password on Jellyfin, a file named `passwordreset*.json` is created in its configuration directory. This directory is monitored and when created, the program reads the username, expiry time and PIN, puts it into a template and sends it to whatever address is specified in `emails.json`.
* **The default forget password popup references the `passwordreset*.json` file created. This is confusing for users, so a quick fix is to edit the `MessageForgotPasswordFileCreated` string in Jellyfin's language folder.**
* Currently, jellyfin-accounts supports generic SSL/TLS or STARTTLS secured SMTP, and the [mailgun](https://mailgun.com) REST API.
* Email html is created using [mjml](https://mjml.io), and [jinja](https://github.com/pallets/jinja) templating is used. If you wish to create your own, ensure you use the same jinja expressions (`{{ pin }}`, etc.) as used in `data/email.mjml` or `invite-email.mjml`, and also create plain text versions for legacy email clients.
### Configuration
* Note: Make sure to put this behind a reverse proxy with HTTPS.
On first run, access the setup wizard at `0.0.0.0:8056`. When finished, restart the program.
The configuration is stored at `~/.jf-accounts/config.ini`. Settings can be changed through the web UI, or by manually editing the file.
For detailed descriptions of each setting, see [setup](https://github.com/hrfee/jellyfin-accounts/wiki/Setup).
### Donations
I strongly suggest you send your money to [Jellyfin](https://opencollective.com/jellyfin) or a good charity, but for those who want to help me out, a Paypal link is below.
[Donate](https://www.paypal.me/hrfee)

91
README.old.md Normal file
View File

@ -0,0 +1,91 @@
# ![jellyfin-accounts](https://raw.githubusercontent.com/hrfee/jellyfin-accounts/bs5/images/jellyfin-accounts-banner-wide.svg)
A basic account management system for [Jellyfin](https://github.com/jellyfin/jellyfin).
* Provides a web interface for creating/sending invites
* Sends out emails when a user requests a password reset
* Uses a basic python jellyfin API client for communication with the server.
* Uses [Flask](https://github.com/pallets/flask), [HTTPAuth](https://github.com/miguelgrinberg/Flask-HTTPAuth), [itsdangerous](https://github.com/pallets/itsdangerous), and [Waitress](https://github.com/Pylons/waitress)
* Frontend uses [Bootstrap](https://v5.getbootstrap.com)
* Password resets are handled using smtplib, requests, and [jinja](https://github.com/pallets/jinja)
## Interface
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/jfa.gif" width="100%"></img>
</p>
<p align="center">
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/admin.png" width="48%" style="margin-right: 1.5%;" alt="Admin page"></img>
<img src="https://raw.githubusercontent.com/hrfee/jellyfin-accounts/main/images/create.png" width="48%" style="margin-left: 1.5%;" alt="Account creation page"></img>
</p>
## Get it
### Requirements
* This should work anywhere Python does, i've tried to not use anything OS-specific. Drop an issue if there's a problem, of course.
```
* python >= 3.6
* flask
* flask_httpauth
* jinja2
* requests
* itsdangerous
* passlib
* pyOpenSSL
* waitress
* pytz
* python-dateutil
* watchdog
* packaging
```
### Install
Usually as simple as:
```
pip install jellyfin-accounts
```
If not, or if you want to use docker, see [install](https://github.com/hrfee/jellyfin-accounts/wiki/Install).
## Usage
* Passing no arguments will run the server
```
usage: jf-accounts [-h] [-c CONFIG] [-d DATA] [--host HOST] [-p PORT] [-g]
jellyfin-accounts
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
specifies path to configuration file.
-d DATA, --data DATA specifies directory to store data in. defaults to
~/.jf-accounts.
--host HOST address to host web ui on.
-p PORT, --port PORT port to host web ui on.
-g, --get_defaults tool to grab a JF users policy (access, perms, etc.)
and homescreen layout and output it as json to be used
as a user template.
```
## Setup
#### New user template
* You may want to restrict a user from accessing certain libraries (e.g 4K Movies), display their account on the login screen by default, or set a default homecrseen layout. Jellyfin stores these settings in the user's policy, configuration and displayPreferences.
* Make a temporary account and configure it, then in the web UI, go into "Settings => Set new account defaults". Choose the account, and its configuration will be stored for future use.
#### Emails/Password Resets
* When someone initiates forget password on Jellyfin, a file named `passwordreset*.json` is created in its configuration directory. This directory is monitored and when created, the program reads the username, expiry time and PIN, puts it into a template and sends it to whatever address is specified in `emails.json`.
* **The default forget password popup references the `passwordreset*.json` file created. This is confusing for users, so a quick fix is to edit the `MessageForgotPasswordFileCreated` string in Jellyfin's language folder.**
* Currently, jellyfin-accounts supports generic SSL/TLS or STARTTLS secured SMTP, and the [mailgun](https://mailgun.com) REST API.
* Email html is created using [mjml](https://mjml.io), and [jinja](https://github.com/pallets/jinja) templating is used. If you wish to create your own, ensure you use the same jinja expressions (`{{ pin }}`, etc.) as used in `data/email.mjml` or `invite-email.mjml`, and also create plain text versions for legacy email clients.
### Configuration
* Note: Make sure to put this behind a reverse proxy with HTTPS.
On first run, access the setup wizard at `0.0.0.0:8056`. When finished, restart the program.
The configuration is stored at `~/.jf-accounts/config.ini`. Settings can be changed through the web UI, or by manually editing the file.
For detailed descriptions of each setting, see [setup](https://github.com/hrfee/jellyfin-accounts/wiki/Setup).
### Donations
I strongly suggest you send your money to [Jellyfin](https://opencollective.com/jellyfin) or a good charity, but for those who want to help me out, a Paypal link is below.
[Donate](https://www.paypal.me/hrfee)

View File

@ -1,6 +1,8 @@
# Runs it! # Runs it!
__version__ = "0.3.9" __version__ = "0.3.9"
print("Note: jellyfin-accounts has been deprecated. Try jfa-go, a rewrite thats fast, portable, and has more features. Find it at\nhttps://github.com/hrfee/jfa-go\n")
import secrets import secrets
import configparser import configparser
import shutil import shutil
@ -72,7 +74,7 @@ else:
# Temp config so logger knows whether to use debug mode or not # Temp config so logger knows whether to use debug mode or not
temp_config = configparser.RawConfigParser() temp_config = configparser.RawConfigParser()
temp_config.read(config_path) temp_config.read(str(config_path.resolve()))
def create_log(name): def create_log(name):
@ -98,7 +100,8 @@ config = Config(config_path, secrets.token_urlsafe(16), data_dir, local_dir, log
web_log = create_log("waitress") web_log = create_log("waitress")
if not first_run: if not first_run:
email_log = create_log("emails") email_log = create_log("email")
pwr_log = create_log("pwr")
auth_log = create_log("auth") auth_log = create_log("auth")
if args.host is not None: if args.host is not None:
@ -185,6 +188,7 @@ def resp(success=True, code=500):
r.status_code = code r.status_code = code
return r return r
app = Flask(__name__, root_path=str(local_dir))
def main(): def main():
if args.install: if args.install:
@ -290,8 +294,6 @@ def main():
success = True success = True
else: else:
global app
app = Flask(__name__, root_path=str(local_dir))
app.config["DEBUG"] = config.getboolean("ui", "debug") app.config["DEBUG"] = config.getboolean("ui", "debug")
app.config["SECRET_KEY"] = secrets.token_urlsafe(16) app.config["SECRET_KEY"] = secrets.token_urlsafe(16)
app.config["JSON_SORT_KEYS"] = False app.config["JSON_SORT_KEYS"] = False
@ -328,7 +330,7 @@ def main():
jellyfin_accounts.pw_reset.start() jellyfin_accounts.pw_reset.start()
pwr = threading.Thread(target=start_pwr, daemon=True) pwr = threading.Thread(target=start_pwr, daemon=True)
log.info("Starting email thread") log.info("Starting password reset thread")
pwr.start() pwr.start()
def signal_handler(sig, frame): def signal_handler(sig, frame):

View File

@ -21,16 +21,19 @@ function checkEmailRadio() {
document.getElementById('emailCommonArea').style.display = ''; document.getElementById('emailCommonArea').style.display = '';
document.getElementById('emailSMTPArea').style.display = ''; document.getElementById('emailSMTPArea').style.display = '';
document.getElementById('emailMailgunArea').style.display = 'none'; document.getElementById('emailMailgunArea').style.display = 'none';
document.getElementById('notificationsEnabled').checked = true;
} else if (document.getElementById('emailMailgunRadio').checked) { } else if (document.getElementById('emailMailgunRadio').checked) {
document.getElementById('emailCommonArea').style.display = ''; document.getElementById('emailCommonArea').style.display = '';
document.getElementById('emailSMTPArea').style.display = 'none'; document.getElementById('emailSMTPArea').style.display = 'none';
document.getElementById('emailMailgunArea').style.display = ''; document.getElementById('emailMailgunArea').style.display = '';
document.getElementById('notificationsEnabled').checked = true;
} else if (document.getElementById('emailDisabledRadio').checked) { } else if (document.getElementById('emailDisabledRadio').checked) {
document.getElementById('emailCommonArea').style.display = 'none'; document.getElementById('emailCommonArea').style.display = 'none';
document.getElementById('emailSMTPArea').style.display = 'none'; document.getElementById('emailSMTPArea').style.display = 'none';
document.getElementById('emailMailgunArea').style.display = 'none'; document.getElementById('emailMailgunArea').style.display = 'none';
document.getElementById('emailNextButton').href = '#page-8'; document.getElementById('emailNextButton').href = '#page-8';
document.getElementById('valBackButton').href = '#page-4'; document.getElementById('valBackButton').href = '#page-4';
document.getElementById('notificationsEnabled').checked = false;
}; };
}; };
var emailRadios = ['emailDisabledRadio', 'emailSMTPRadio', 'emailMailgunRadio']; var emailRadios = ['emailDisabledRadio', 'emailSMTPRadio', 'emailMailgunRadio'];
@ -165,6 +168,7 @@ document.getElementById('submitButton').onclick = function() {
if (document.getElementById('emailDisabledRadio').checked) { if (document.getElementById('emailDisabledRadio').checked) {
config['password_resets']['enabled'] = 'false'; config['password_resets']['enabled'] = 'false';
config['invite_emails']['enabled'] = 'false'; config['invite_emails']['enabled'] = 'false';
config['notificatons']['enabled'] = 'false';
} else { } else {
if (document.getElementById('emailSMTPRadio').checked) { if (document.getElementById('emailSMTPRadio').checked) {
if (document.getElementById('emailSSL_TLS').checked) { if (document.getElementById('emailSSL_TLS').checked) {

View File

@ -61,6 +61,7 @@ class Jellyfin:
self.version = version self.version = version
self.device = device self.device = device
self.deviceId = deviceId self.deviceId = deviceId
self.authenticated = False
self.timeout = cacheMinutes * 60 self.timeout = cacheMinutes * 60
self.userCacheAge = time.time() - self.timeout - 1 self.userCacheAge = time.time() - self.timeout - 1
self.userCachePublicAge = self.userCacheAge self.userCachePublicAge = self.userCacheAge
@ -80,10 +81,10 @@ class Jellyfin:
"X-Emby-Authorization": self.auth, "X-Emby-Authorization": self.auth,
} }
try: try:
self.info = requests.get(self.server + "/System/Info/Public").json() self.info = requests.get(f"{self.server}/System/Info/Public").json()
except: except:
pass pass
def reloadCache(self): def reloadCache(self):
""" Forces a reload of the user caches """ """ Forces a reload of the user caches """
self.userCachePublicAge = time.time() - self.timeout - 1 self.userCachePublicAge = time.time() - self.timeout - 1
@ -107,7 +108,7 @@ class Jellyfin:
""" """
if public is True: if public is True:
if (time.time() - self.userCachePublicAge) >= self.timeout: if (time.time() - self.userCachePublicAge) >= self.timeout:
response = requests.get(self.server + "/emby/Users/Public").json() response = requests.get(f"{self.server}/Users/Public").json()
self.userCachePublic = response self.userCachePublic = response
self.userCachePublicAge = time.time() self.userCachePublicAge = time.time()
else: else:
@ -117,7 +118,7 @@ class Jellyfin:
): ):
if (time.time() - self.userCacheAge) >= self.timeout: if (time.time() - self.userCacheAge) >= self.timeout:
response = requests.get( response = requests.get(
self.server + "/emby/Users", f"{self.server}/Users",
headers=self.header, headers=self.header,
params={"Username": self.username, "Pw": self.password}, params={"Username": self.username, "Pw": self.password},
) )
@ -125,7 +126,7 @@ class Jellyfin:
response = response.json() response = response.json()
self.userCache = response self.userCache = response
self.userCacheAge = time.time() self.userCacheAge = time.time()
else: elif response.status_code == 401:
try: try:
self.authenticate(self.username, self.password) self.authenticate(self.username, self.password)
return self.getUsers(username, userId, public) return self.getUsers(username, userId, public)
@ -161,12 +162,10 @@ class Jellyfin:
:param username: Plaintext username. :param username: Plaintext username.
:param password: Plaintext password. :param password: Plaintext password.
""" """
self.username = username
self.password = password
response = requests.post( response = requests.post(
self.server + "/emby/Users/AuthenticateByName", f"{self.server}/Users/AuthenticateByName",
headers=self.header, headers=self.header,
params={"Username": self.username, "Pw": self.password}, params={"Username": username, "Pw": password},
) )
if response.status_code == 200: if response.status_code == 200:
json = response.json() json = response.json()
@ -180,8 +179,11 @@ class Jellyfin:
self.auth += f", Token={self.accessToken}" self.auth += f", Token={self.accessToken}"
self.header["X-Emby-Authorization"] = self.auth self.header["X-Emby-Authorization"] = self.auth
self.info = requests.get( self.info = requests.get(
self.server + "/System/Info", headers=self.header f"{self.server}/System/Info", headers=self.header
).json() ).json()
self.username = username
self.password = password
self.authenticated = True
return True return True
else: else:
raise self.AuthenticationError raise self.AuthenticationError
@ -194,7 +196,7 @@ class Jellyfin:
:param policy: User policy in dictionary form. :param policy: User policy in dictionary form.
""" """
return requests.post( return requests.post(
self.server + "/Users/" + userId + "/Policy", f"{self.server}/Users/" + userId + "/Policy",
headers=self.header, headers=self.header,
params=policy, params=policy,
) )
@ -204,7 +206,7 @@ class Jellyfin:
if user["Name"] == username: if user["Name"] == username:
raise self.UserExistsError raise self.UserExistsError
response = requests.post( response = requests.post(
self.server + "/emby/Users/New", f"{self.server}/Users/New",
headers=self.header, headers=self.header,
params={"Name": username, "Password": password}, params={"Name": username, "Password": password},
) )
@ -222,7 +224,7 @@ class Jellyfin:
else: else:
param = "" param = ""
views = requests.get( views = requests.get(
self.server + "/Users/" + userId + "/Views" + param, headers=self.header f"{self.server}/Users/" + userId + "/Views" + param, headers=self.header
).json()["Items"] ).json()["Items"]
orderedViews = [] orderedViews = []
for library in views: for library in views:
@ -236,7 +238,7 @@ class Jellyfin:
:param configuration: Configuration to write in dictionary form. :param configuration: Configuration to write in dictionary form.
""" """
resp = requests.post( resp = requests.post(
self.server + "/Users/" + userId + "/Configuration", f"{self.server}/Users/" + userId + "/Configuration",
headers=self.header, headers=self.header,
params=configuration, params=configuration,
) )

View File

@ -6,7 +6,7 @@ from watchdog.events import FileSystemEventHandler
from jellyfin_accounts.email import Mailgun, Smtp from jellyfin_accounts.email import Mailgun, Smtp
from jellyfin_accounts.web_api import jf from jellyfin_accounts.web_api import jf
from jellyfin_accounts import config, data_store from jellyfin_accounts import config, data_store
from jellyfin_accounts import email_log as log from jellyfin_accounts import pwr_log as log
class Watcher: class Watcher:

View File

@ -441,7 +441,7 @@ def modifyConfig():
temp_config = configparser.RawConfigParser( temp_config = configparser.RawConfigParser(
comment_prefixes="/", allow_no_value=True comment_prefixes="/", allow_no_value=True
) )
temp_config.read(config_path) temp_config.read(str(config_path.resolve()))
for section in data: for section in data:
if section in temp_config and 'restart-program' not in section: if section in temp_config and 'restart-program' not in section:
for item in data[section]: for item in data[section]:

View File

@ -1,13 +1,39 @@
import subprocess import subprocess
import shutil import shutil
import os
import argparse
from pathlib import Path from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument(
"-y", "--yes", help="use assumed node bin directory.", action="store_true"
)
def runcmd(cmd): def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
return proc.communicate() return proc.communicate()
local_path = Path(__file__).resolve().parent local_path = Path(__file__).resolve().parent
node_bin = local_path.parent / 'node_modules' / '.bin' out = runcmd("npm bin")
try:
node_bin = Path(out[0].decode('utf-8').rstrip())
except:
node_bin = Path(out.decode('utf-8').rstrip())
args = parser.parse_args()
if not args.yes:
print(f"assuming npm bin directory \"{node_bin}\". Is this correct?")
if input("[yY/nN]: ").lower() == "n":
node_bin = local_path.parent / 'node_modules' / '.bin'
print(f"this? \"{node_bin}\"")
if input("[yY/nN]: ").lower() == "n":
node_bin = input("input bin directory: ")
for mjml in [f for f in local_path.iterdir() if f.is_file() and 'mjml' in f.suffix]: for mjml in [f for f in local_path.iterdir() if f.is_file() and 'mjml' in f.suffix]:
print(f'Compiling {mjml.name}') print(f'Compiling {mjml.name}')

1438
package-lock.json generated

File diff suppressed because it is too large Load Diff

378
poetry.lock generated
View File

@ -1,18 +1,18 @@
[[package]] [[package]]
category = "dev"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
name = "appdirs" name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.4.4"
[[package]] [[package]]
category = "dev"
description = "Classes Without Boilerplate"
name = "attrs" name = "attrs"
version = "19.3.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "19.3.0"
[package.extras] [package.extras]
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
@ -21,12 +21,12 @@ docs = ["sphinx", "zope.interface"]
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
[[package]] [[package]]
category = "dev"
description = "The uncompromising code formatter."
name = "black" name = "black"
version = "19.10b0"
description = "The uncompromising code formatter."
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
version = "19.10b0"
[package.dependencies] [package.dependencies]
appdirs = "*" appdirs = "*"
@ -41,72 +41,72 @@ typed-ast = ">=1.4.0"
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]] [[package]]
category = "main"
description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
version = "2020.6.20"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.6.20"
[[package]] [[package]]
category = "main"
description = "Foreign Function Interface for Python calling C code."
name = "cffi" name = "cffi"
version = "1.14.1"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.14.0"
[package.dependencies] [package.dependencies]
pycparser = "*" pycparser = "*"
[[package]] [[package]]
category = "main"
description = "Universal encoding detector for Python 2 and 3"
name = "chardet" name = "chardet"
version = "3.0.4"
description = "Universal encoding detector for Python 2 and 3"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.0.4"
[[package]] [[package]]
category = "main"
description = "Composable command line interface toolkit"
name = "click" name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "7.1.2"
[[package]] [[package]]
category = "main"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
name = "cryptography" name = "cryptography"
version = "3.2"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "2.9.2"
[package.dependencies] [package.dependencies]
cffi = ">=1.8,<1.11.3 || >1.11.3" cffi = ">=1.8,<1.11.3 || >1.11.3"
six = ">=1.4.1" six = ">=1.4.1"
[package.extras] [package.extras]
docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
idna = ["idna (>=2.1)"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
pep8test = ["flake8", "flake8-import-order", "pep8-naming"] ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]] [[package]]
category = "main"
description = "A simple framework for building complex web applications."
name = "flask" name = "flask"
version = "1.1.2"
description = "A simple framework for building complex web applications."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.1.2"
[package.dependencies] [package.dependencies]
Jinja2 = ">=2.10.1"
Werkzeug = ">=0.15"
click = ">=5.1" click = ">=5.1"
itsdangerous = ">=0.24" itsdangerous = ">=0.24"
Jinja2 = ">=2.10.1"
Werkzeug = ">=0.15"
[package.extras] [package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
@ -114,47 +114,47 @@ docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-
dotenv = ["python-dotenv"] dotenv = ["python-dotenv"]
[[package]] [[package]]
category = "main"
description = "Basic and Digest HTTP authentication for Flask routes"
name = "flask-httpauth" name = "flask-httpauth"
version = "4.1.0"
description = "Basic and Digest HTTP authentication for Flask routes"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "4.1.0"
[package.dependencies] [package.dependencies]
Flask = "*" Flask = "*"
[[package]] [[package]]
category = "dev"
description = "Lightweight in-process concurrent programming"
name = "greenlet" name = "greenlet"
version = "0.4.16"
description = "Lightweight in-process concurrent programming"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.4.16"
[[package]] [[package]]
category = "main"
description = "Internationalized Domain Names in Applications (IDNA)"
name = "idna" name = "idna"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.10" version = "2.10"
description = "Internationalized Domain Names in Applications (IDNA)"
[[package]]
category = "main" category = "main"
description = "Various helpers to pass data to untrusted environments and back."
name = "itsdangerous"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.1.0"
[[package]] [[package]]
name = "itsdangerous"
version = "1.1.0"
description = "Various helpers to pass data to untrusted environments and back."
category = "main" category = "main"
description = "A very fast and expressive template engine." optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "jinja2" name = "jinja2"
version = "2.11.2"
description = "A very fast and expressive template engine."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.11.2"
[package.dependencies] [package.dependencies]
MarkupSafe = ">=0.23" MarkupSafe = ">=0.23"
@ -163,62 +163,62 @@ MarkupSafe = ">=0.23"
i18n = ["Babel (>=0.8)"] i18n = ["Babel (>=0.8)"]
[[package]] [[package]]
category = "dev"
description = "Sass for Python: A straightforward binding of libsass for Python."
name = "libsass" name = "libsass"
version = "0.20.0"
description = "Sass for Python: A straightforward binding of libsass for Python."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.20.0"
[package.dependencies] [package.dependencies]
six = "*" six = "*"
[[package]] [[package]]
category = "main"
description = "Safely add untrusted strings to HTML/XML markup."
name = "markupsafe" name = "markupsafe"
version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
version = "1.1.1"
[[package]] [[package]]
category = "dev"
description = "MessagePack (de)serializer."
name = "msgpack" name = "msgpack"
version = "1.0.0"
description = "MessagePack (de)serializer."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.0.0"
[[package]] [[package]]
category = "dev"
description = "Transition packgage for pynvim"
name = "neovim" name = "neovim"
version = "0.3.1"
description = "Transition packgage for pynvim"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.3.1"
[package.dependencies] [package.dependencies]
pynvim = ">=0.3.1" pynvim = ">=0.3.1"
[[package]] [[package]]
category = "main"
description = "Core utilities for Python packages"
name = "packaging" name = "packaging"
version = "20.4"
description = "Core utilities for Python packages"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.4"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
six = "*" six = "*"
[[package]] [[package]]
category = "main"
description = "comprehensive password hashing framework supporting over 30 schemes"
name = "passlib" name = "passlib"
version = "1.7.2"
description = "comprehensive password hashing framework supporting over 30 schemes"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.7.2"
[package.extras] [package.extras]
argon2 = ["argon2-cffi (>=18.2.0)"] argon2 = ["argon2-cffi (>=18.2.0)"]
@ -227,47 +227,47 @@ build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-spthem
totp = ["cryptography"] totp = ["cryptography"]
[[package]] [[package]]
category = "dev"
description = "Utility library for gitignore style pattern matching of file paths."
name = "pathspec" name = "pathspec"
version = "0.8.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.8.0"
[[package]] [[package]]
category = "main"
description = "File system general utilities"
name = "pathtools" name = "pathtools"
version = "0.1.2"
description = "File system general utilities"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.1.2"
[[package]] [[package]]
category = "main"
description = "Cross-platform lib for process and system monitoring in Python."
name = "psutil" name = "psutil"
version = "5.7.2"
description = "Cross-platform lib for process and system monitoring in Python."
category = "main"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "5.7.2"
[package.extras] [package.extras]
test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"] test = ["ipaddress", "mock", "unittest2", "enum34", "pywin32", "wmi"]
[[package]] [[package]]
category = "main"
description = "C parser in Python"
name = "pycparser" name = "pycparser"
version = "2.20"
description = "C parser in Python"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.20"
[[package]] [[package]]
category = "dev"
description = "Python client to neovim"
name = "pynvim" name = "pynvim"
version = "0.4.1"
description = "Python client to neovim"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.4.1"
[package.dependencies] [package.dependencies]
greenlet = "*" greenlet = "*"
@ -278,12 +278,12 @@ pyuv = ["pyuv (>=1.0.0)"]
test = ["pytest (>=3.4.0)"] test = ["pytest (>=3.4.0)"]
[[package]] [[package]]
category = "main"
description = "Python wrapper module around the OpenSSL library"
name = "pyopenssl" name = "pyopenssl"
version = "19.1.0"
description = "Python wrapper module around the OpenSSL library"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "19.1.0"
[package.dependencies] [package.dependencies]
cryptography = ">=2.8" cryptography = ">=2.8"
@ -294,47 +294,47 @@ docs = ["sphinx", "sphinx-rtd-theme"]
test = ["flaky", "pretend", "pytest (>=3.0.1)"] test = ["flaky", "pretend", "pytest (>=3.0.1)"]
[[package]] [[package]]
category = "main"
description = "Python parsing module"
name = "pyparsing" name = "pyparsing"
version = "2.4.7"
description = "Python parsing module"
category = "main"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.7"
[[package]] [[package]]
category = "main"
description = "Extensions to the standard Python datetime module"
name = "python-dateutil" name = "python-dateutil"
version = "2.8.1"
description = "Extensions to the standard Python datetime module"
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
version = "2.8.1"
[package.dependencies] [package.dependencies]
six = ">=1.5" six = ">=1.5"
[[package]] [[package]]
category = "main"
description = "World timezone definitions, modern and historical"
name = "pytz" name = "pytz"
optional = false
python-versions = "*"
version = "2020.1" version = "2020.1"
description = "World timezone definitions, modern and historical"
[[package]] category = "main"
category = "dev" optional = false
description = "Alternative regular expression module, to replace re." python-versions = "*"
name = "regex"
[[package]]
name = "regex"
version = "2020.7.14"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.7.14"
[[package]] [[package]]
category = "main"
description = "Python HTTP for Humans."
name = "requests" name = "requests"
version = "2.24.0"
description = "Python HTTP for Humans."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.24.0"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
@ -344,75 +344,75 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
[package.extras] [package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]] [[package]]
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six" name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.15.0"
[[package]] [[package]]
category = "dev"
description = "tasks runner for python projects"
name = "taskipy" name = "taskipy"
version = "1.2.1"
description = "tasks runner for python projects"
category = "dev"
optional = false optional = false
python-versions = ">=3.6,<4.0" python-versions = ">=3.6,<4.0"
version = "1.2.1"
[package.dependencies] [package.dependencies]
toml = ">=0.10.0,<0.11.0" toml = ">=0.10.0,<0.11.0"
[[package]] [[package]]
category = "dev"
description = "Python Library for Tom's Obvious, Minimal Language"
name = "toml" name = "toml"
optional = false
python-versions = "*"
version = "0.10.1" version = "0.10.1"
description = "Python Library for Tom's Obvious, Minimal Language"
[[package]] category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "typed-ast"
version = "1.4.1"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev" category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
name = "typed-ast"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "1.4.1"
[[package]] [[package]]
category = "main"
description = "HTTP library with thread-safe connection pooling, file post, and more."
name = "urllib3" name = "urllib3"
version = "1.25.10"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "1.25.9"
[package.extras] [package.extras]
brotli = ["brotlipy (>=0.6.0)"] brotli = ["brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
category = "main"
description = "Waitress WSGI server"
name = "waitress" name = "waitress"
version = "1.4.4"
description = "Waitress WSGI server"
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
version = "1.4.4"
[package.extras] [package.extras]
docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"] docs = ["Sphinx (>=1.8.1)", "docutils", "pylons-sphinx-themes (>=1.0.9)"]
testing = ["pytest", "pytest-cover", "coverage (>=5.0)"] testing = ["pytest", "pytest-cover", "coverage (>=5.0)"]
[[package]] [[package]]
category = "main"
description = "Filesystem events monitoring"
name = "watchdog" name = "watchdog"
version = "0.10.3"
description = "Filesystem events monitoring"
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.10.3"
[package.dependencies] [package.dependencies]
pathtools = ">=0.1.1" pathtools = ">=0.1.1"
@ -421,20 +421,21 @@ pathtools = ">=0.1.1"
watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"] watchmedo = ["PyYAML (>=3.10)", "argh (>=0.24.1)"]
[[package]] [[package]]
category = "main"
description = "The comprehensive WSGI web application library."
name = "werkzeug" name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "1.0.1"
[package.extras] [package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"] dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"] watchdog = ["watchdog"]
[metadata] [metadata]
content-hash = "1c2741c9be187d9d0be662509fb4a87f5978e5f44420e5049a20504824c29a59" lock-version = "1.1"
python-versions = "^3.6" python-versions = "^3.6"
content-hash = "1c2741c9be187d9d0be662509fb4a87f5978e5f44420e5049a20504824c29a59"
[metadata.files] [metadata.files]
appdirs = [ appdirs = [
@ -454,34 +455,34 @@ certifi = [
{file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"},
] ]
cffi = [ cffi = [
{file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, {file = "cffi-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:66dd45eb9530e3dde8f7c009f84568bc7cac489b93d04ac86e3111fb46e470c2"},
{file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, {file = "cffi-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:4f53e4128c81ca3212ff4cf097c797ab44646a40b42ec02a891155cd7a2ba4d8"},
{file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, {file = "cffi-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:833401b15de1bb92791d7b6fb353d4af60dc688eaa521bd97203dcd2d124a7c1"},
{file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, {file = "cffi-1.14.1-cp27-cp27m-win32.whl", hash = "sha256:26f33e8f6a70c255767e3c3f957ccafc7f1f706b966e110b855bfe944511f1f9"},
{file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, {file = "cffi-1.14.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b87dfa9f10a470eee7f24234a37d1d5f51e5f5fa9eeffda7c282e2b8f5162eb1"},
{file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, {file = "cffi-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:effd2ba52cee4ceff1a77f20d2a9f9bf8d50353c854a282b8760ac15b9833168"},
{file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, {file = "cffi-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bac0d6f7728a9cc3c1e06d4fcbac12aaa70e9379b3025b27ec1226f0e2d404cf"},
{file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, {file = "cffi-1.14.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d6033b4ffa34ef70f0b8086fd4c3df4bf801fee485a8a7d4519399818351aa8e"},
{file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, {file = "cffi-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:8416ed88ddc057bab0526d4e4e9f3660f614ac2394b5e019a628cdfff3733849"},
{file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, {file = "cffi-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:892daa86384994fdf4856cb43c93f40cbe80f7f95bb5da94971b39c7f54b3a9c"},
{file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, {file = "cffi-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:c991112622baee0ae4d55c008380c32ecfd0ad417bcd0417ba432e6ba7328caa"},
{file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, {file = "cffi-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fcf32bf76dc25e30ed793145a57426064520890d7c02866eb93d3e4abe516948"},
{file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, {file = "cffi-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f960375e9823ae6a07072ff7f8a85954e5a6434f97869f50d0e41649a1c8144f"},
{file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, {file = "cffi-1.14.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a6d28e7f14ecf3b2ad67c4f106841218c8ab12a0683b1528534a6c87d2307af3"},
{file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, {file = "cffi-1.14.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cda422d54ee7905bfc53ee6915ab68fe7b230cacf581110df4272ee10462aadc"},
{file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, {file = "cffi-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:4a03416915b82b81af5502459a8a9dd62a3c299b295dcdf470877cb948d655f2"},
{file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, {file = "cffi-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:4ce1e995aeecf7cc32380bc11598bfdfa017d592259d5da00fc7ded11e61d022"},
{file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, {file = "cffi-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e23cb7f1d8e0f93addf0cae3c5b6f00324cccb4a7949ee558d7b6ca973ab8ae9"},
{file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, {file = "cffi-1.14.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ddff0b2bd7edcc8c82d1adde6dbbf5e60d57ce985402541cd2985c27f7bec2a0"},
{file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, {file = "cffi-1.14.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:f90c2267101010de42f7273c94a1f026e56cbc043f9330acd8a80e64300aba33"},
{file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, {file = "cffi-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:3cd2c044517f38d1b577f05927fb9729d3396f1d44d0c659a445599e79519792"},
{file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, {file = "cffi-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fa72a52a906425416f41738728268072d5acfd48cbe7796af07a923236bcf96"},
{file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, {file = "cffi-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:267adcf6e68d77ba154334a3e4fc921b8e63cbb38ca00d33d40655d4228502bc"},
{file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, {file = "cffi-1.14.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:d3148b6ba3923c5850ea197a91a42683f946dba7e8eb82dfa211ab7e708de939"},
{file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, {file = "cffi-1.14.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:98be759efdb5e5fa161e46d404f4e0ce388e72fbf7d9baf010aff16689e22abe"},
{file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, {file = "cffi-1.14.1-cp38-cp38-win32.whl", hash = "sha256:6923d077d9ae9e8bacbdb1c07ae78405a9306c8fd1af13bfa06ca891095eb995"},
{file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, {file = "cffi-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:b1d6ebc891607e71fd9da71688fcf332a6630b7f5b7f5549e6e631821c0e5d90"},
{file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, {file = "cffi-1.14.1.tar.gz", hash = "sha256:b2a2b0d276a136146e012154baefaea2758ef1f56ae9f4e01c612b0831e0bd2f"},
] ]
chardet = [ chardet = [
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
@ -492,25 +493,28 @@ click = [
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
] ]
cryptography = [ cryptography = [
{file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, {file = "cryptography-3.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:9a8580c9afcdcddabbd064c0a74f337af74ff4529cdf3a12fa2e9782d677a2e5"},
{file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"}, {file = "cryptography-3.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ef41304bf978f33cfb6f43ca13bb0faac0c99cda33693aa20ad4f5e34e8cb8f"},
{file = "cryptography-2.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365"}, {file = "cryptography-3.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:87c2fffd61e934bc0e2c927c3764c20b22d7f5f7f812ee1a477de4c89b044ca6"},
{file = "cryptography-2.9.2-cp27-cp27m-win32.whl", hash = "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"}, {file = "cryptography-3.2-cp27-cp27m-win32.whl", hash = "sha256:6e8a3c7c45101a7eeee93102500e1b08f2307c717ff553fcb3c1127efc9b6917"},
{file = "cryptography-2.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55"}, {file = "cryptography-3.2-cp27-cp27m-win_amd64.whl", hash = "sha256:52a47e60953679eea0b4d490ca3c241fb1b166a7b161847ef4667dfd49e7699d"},
{file = "cryptography-2.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270"}, {file = "cryptography-3.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:6a8f64ed096d13f92d1f601a92d9fd1f1025dc73a2ca1ced46dcf5e0d4930943"},
{file = "cryptography-2.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf"}, {file = "cryptography-3.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3e17d02941c0f169c5b877597ca8be895fca0e5e3eb882526a74aa4804380a98"},
{file = "cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d"}, {file = "cryptography-3.2-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:d1cbc3426e6150583b22b517ef3720036d7e3152d428c864ff0f3fcad2b97591"},
{file = "cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785"}, {file = "cryptography-3.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:22f8251f68953553af4f9c11ec5f191198bc96cff9f0ac5dd5ff94daede0ee6d"},
{file = "cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b"}, {file = "cryptography-3.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:f2aa3f8ba9e2e3fd49bd3de743b976ab192fbf0eb0348cebde5d2a9de0090a9f"},
{file = "cryptography-2.9.2-cp35-cp35m-win32.whl", hash = "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae"}, {file = "cryptography-3.2-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:9a07e6d255053674506091d63ab4270a119e9fc83462c7ab1dbcb495b76307af"},
{file = "cryptography-2.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b"}, {file = "cryptography-3.2-cp35-cp35m-win32.whl", hash = "sha256:f0e3986f6cce007216b23c490f093f35ce2068f3c244051e559f647f6731b7ae"},
{file = "cryptography-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6"}, {file = "cryptography-3.2-cp35-cp35m-win_amd64.whl", hash = "sha256:284e275e3c099a80831f9898fb5c9559120d27675c3521278faba54e584a7832"},
{file = "cryptography-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3"}, {file = "cryptography-3.2-cp36-abi3-win32.whl", hash = "sha256:f01c9116bfb3ad2831e125a73dcd957d173d6ddca7701528eff1e7d97972872c"},
{file = "cryptography-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b"}, {file = "cryptography-3.2-cp36-abi3-win_amd64.whl", hash = "sha256:bd80bc156d3729b38cb227a5a76532aef693b7ac9e395eea8063ee50ceed46a5"},
{file = "cryptography-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e"}, {file = "cryptography-3.2-cp36-cp36m-win32.whl", hash = "sha256:8f0fd8b0751d75c4483c534b209e39e918f0d14232c0d8a2a76e687f64ced831"},
{file = "cryptography-2.9.2-cp38-cp38-win32.whl", hash = "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0"}, {file = "cryptography-3.2-cp36-cp36m-win_amd64.whl", hash = "sha256:8a0866891326d3badb17c5fd3e02c926b635e8923fa271b4813cd4d972a57ff3"},
{file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, {file = "cryptography-3.2-cp37-cp37m-win32.whl", hash = "sha256:57b8c1ed13b8aa386cabbfde3be175d7b155682470b0e259fecfe53850967f8a"},
{file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, {file = "cryptography-3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:88069392cd9a1e68d2cfd5c3a2b0d72a44ef3b24b8977a4f7956e9e3c4c9477a"},
{file = "cryptography-3.2-cp38-cp38-win32.whl", hash = "sha256:fb70a4cedd69dc52396ee114416a3656e011fb0311fca55eb55c7be6ed9c8aef"},
{file = "cryptography-3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e15ac84dcdb89f92424cbaca4b0b34e211e7ce3ee7b0ec0e4f3c55cee65fae5a"},
{file = "cryptography-3.2.tar.gz", hash = "sha256:e4789b84f8dedf190148441f7c5bfe7244782d9cbb194a36e17b91e7d3e1cca9"},
] ]
flask = [ flask = [
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
@ -735,11 +739,17 @@ typed-ast = [
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"},
{file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"},
{file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"},
{file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"},
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
] ]
urllib3 = [ urllib3 = [
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
{file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
] ]
waitress = [ waitress = [
{file = "waitress-1.4.4-py2.py3-none-any.whl", hash = "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db"}, {file = "waitress-1.4.4-py2.py3-none-any.whl", hash = "sha256:3d633e78149eb83b60a07dfabb35579c29aac2d24bb803c18b26fb2ab1a584db"},

View File

@ -2,14 +2,40 @@
import sass import sass
import subprocess import subprocess
import shutil import shutil
import os
import argparse
from pathlib import Path from pathlib import Path
parser = argparse.ArgumentParser()
parser.add_argument(
"-y", "--yes", help="use assumed node bin directory.", action="store_true"
)
def runcmd(cmd): def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
return proc.communicate() return proc.communicate()
local_path = Path(__file__).resolve().parent local_path = Path(__file__).resolve().parent
node_bin = local_path.parent / 'node_modules' / '.bin' out = runcmd("npm bin")
try:
node_bin = Path(out[0].decode('utf-8').rstrip())
except:
node_bin = Path(out.decode('utf-8').rstrip())
args = parser.parse_args()
if not args.yes:
print(f"assuming npm bin directory \"{node_bin}\". Is this correct?")
if input("[yY/nN]: ").lower() == "n":
node_bin = local_path.parent / 'node_modules' / '.bin'
print(f"this? \"{node_bin}\"")
if input("[yY/nN]: ").lower() == "n":
node_bin = input("input bin directory: ")
for bsv in [d for d in local_path.iterdir() if 'bs' in d.name]: for bsv in [d for d in local_path.iterdir() if 'bs' in d.name]:
scss = bsv / f'{bsv.name}-jf.scss' scss = bsv / f'{bsv.name}-jf.scss'
@ -21,7 +47,11 @@ for bsv in [d for d in local_path.iterdir() if 'bs' in d.name]:
precision=6)) precision=6))
if css.exists(): if css.exists():
print(f'{bsv.name}: Compiled.') print(f'{bsv.name}: Compiled.')
runcmd(f'{str((node_bin / "postcss").resolve())} {str(css.resolve())} --replace --use autoprefixer') # postcss only excepts forwards slashes? weird.
cssPath = str(css.resolve())
if os.name == 'nt':
cssPath = cssPath.replace('\\', '/')
runcmd(f'{str((node_bin / "postcss").resolve())} {cssPath} --replace --use autoprefixer')
print(f'{bsv.name}: Prefixed.') print(f'{bsv.name}: Prefixed.')
runcmd(f'{str((node_bin / "cleancss").resolve())} --level 1 --format breakWith=lf --output {str(min_css.resolve())} {str(css.resolve())}') runcmd(f'{str((node_bin / "cleancss").resolve())} --level 1 --format breakWith=lf --output {str(min_css.resolve())} {str(css.resolve())}')
if min_css.exists(): if min_css.exists():

View File

@ -1,17 +1,25 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import subprocess import subprocess
import os
from pathlib import Path from pathlib import Path
def runcmd(cmd): def runcmd(cmd):
if os.name == "nt":
return subprocess.check_output(cmd, shell=True)
proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
return proc.communicate() return proc.communicate()
print('Installing npm packages') print('Installing npm packages')
root_path = Path(__file__).parents[1] root_path = Path(__file__).parents[1]
runcmd(f'npm install --prefix {root_path}') if os.name == 'nt':
root_path /= 'node_modules'
runcmd(f'npm install')
if (root_path / 'node_modules' / 'cleancss').exists(): if (root_path / 'node_modules' / 'cleancss').exists():
print(f'Installed successfully in {str((root_path / "node_modules").resolve())}.') print(f'Installed successfully in {str((root_path / "node_modules").resolve())}.')