This commit is contained in:
Michał 2023-08-04 17:34:08 +00:00
parent 4c7bf9706f
commit d19a33501a
36 changed files with 808 additions and 1052 deletions

View file

@ -1,140 +0,0 @@
"""
Onlylegs Gallery
This is the main app file, it loads all the other files and sets up the app
"""
import os
import logging
from flask_assets import Bundle
from flask_migrate import init as migrate_init
from flask import Flask, render_template, abort
from werkzeug.exceptions import HTTPException
from werkzeug.security import generate_password_hash
from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache
from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR
from onlylegs.models import User
from onlylegs.views import (
index as view_index,
image as view_image,
group as view_group,
settings as view_settings,
profile as view_profile,
)
from onlylegs.api import media as api_media, group as api_group, account as api_account
from onlylegs import auth as view_auth
from onlylegs import filters
from onlylegs import gwagwa
def create_app(): # pylint: disable=R0914
"""
Create and configure the main app
"""
app = Flask(__name__, instance_path=INSTANCE_DIR)
app.config.from_pyfile("config.py")
# DATABASE
db.init_app(app)
migrate.init_app(app, db, directory=MIGRATIONS_DIR)
# If database file doesn't exist, create it
if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")):
print("Creating database")
with app.app_context():
db.create_all()
register_user = User(
username=app.config["ADMIN_CONF"]["username"],
email=app.config["ADMIN_CONF"]["email"],
password=generate_password_hash("changeme!", method="sha256"),
)
db.session.add(register_user)
db.session.commit()
print(
"""
####################################################
# DEFAULT ADMIN USER GENERATED WITH GIVEN USERNAME #
# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, #
# PLEASE UPDATE IT IN THE SETTINGS! #
####################################################
"""
)
# Check if migrations directory exists, if not create it
with app.app_context():
if not os.path.exists(MIGRATIONS_DIR):
print("Creating migrations directory")
migrate_init(directory=MIGRATIONS_DIR)
# LOGIN MANAGER
# can also set session_protection to "strong"
# this would protect against session hijacking
login_manager.init_app(app)
login_manager.login_view = "onlylegs.index"
@login_manager.user_loader
def load_user(user_id):
return User.query.filter_by(alt_id=user_id).first()
@login_manager.unauthorized_handler
def unauthorized():
error = 401
msg = "You are not authorized to view this page!!!!"
return render_template("error.html", error=error, msg=msg), error
# ERROR HANDLERS
@app.errorhandler(Exception)
def error_page(err):
"""
Error handlers, if the error is not a HTTP error, return 500
"""
if not isinstance(err, HTTPException):
abort(500)
return (
render_template("error.html", error=err.code, msg=err.description),
err.code,
)
# ASSETS
assets.init_app(app)
scripts = Bundle(
"js/*.js", output="gen/js.js", depends="js/*.js"
) # filter jsmin is broken :c
styles = Bundle(
"sass/style.sass",
filters="libsass, cssmin",
output="gen/styles.css",
depends="sass/**/*.sass",
)
assets.register("scripts", scripts)
assets.register("styles", styles)
# BLUEPRINTS
app.register_blueprint(view_auth.blueprint)
app.register_blueprint(view_index.blueprint)
app.register_blueprint(view_image.blueprint)
app.register_blueprint(view_group.blueprint)
app.register_blueprint(view_profile.blueprint)
app.register_blueprint(view_settings.blueprint)
# APIS
app.register_blueprint(api_media.blueprint)
app.register_blueprint(api_group.blueprint)
app.register_blueprint(api_account.blueprint)
# FILTERS
app.register_blueprint(filters.blueprint)
# CACHE AND COMPRESS
cache.init_app(app)
compress.init_app(app)
# Yupee! We got there :3
print("Done!")
logging.info("Gallery started successfully!")
return app

293
onlylegs/api.py Normal file
View file

@ -0,0 +1,293 @@
"""
Onlylegs - API endpoints
"""
import os
import pathlib
import re
import logging
from uuid import uuid4
from flask import (
Blueprint,
flash,
abort,
send_from_directory,
jsonify,
request,
current_app,
)
from flask_login import login_required, current_user
from colorthief import ColorThief
from onlylegs.extensions import db
from onlylegs.models import Users, Pictures, Albums, AlbumJunction
from onlylegs.utils.metadata import yoink
from onlylegs.utils.generate_image import generate_thumbnail
blueprint = Blueprint("api", __name__, url_prefix="/api")
@blueprint.route("/account/picture/<int:user_id>", methods=["POST"])
@login_required
def account_picture(user_id):
"""
Returns the profile of a user
"""
user = db.get_or_404(Users, user_id)
file = request.files.get("file", None)
# If no image is uploaded, return 404 error
if not file:
return jsonify({"error": "No file uploaded"}), 400
if user.id != current_user.id:
return jsonify({"error": "You are not allowed to do this, go away"}), 403
# Get file extension, generate random name and set file path
img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower()
img_name = str(user.id)
img_path = os.path.join(current_app.config["PFP_FOLDER"], img_name + "." + img_ext)
# Check if file extension is allowed
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
logging.info("File extension not allowed: %s", img_ext)
return jsonify({"error": "File extension not allowed"}), 403
if user.picture:
# Delete cached files and old image
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture))
cache_name = user.picture.rsplit(".")[0]
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
cache_name + "*"
):
os.remove(cache_file)
# Save file
try:
file.save(img_path)
except OSError as err:
logging.info("Error saving file %s because of %s", img_path, err)
return jsonify({"error": "Error saving file"}), 500
img_colors = ColorThief(img_path).get_color()
# Save to database
user.colour = img_colors
user.picture = str(img_name + "." + img_ext)
db.session.commit()
return jsonify({"message": "File uploaded"}), 200
@blueprint.route("/account/username/<int:user_id>", methods=["POST"])
@login_required
def account_username(user_id):
"""
Returns the profile of a user
"""
user = db.get_or_404(Users, user_id)
new_name = request.form["name"]
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
# Validate the form
if not new_name or not username_regex.match(new_name):
return jsonify({"error": "Username is invalid"}), 400
if user.id != current_user.id:
return jsonify({"error": "You are not allowed to do this, go away"}), 403
# Save to database
user.username = new_name
db.session.commit()
return jsonify({"message": "Username changed"}), 200
@blueprint.route("/media/<path:path>", methods=["GET"])
def media(path):
"""
Returns image from media folder
r for resolution, thumb for thumbnail etc
e for extension, jpg, png etc
"""
res = request.args.get("r", default=None).strip()
ext = request.args.get("e", default=None).strip()
# if no args are passed, return the raw file
if not res and not ext:
if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)):
abort(404)
return send_from_directory(current_app.config["MEDIA_FOLDER"], path)
# Generate thumbnail, if None is returned a server error occured
thumb = generate_thumbnail(path, res, ext)
if not thumb:
abort(500)
response = send_from_directory(os.path.dirname(thumb), os.path.basename(thumb))
response.headers["Cache-Control"] = "public, max-age=31536000"
response.headers["Expires"] = "31536000"
return response
@blueprint.route("/media/upload", methods=["POST"])
@login_required
def upload():
"""
Uploads an image to the server and saves it to the database
"""
form_file = request.files.get("file", None)
form = request.form
if not form_file:
return jsonify({"message": "No file"}), 400
# Get file extension, generate random name and set file path
img_ext = pathlib.Path(form_file.filename).suffix.replace(".", "").lower()
img_name = "GWAGWA_" + str(uuid4())
img_path = os.path.join(
current_app.config["UPLOAD_FOLDER"], img_name + "." + img_ext
)
# Check if file extension is allowed
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
logging.info("File extension not allowed: %s", img_ext)
return jsonify({"message": "File extension not allowed"}), 403
# Save file
try:
form_file.save(img_path)
except OSError as err:
logging.info("Error saving file %s because of %s", img_path, err)
return jsonify({"message": "Error saving file"}), 500
img_exif = yoink(img_path) # Get EXIF data
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
# Save to database
query = Pictures(
author_id=current_user.id,
filename=img_name + "." + img_ext,
mimetype=img_ext,
exif=img_exif,
colours=img_colors,
description=form["description"],
alt=form["alt"],
)
db.session.add(query)
db.session.commit()
return jsonify({"message": "File uploaded"}), 200
@blueprint.route("/media/delete/<int:image_id>", methods=["POST"])
@login_required
def delete_image(image_id):
"""
Deletes an image from the server and database
"""
post = db.get_or_404(Pictures, image_id)
# Check if image exists and if user is allowed to delete it (author)
if post.author_id != current_user.id:
logging.info("User %s tried to delete image %s", current_user.id, image_id)
return (
jsonify({"message": "You are not allowed to delete this image, heck off"}),
403,
)
# Delete file
try:
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
except FileNotFoundError:
logging.warning(
"File not found: %s, already deleted or never existed", post.filename
)
# Delete cached files
cache_name = post.filename.rsplit(".")[0]
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
cache_name + "*"
):
os.remove(cache_file)
AlbumJunction.query.filter_by(picture_id=image_id).delete()
db.session.delete(post)
db.session.commit()
logging.info("Removed image (%s) %s", image_id, post.filename)
flash(["Image was all in Le Head!", "1"])
return jsonify({"message": "Image deleted"}), 200
@blueprint.route("/group/create", methods=["POST"])
@login_required
def create_group():
"""
Creates a group
"""
group_name = request.form.get("name", "").strip()
group_description = request.form.get("description", "").strip()
group_author = current_user.id
new_group = Albums(
name=group_name,
description=group_description,
author_id=group_author,
)
db.session.add(new_group)
db.session.commit()
return jsonify({"message": "Group created", "id": new_group.id})
@blueprint.route("/group/modify", methods=["POST"])
@login_required
def modify_group():
"""
Changes the images in a group
"""
group_id = request.form.get("group", "").strip()
image_id = request.form.get("image", "").strip()
action = request.form.get("action", "").strip()
group = db.get_or_404(Albums, group_id)
db.get_or_404(Pictures, image_id) # Check if image exists
if group.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
junction_exist = AlbumJunction.query.filter_by(
album_id=group_id, picture_id=image_id
).first()
if action == "add" and not junction_exist:
db.session.add(AlbumJunction(album_id=group_id, picture_id=image_id))
elif request.form["action"] == "remove":
AlbumJunction.query.filter_by(album_id=group_id, picture_id=image_id).delete()
db.session.commit()
return jsonify({"message": "Group modified"})
@blueprint.route("/group/delete", methods=["POST"])
def delete_group():
"""
Deletes a group
"""
group_id = request.form.get("group", "").strip()
group = db.get_or_404(Albums, group_id)
if group.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
AlbumJunction.query.filter_by(album_id=group_id).delete()
db.session.delete(group)
db.session.commit()
flash(["Group yeeted!", "1"])
return jsonify({"message": "Group deleted"})

View file

@ -1,93 +0,0 @@
"""
Onlylegs - API endpoints
"""
import os
import pathlib
import re
import logging
from flask import Blueprint, jsonify, request, current_app
from flask_login import login_required, current_user
from colorthief import ColorThief
from onlylegs.extensions import db
from onlylegs.models import User
blueprint = Blueprint("account_api", __name__, url_prefix="/api/account")
@blueprint.route("/picture/<int:user_id>", methods=["POST"])
@login_required
def account_picture(user_id):
"""
Returns the profile of a user
"""
user = db.get_or_404(User, user_id)
file = request.files["file"]
# If no image is uploaded, return 404 error
if not file:
return jsonify({"error": "No file uploaded"}), 400
if user.id != current_user.id:
return jsonify({"error": "You are not allowed to do this, go away"}), 403
# Get file extension, generate random name and set file path
img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower()
img_name = str(user.id)
img_path = os.path.join(current_app.config["PFP_FOLDER"], img_name + "." + img_ext)
# Check if file extension is allowed
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
logging.info("File extension not allowed: %s", img_ext)
return jsonify({"error": "File extension not allowed"}), 403
if user.picture:
# Delete cached files and old image
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture))
cache_name = user.picture.rsplit(".")[0]
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
cache_name + "*"
):
os.remove(cache_file)
# Save file
try:
file.save(img_path)
except OSError as err:
logging.info("Error saving file %s because of %s", img_path, err)
return jsonify({"error": "Error saving file"}), 500
img_colors = ColorThief(img_path).get_color()
# Save to database
user.colour = img_colors
user.picture = str(img_name + "." + img_ext)
db.session.commit()
return jsonify({"message": "File uploaded"}), 200
@blueprint.route("/username/<int:user_id>", methods=["POST"])
@login_required
def account_username(user_id):
"""
Returns the profile of a user
"""
user = db.get_or_404(User, user_id)
new_name = request.form["name"]
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
# Validate the form
if not new_name or not username_regex.match(new_name):
return jsonify({"error": "Username is invalid"}), 400
if user.id != current_user.id:
return jsonify({"error": "You are not allowed to do this, go away"}), 403
# Save to database
user.username = new_name
db.session.commit()
return jsonify({"message": "Username changed"}), 200

View file

@ -1,78 +0,0 @@
"""
Onlylegs - API endpoints
"""
from flask import Blueprint, flash, jsonify, request
from flask_login import login_required, current_user
from onlylegs.extensions import db
from onlylegs.models import Post, Group, GroupJunction
blueprint = Blueprint("group_api", __name__, url_prefix="/api/group")
@blueprint.route("/create", methods=["POST"])
@login_required
def create_group():
"""
Creates a group
"""
new_group = Group(
name=request.form["name"],
description=request.form["description"],
author_id=current_user.id,
)
db.session.add(new_group)
db.session.commit()
return jsonify({"message": "Group created", "id": new_group.id})
@blueprint.route("/modify", methods=["POST"])
@login_required
def modify_group():
"""
Changes the images in a group
"""
group_id = request.form["group"]
image_id = request.form["image"]
action = request.form["action"]
group = db.get_or_404(Group, group_id)
db.get_or_404(Post, image_id) # Check if image exists
if group.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
if (
action == "add"
and not GroupJunction.query.filter_by(
group_id=group_id, post_id=image_id
).first()
):
db.session.add(GroupJunction(group_id=group_id, post_id=image_id))
elif request.form["action"] == "remove":
GroupJunction.query.filter_by(group_id=group_id, post_id=image_id).delete()
db.session.commit()
return jsonify({"message": "Group modified"})
@blueprint.route("/delete", methods=["POST"])
def delete_group():
"""
Deletes a group
"""
group_id = request.form["group"]
group = db.get_or_404(Group, group_id)
if group.author_id != current_user.id:
return jsonify({"message": "You are not the owner of this group"}), 403
GroupJunction.query.filter_by(group_id=group_id).delete()
db.session.delete(group)
db.session.commit()
flash(["Group yeeted!", "1"])
return jsonify({"message": "Group deleted"})

View file

@ -1,148 +0,0 @@
"""
Onlylegs - API endpoints
Media upload and retrieval
"""
from uuid import uuid4
import os
import pathlib
import logging
from flask import (
Blueprint,
flash,
abort,
send_from_directory,
jsonify,
request,
current_app,
)
from flask_login import login_required, current_user
from colorthief import ColorThief
from onlylegs.extensions import db
from onlylegs.models import Post, GroupJunction
from onlylegs.utils.metadata import yoink
from onlylegs.utils.generate_image import generate_thumbnail
blueprint = Blueprint("media_api", __name__, url_prefix="/api/media")
@blueprint.route("/<path:path>", methods=["GET"])
def media(path):
"""
Returns image from media folder
r for resolution, thumb for thumbnail etc
e for extension, jpg, png etc
"""
res = request.args.get("r", default=None, type=str)
ext = request.args.get("e", default=None, type=str)
# if no args are passed, return the raw file
if not res and not ext:
if not os.path.exists(os.path.join(current_app.config["MEDIA_FOLDER"], path)):
abort(404)
return send_from_directory(current_app.config["MEDIA_FOLDER"], path)
# Generate thumbnail, if None is returned a server error occured
thumb = generate_thumbnail(path, res, ext)
if not thumb:
abort(500)
response = send_from_directory(os.path.dirname(thumb), os.path.basename(thumb))
response.headers["Cache-Control"] = "public, max-age=31536000"
response.headers["Expires"] = "31536000"
return response
@blueprint.route("/upload", methods=["POST"])
@login_required
def upload():
"""
Uploads an image to the server and saves it to the database
"""
form_file = request.files["file"]
form = request.form
if not form_file:
return jsonify({"message": "No file"}), 400
# Get file extension, generate random name and set file path
img_ext = pathlib.Path(form_file.filename).suffix.replace(".", "").lower()
img_name = "GWAGWA_" + str(uuid4())
img_path = os.path.join(
current_app.config["UPLOAD_FOLDER"], img_name + "." + img_ext
)
# Check if file extension is allowed
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
logging.info("File extension not allowed: %s", img_ext)
return jsonify({"message": "File extension not allowed"}), 403
# Save file
try:
form_file.save(img_path)
except OSError as err:
logging.info("Error saving file %s because of %s", img_path, err)
return jsonify({"message": "Error saving file"}), 500
img_exif = yoink(img_path) # Get EXIF data
img_colors = ColorThief(img_path).get_palette(color_count=3) # Get color palette
# Save to database
query = Post(
author_id=current_user.id,
filename=img_name + "." + img_ext,
mimetype=img_ext,
exif=img_exif,
colours=img_colors,
description=form["description"],
alt=form["alt"],
)
db.session.add(query)
db.session.commit()
return jsonify({"message": "File uploaded"}), 200
@blueprint.route("/delete/<int:image_id>", methods=["POST"])
@login_required
def delete_image(image_id):
"""
Deletes an image from the server and database
"""
post = db.get_or_404(Post, image_id)
# Check if image exists and if user is allowed to delete it (author)
if post.author_id != current_user.id:
logging.info("User %s tried to delete image %s", current_user.id, image_id)
return (
jsonify({"message": "You are not allowed to delete this image, heck off"}),
403,
)
# Delete file
try:
os.remove(os.path.join(current_app.config["UPLOAD_FOLDER"], post.filename))
except FileNotFoundError:
logging.warning(
"File not found: %s, already deleted or never existed", post.filename
)
# Delete cached files
cache_name = post.filename.rsplit(".")[0]
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
cache_name + "*"
):
os.remove(cache_file)
GroupJunction.query.filter_by(post_id=image_id).delete()
db.session.delete(post)
db.session.commit()
logging.info("Removed image (%s) %s", image_id, post.filename)
flash(["Image was all in Le Head!", "1"])
return jsonify({"message": "Image deleted"}), 200

134
onlylegs/app.py Normal file
View file

@ -0,0 +1,134 @@
"""
Onlylegs Gallery
This is the main app file, it loads all the other files and sets up the app
"""
import os
import logging
from flask_assets import Bundle
from flask_migrate import init as migrate_init
from flask import Flask, render_template, abort
from werkzeug.exceptions import HTTPException
from werkzeug.security import generate_password_hash
from onlylegs.extensions import db, migrate, login_manager, assets, compress, cache
from onlylegs.config import INSTANCE_DIR, MIGRATIONS_DIR
from onlylegs.models import Users
from onlylegs.views import (
index as view_index,
image as view_image,
group as view_group,
settings as view_settings,
profile as view_profile,
)
from onlylegs import api
from onlylegs import auth as view_auth
from onlylegs import filters
app = Flask(__name__, instance_path=INSTANCE_DIR)
app.config.from_pyfile("config.py")
# DATABASE
db.init_app(app)
migrate.init_app(app, db, directory=MIGRATIONS_DIR)
# If database file doesn't exist, create it
if not os.path.exists(os.path.join(INSTANCE_DIR, "gallery.sqlite3")):
print("Creating database")
with app.app_context():
db.create_all()
register_user = Users(
username=app.config["ADMIN_CONF"]["username"],
email=app.config["ADMIN_CONF"]["email"],
password=generate_password_hash("changeme!", method="sha256"),
)
db.session.add(register_user)
db.session.commit()
print(
"""
####################################################
# DEFAULT ADMIN USER GENERATED WITH GIVEN USERNAME #
# THE DEFAULT PASSWORD "changeme!" HAS BEEN USED, #
# PLEASE UPDATE IT IN THE SETTINGS! #
####################################################
"""
)
# Check if migrations directory exists, if not create it
with app.app_context():
if not os.path.exists(MIGRATIONS_DIR):
print("Creating migrations directory")
migrate_init(directory=MIGRATIONS_DIR)
# LOGIN MANAGER
# can also set session_protection to "strong"
# this would protect against session hijacking
login_manager.init_app(app)
login_manager.login_view = "onlylegs.index"
@login_manager.user_loader
def load_user(user_id):
return Users.query.filter_by(alt_id=user_id).first()
@login_manager.unauthorized_handler
def unauthorized():
error = 401
msg = "You are not authorized to view this page!!!!"
return render_template("error.html", error=error, msg=msg), error
# ERROR HANDLERS
@app.errorhandler(Exception)
def error_page(err):
"""
Error handlers, if the error is not a HTTP error, return 500
"""
if not isinstance(err, HTTPException):
abort(500)
return (
render_template("error.html", error=err.code, msg=err.description),
err.code,
)
# ASSETS
assets.init_app(app)
scripts = Bundle(
"js/*.js", output="gen/js.js", depends="js/*.js"
) # filter jsmin is broken :c
styles = Bundle(
"sass/style.sass",
filters="libsass, cssmin",
output="gen/styles.css",
depends="sass/**/*.sass",
)
assets.register("scripts", scripts)
assets.register("styles", styles)
# BLUEPRINTS
app.register_blueprint(view_auth.blueprint)
app.register_blueprint(view_index.blueprint)
app.register_blueprint(view_image.blueprint)
app.register_blueprint(view_group.blueprint)
app.register_blueprint(view_profile.blueprint)
app.register_blueprint(view_settings.blueprint)
app.register_blueprint(api.blueprint)
# FILTERS
app.register_blueprint(filters.blueprint)
# CACHE AND COMPRESS
cache.init_app(app)
compress.init_app(app)
# Yupee! We got there :3
print("Done!")
logging.info("Gallery started successfully!")

View file

@ -11,7 +11,7 @@ from werkzeug.security import check_password_hash, generate_password_hash
from flask_login import login_user, logout_user, login_required from flask_login import login_user, logout_user, login_required
from onlylegs.extensions import db from onlylegs.extensions import db
from onlylegs.models import User from onlylegs.models import Users
blueprint = Blueprint("auth", __name__, url_prefix="/auth") blueprint = Blueprint("auth", __name__, url_prefix="/auth")
@ -28,7 +28,7 @@ def login():
password = request.form["password"].strip() password = request.form["password"].strip()
remember = bool(request.form["remember-me"]) remember = bool(request.form["remember-me"])
user = User.query.filter_by(username=username).first() user = Users.query.filter_by(username=username).first()
if not user or not check_password_hash(user.password, password): if not user or not check_password_hash(user.password, password):
logging.error("Login attempt from %s", request.remote_addr) logging.error("Login attempt from %s", request.remote_addr)
@ -77,7 +77,7 @@ def register():
elif password_repeat != password: elif password_repeat != password:
error.append("Passwords do not match!") error.append("Passwords do not match!")
user_exists = User.query.filter_by(username=username).first() user_exists = Users.query.filter_by(username=username).first()
if user_exists: if user_exists:
error.append("User already exists!") error.append("User already exists!")
@ -86,7 +86,7 @@ def register():
print(error) print(error)
return jsonify(error), 400 return jsonify(error), 400
register_user = User( register_user = Users(
username=username, username=username,
email=email, email=email,
password=generate_password_hash(password, method="sha256"), password=generate_password_hash(password, method="sha256"),

View file

@ -13,4 +13,4 @@ migrate = Migrate()
login_manager = LoginManager() login_manager = LoginManager()
assets = Environment() assets = Environment()
compress = Compress() compress = Compress()
cache = Cache(config={'CACHE_TYPE': 'simple', "CACHE_DEFAULT_TIMEOUT": 300}) cache = Cache(config={"CACHE_TYPE": "simple", "CACHE_DEFAULT_TIMEOUT": 300})

View file

@ -3,7 +3,7 @@ OnlyLegs filters
Custom Jinja2 filters Custom Jinja2 filters
""" """
from flask import Blueprint from flask import Blueprint
from onlylegs.utils.colour import contrast from onlylegs.utils import colour as colour_utils
blueprint = Blueprint("filters", __name__) blueprint = Blueprint("filters", __name__)
@ -16,7 +16,5 @@ def colour_contrast(colour):
a css variable based on the contrast of text required to be readable a css variable based on the contrast of text required to be readable
"color: var(--fg-white);" or "color: var(--fg-black);" "color: var(--fg-white);" or "color: var(--fg-black);"
""" """
bright = "var(--fg-white)" colour_obj = colour_utils.Colour(colour)
dark = "var(--fg-black)" return "rgb(var(--fg-black));" if colour_obj.is_light() else "rgb(var(--fg-white));"
return "color: RGB(" + contrast(colour, dark, bright) + ");"

View file

@ -1,4 +0,0 @@
"""
Gwa Gwa!
"""
print("Gwa Gwa!")

View file

@ -1,4 +0,0 @@
{
"IMAGES_UPLOADED": "%s images uploaded!",
"DONT USE THIS": "variable:format(data), jinja2 doesnt use the same method as Django does, odd"
}

View file

@ -6,18 +6,18 @@ from flask_login import UserMixin
from onlylegs.extensions import db from onlylegs.extensions import db
class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103 class AlbumJunction(db.Model): # pylint: disable=too-few-public-methods, C0103
""" """
Junction table for posts and groups Junction table for picturess and albums
Joins with posts and groups Joins with picturess and albums
""" """
__tablename__ = "group_junction" __tablename__ = "album_junction"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey("group.id")) album_id = db.Column(db.Integer, db.ForeignKey("albums.id"))
post_id = db.Column(db.Integer, db.ForeignKey("post.id")) picture_id = db.Column(db.Integer, db.ForeignKey("pictures.id"))
date_added = db.Column( date_added = db.Column(
db.DateTime, db.DateTime,
@ -26,16 +26,15 @@ class GroupJunction(db.Model): # pylint: disable=too-few-public-methods, C0103
) )
class Post(db.Model): # pylint: disable=too-few-public-methods, C0103 class Pictures(db.Model): # pylint: disable=too-few-public-methods, C0103
""" """
Post table Pictures table
""" """
__tablename__ = "post" __tablename__ = "pictures"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
filename = db.Column(db.String, unique=True, nullable=False) filename = db.Column(db.String, unique=True, nullable=False)
mimetype = db.Column(db.String, nullable=False) mimetype = db.Column(db.String, nullable=False)
@ -51,37 +50,37 @@ class Post(db.Model): # pylint: disable=too-few-public-methods, C0103
server_default=db.func.now(), # pylint: disable=E1102 server_default=db.func.now(), # pylint: disable=E1102
) )
junction = db.relationship("GroupJunction", backref="posts") album_fk = db.relationship("AlbumJunction", backref="pictures")
class Group(db.Model): # pylint: disable=too-few-public-methods, C0103 class Albums(db.Model): # pylint: disable=too-few-public-methods, C0103
""" """
Group table albums table
""" """
__tablename__ = "group" __tablename__ = "albums"
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False) name = db.Column(db.String, nullable=False)
description = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey("user.id")) author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
created_at = db.Column( created_at = db.Column(
db.DateTime, db.DateTime,
nullable=False, nullable=False,
server_default=db.func.now(), # pylint: disable=E1102 server_default=db.func.now(), # pylint: disable=E1102
) )
junction = db.relationship("GroupJunction", backref="groups") album_fk = db.relationship("AlbumJunction", backref="albums")
class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103 class Users(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C0103
""" """
User table Users table
""" """
__tablename__ = "user" __tablename__ = "users"
# Gallery used information # Gallery used information
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -100,8 +99,8 @@ class User(db.Model, UserMixin): # pylint: disable=too-few-public-methods, C010
server_default=db.func.now(), # pylint: disable=E1102 server_default=db.func.now(), # pylint: disable=E1102
) )
posts = db.relationship("Post", backref="author") pictures_fk = db.relationship("Pictures", backref="author")
groups = db.relationship("Group", backref="author") albums_fk = db.relationship("Albums", backref="author")
def get_id(self): def get_id(self):
return str(self.alt_id) return str(self.alt_id)

Binary file not shown.

View file

@ -1,7 +0,0 @@
@font-face {
font-family: 'Rubik';
src: url('./Rubik.ttf') format('truetype');
font-style: normal;
font-display: block;
font-weight: 300 900;
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -1,18 +0,0 @@
{
"name": "OnlyLegs",
"short_name": "OnlyLegs",
"start_url": "/",
"display": "standalone",
"background_color": "#151515",
"theme_color": "#151515",
"description": "A gallery built for fast and simple image management!",
"icons": [
{
"src": "icon.png",
"sizes": "621x621",
"type": "image/png"
}
],
"splash_pages": null
}

View file

@ -23,9 +23,8 @@
* *
box-sizing: border-box box-sizing: border-box
font-family: $font
scrollbar-color: RGB($primary) transparent scrollbar-color: RGB($primary) transparent
font-family: $font
::-webkit-scrollbar ::-webkit-scrollbar
width: 0.5rem width: 0.5rem

View file

@ -1,37 +1,32 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>{{ config.WEBSITE_CONF.name }}</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ config.WEBSITE_CONF.name }}</title> <meta name="description" content="{{ config.WEBSITE_CONF.motto }}">
<meta name="author" content="{{ config.WEBSITE_CONF.author }}">
<meta name="description" content="{{ config.WEBSITE_CONF.motto }}"/> <meta property="og:title" content="{{ config.WEBSITE_CONF.name }}">
<meta name="author" content="{{ config.WEBSITE_CONF.author }}"/> <meta property="og:description" content="{{ config.WEBSITE_CONF.motto }}">
<meta property="og:type" content="website">
<meta property="og:title" content="{{ config.WEBSITE_CONF.name }}"/> <meta name="twitter:title" content="{{ config.WEBSITE_CONF.name }}">
<meta property="og:description" content="{{ config.WEBSITE_CONF.motto }}"/> <meta name="twitter:description" content="{{ config.WEBSITE_CONF.motto }}">
<meta property="og:type" content="website"/>
<meta name="twitter:title" content="{{ config.WEBSITE_CONF.name }}"/> <!-- Fonts -->
<meta name="twitter:description" content="{{ config.WEBSITE_CONF.motto }}"/> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap">
<!-- phosphor icons!!! --> <!-- phosphor icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script> <script src="https://unpkg.com/@phosphor-icons/web"></script>
<link <!-- Favicon -->
href="{{url_for('static', filename='logo-black.svg')}}" <link rel="icon" href="{{url_for('static', filename='icon.png')}}" type="image/png">
rel="icon"
type="image/svg+xml"
media="(prefers-color-scheme: light)"/>
<link
href="{{url_for('static', filename='logo-white.svg')}}"
rel="icon"
type="image/svg+xml"
media="(prefers-color-scheme: dark)"/>
<link rel="manifest" href="static/manifest.json"/>
<link rel="prefetch" href="{{url_for('static', filename='fonts/font.css')}}" type="stylesheet"/>
{% assets "scripts" %} <script type="text/javascript" src="{{ ASSET_URL }}"></script> {% endassets %} {% assets "scripts" %} <script type="text/javascript" src="{{ ASSET_URL }}"></script> {% endassets %}
{% assets "styles" %} <link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css" defer> {% endassets %} {% assets "styles" %} <link rel="stylesheet" href="{{ ASSET_URL }}" type="text/css" defer> {% endassets %}
{% block head %}{% endblock %} {% block head %}{% endblock %}
@ -70,14 +65,14 @@
{% if current_user.picture %} {% if current_user.picture %}
<span class="nav-pfp"> <span class="nav-pfp">
<picture> <picture>
<source srcset="{{ url_for('media_api.media', path='pfp/' + current_user.picture) }}?r=pfp&e=webp"> <source srcset="{{ url_for('api.media', path='pfp/' + current_user.picture) }}?r=pfp&e=webp">
<source srcset="{{ url_for('media_api.media', path='pfp/' + current_user.picture) }}?r=pfp&e=png"> <source srcset="{{ url_for('api.media', path='pfp/' + current_user.picture) }}?r=pfp&e=png">
<img <img
src="{{ url_for('media_api.media', path='pfp/' + current_user.picture) }}?r=icon" src="{{ url_for('api.media', path='pfp/' + current_user.picture) }}?r=icon"
alt="Profile picture" alt="Profile picture"
onload="imgFade(this)" onload="imgFade(this)"
style="opacity:0;" style="opacity:0;"
/> >
</picture> </picture>
</span> </span>
{% else %} {% else %}
@ -109,12 +104,12 @@
<button class="fileDrop-block" type="button"> <button class="fileDrop-block" type="button">
<i class="ph ph-upload"></i> <i class="ph ph-upload"></i>
<span class="status">Choose or Drop file</span> <span class="status">Choose or Drop file</span>
<input type="file" id="file" tab-index="-1"/> <input type="file" id="file" tab-index="-1">
</button> </button>
<input class="input-block" type="text" placeholder="alt" id="alt"/> <input class="input-block" type="text" placeholder="alt" id="alt">
<input class="input-block" type="text" placeholder="description" id="description"/> <input class="input-block" type="text" placeholder="description" id="description">
<input class="input-block" type="text" placeholder="tags" id="tags"/> <input class="input-block" type="text" placeholder="tags" id="tags">
<button class="btn-block primary" type="submit">Upload</button> <button class="btn-block primary" type="submit">Upload</button>
</form> </form>
<div class="upload-jobs"></div> <div class="upload-jobs"></div>

View file

@ -4,8 +4,8 @@
{% block head %} {% block head %}
{% if images %} {% if images %}
<meta property="og:image" content="{{ url_for('media_api.media', path='uploads/' + images.0.filename) }}"/> <meta property="og:image" content="{{ url_for('api.media', path='uploads/' + images.0.filename) }}"/>
<meta name="twitter:image" content="{{ url_for('media_api.media', path='uploads/' + images.0.filename) }}"> <meta name="twitter:image" content="{{ url_for('api.media', path='uploads/' + images.0.filename) }}">
<meta name="theme-color" content="rgb{{ images.0.colours.0 }}"/> <meta name="theme-color" content="rgb{{ images.0.colours.0 }}"/>
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
{% endif %} {% endif %}
@ -47,7 +47,7 @@
const formData = new FormData(); const formData = new FormData();
formData.append("group", formID); formData.append("group", formID);
fetch('{{ url_for('group_api.delete_group') }}', { fetch('{{ url_for('api.delete_group') }}', {
method: 'POST', method: 'POST',
body: formData body: formData
}).then(response => { }).then(response => {
@ -143,7 +143,7 @@
formData.append("image", formImage); formData.append("image", formImage);
formData.append("action", formAction); formData.append("action", formAction);
fetch('{{ url_for('group_api.modify_group') }}', { fetch('{{ url_for('api.modify_group') }}', {
method: 'POST', method: 'POST',
body: formData body: formData
}).then(response => { }).then(response => {
@ -185,9 +185,9 @@
.navigation-item.selected { color: {{ text_colour }} !important; } .navigation-item.selected { color: {{ text_colour }} !important; }
.banner-header, .banner .banner-content .banner-header,
.banner-info, .banner .banner-content .banner-info,
.banner-subtitle { .banner .banner-content .banner-subtitle {
color: {{ text_colour }} !important; color: {{ text_colour }} !important;
} }
.banner-content .link { .banner-content .link {
@ -215,10 +215,10 @@
{% if images %} {% if images %}
<div class="banner"> <div class="banner">
<picture> <picture>
<source srcset="{{ url_for('media_api.media', path='uploads/' + images.0.filename) }}?r=prev&e=webp"> <source srcset="{{ url_for('api.media', path='uploads/' + images.0.filename) }}?r=prev&e=webp">
<source srcset="{{ url_for('media_api.media', path='uploads/' + images.0.filename) }}?r=prev&e=png"> <source srcset="{{ url_for('api.media', path='uploads/' + images.0.filename) }}?r=prev&e=png">
<img <img
src="{{ url_for('media_api.media', path='uploads/' + images.0.filename) }}?r=prev" src="{{ url_for('api.media', path='uploads/' + images.0.filename) }}?r=prev"
alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}" alt="{% if images.0.alt %}{{ images.0.alt }}{% else %}Group Banner{% endif %}"
onload="imgFade(this)" style="opacity:0;" onload="imgFade(this)" style="opacity:0;"
/> />

View file

@ -3,8 +3,8 @@
{% if return_page %}?page={{ return_page }}{% endif %}{% endblock %} {% if return_page %}?page={{ return_page }}{% endif %}{% endblock %}
{% block head %} {% block head %}
<meta property="og:image" content="{{ url_for('media_api.media', path='uploads/' + image.filename) }}"/> <meta property="og:image" content="{{ url_for('api.media', path='uploads/' + image.filename) }}"/>
<meta name="twitter:image" content="{{ url_for('media_api.media', path='uploads/' + image.filename) }}"> <meta name="twitter:image" content="{{ url_for('api.media', path='uploads/' + image.filename) }}">
<meta name="theme-color" content="rgb{{ image.colours.0 }}"/> <meta name="theme-color" content="rgb{{ image.colours.0 }}"/>
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
@ -47,7 +47,7 @@
function deleteConfirm() { function deleteConfirm() {
popupDissmiss(); popupDissmiss();
fetch('{{ url_for('media_api.delete_image', image_id=image['id']) }}', { fetch('{{ url_for('api.delete_image', image_id=image['id']) }}', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -87,7 +87,7 @@
<div> <div>
<button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button> <button class="pill-item" onclick="fullscreen()" id="fullscreenImage"><i class="ph ph-info"></i></button>
<button class="pill-item" onclick="copyToClipboard(window.location.href)"><i class="ph ph-export"></i></button> <button class="pill-item" onclick="copyToClipboard(window.location.href)"><i class="ph ph-export"></i></button>
<a class="pill-item" href="{{ url_for('media_api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a> <a class="pill-item" href="{{ url_for('api.media', path='uploads/' + image.filename) }}" download onclick="addNotification('Download started!', 4)"><i class="ph ph-file-arrow-down"></i></a>
</div> </div>
{% if current_user.id == image.author.id %} {% if current_user.id == image.author.id %}
<div> <div>
@ -104,18 +104,18 @@
{% block content %} {% block content %}
<div class="background"> <div class="background">
<picture> <picture>
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev&e=webp"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev&e=webp">
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev&e=png"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev&e=png">
<img src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/> <img src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev" alt="{{ image.alt }}" onload="imgFade(this)" style="opacity:0;"/>
</picture> </picture>
</div> </div>
<div class="image-container {% if close_tab %}collapsed{% endif %}"> <div class="image-container {% if close_tab %}collapsed{% endif %}">
<picture> <picture>
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev&e=webp"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev&e=webp">
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev&e=png"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev&e=png">
<img <img
src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=prev" src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=prev"
alt="{{ image.alt }}" alt="{{ image.alt }}"
onload="imgFade(this)" onload="imgFade(this)"
style="opacity:0;" style="opacity:0;"
@ -138,7 +138,9 @@
<tr> <tr>
<td>Author</td> <td>Author</td>
<td> <td>
<img src="{{ url_for('media_api.media', path='pfp/' + image.author.picture) }}" alt="Profile Picture" class="pfp" onload="imgFade(this)" style="opacity: 0;"/> {% if image.author.picture %}
<img src="{{ url_for('api.media', path='pfp/' + image.author.picture) }}" alt="Profile Picture" class="pfp" onload="imgFade(this)" style="opacity: 0;"/>
{% endif %}
<a href="{{ url_for('profile.profile', id=image.author.id) }}" class="link">{{ image.author.username }}</a> <a href="{{ url_for('profile.profile', id=image.author.id) }}" class="link">{{ image.author.username }}</a>
</td> </td>
</tr> </tr>
@ -155,7 +157,9 @@
</table> </table>
<div class="img-colours"> <div class="img-colours">
{% for col in image.colours %} {% for col in image.colours %}
<button style="background-color: rgb{{ col }}" onclick="copyToClipboard('rgb{{ col }}')"><i class="ph-fill ph-paint-bucket" style="{{ col|colour_contrast }}"></i></button> <button style="background-color: rgb{{ col }}" onclick="copyToClipboard('rgb{{ col }}')">
<i class="ph-fill ph-paint-bucket" style="color:{{ col|colour_contrast }};"></i>
</button>
{% endfor %} {% endfor %}
</div> </div>
{% if image.groups %} {% if image.groups %}

View file

@ -66,7 +66,7 @@
formData.append("name", formName); formData.append("name", formName);
formData.append("description", formDescription); formData.append("description", formDescription);
fetch('{{ url_for('group_api.create_group') }}', { fetch('{{ url_for('api.create_group') }}', {
method: 'POST', method: 'POST',
body: formData body: formData
}).then(response => { }).then(response => {
@ -134,10 +134,10 @@
{% if group.images|length > 0 %} {% if group.images|length > 0 %}
{% for image in group.images %} {% for image in group.images %}
<picture> <picture>
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb&e=webp"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb&e=webp">
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb&e=png"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb&e=png">
<img <img
src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb"
alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}"
class="data-{{ loop.index }}" class="data-{{ loop.index }}"
onload="imgFade(this)" onload="imgFade(this)"

View file

@ -1,11 +1,19 @@
{% macro gallery_item(image) %} {% macro gallery_item(image) %}
<a id="image-{{ image.id }}" class="gallery-item square" href="{{ url_for('image.image', image_id=image.id) }}" style="background-color: rgb{{ image.colours.0 }}"> <a
<div class="image-filter"><p class="image-title"><span class="time">{{ image.created_at }}</span></p></div> id="image-{{ image.id }}"
class="gallery-item square"
href="{{ url_for('image.image', image_id=image.id) }}"
style="background-color: rgb{{ image.colours.0 }}"
draggable="false">
<div class="image-filter">
<p class="image-subtitle">By {{ image.username }}</p>
<p class="image-title"><span class="time">{{ image.created_at }}</span></p>
</div>
<picture> <picture>
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb&e=webp"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb&e=webp">
<source srcset="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb&e=png"> <source srcset="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb&e=png">
<img <img
src="{{ url_for('media_api.media', path='uploads/' + image.filename) }}?r=thumb" src="{{ url_for('api.media', path='uploads/' + image.filename) }}?r=thumb"
alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}" alt="{% if image.alt %}{{ image.alt }}{% else %}Image Thumbnail{% endif %}"
onload="imgFade(this)" onload="imgFade(this)"
style="opacity:0;" style="opacity:0;"

View file

@ -4,8 +4,8 @@
{% block head %} {% block head %}
{% if user.picture %} {% if user.picture %}
<meta property="og:image" content="{{ url_for('media_api.media', path='pfp/' + user.picture) }}"/> <meta property="og:image" content="{{ url_for('api.media', path='pfp/' + user.picture) }}"/>
<meta name="twitter:image" content="{{ url_for('media_api.media', path='pfp/' + user.picture) }}"> <meta name="twitter:image" content="{{ url_for('api.media', path='pfp/' + user.picture) }}">
{% endif %} {% endif %}
{% if user.colour %}<meta name="theme-color" content="rgb{{ user.colour }}"/>{% endif %} {% if user.colour %}<meta name="theme-color" content="rgb{{ user.colour }}"/>{% endif %}
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
@ -37,10 +37,10 @@
<div class="banner-content"> <div class="banner-content">
{% if user.picture %} {% if user.picture %}
<picture class="banner-picture"> <picture class="banner-picture">
<source srcset="{{ url_for('media_api.media', path='pfp/' + user.picture) }}?r=pfp&e=webp"> <source srcset="{{ url_for('api.media', path='pfp/' + user.picture) }}?r=pfp&e=webp">
<source srcset="{{ url_for('media_api.media', path='pfp/' + user.picture) }}?r=pfp&e=png"> <source srcset="{{ url_for('api.media', path='pfp/' + user.picture) }}?r=pfp&e=png">
<img <img
src="{{ url_for('media_api.media', path='pfp/' + user.picture) }}?r=pfp" src="{{ url_for('api.media', path='pfp/' + user.picture) }}?r=pfp"
alt="Profile picture" alt="Profile picture"
onload="imgFade(this)" onload="imgFade(this)"
style="opacity:0;" style="opacity:0;"

View file

@ -23,12 +23,12 @@
<button class="collapse-indicator"><i class="ph ph-caret-down"></i></button> <button class="collapse-indicator"><i class="ph ph-caret-down"></i></button>
</div> </div>
<div class="info-table"> <div class="info-table">
<form method="POST" action="{{ url_for('account_api.account_picture', user_id=current_user.id) }}" enctype="multipart/form-data"> <form method="POST" action="{{ url_for('api.account_picture', user_id=current_user.id) }}" enctype="multipart/form-data">
<h3>Profile Picture</h3> <h3>Profile Picture</h3>
<input type="file" name="file" tab-index="-1"/> <input type="file" name="file" tab-index="-1"/>
<input type="submit" value="Upload" class="btn-block"> <input type="submit" value="Upload" class="btn-block">
</form> </form>
<form method="POST" action="{{ url_for('account_api.account_username', user_id=current_user.id) }}" enctype="multipart/form-data"> <form method="POST" action="{{ url_for('api.account_username', user_id=current_user.id) }}" enctype="multipart/form-data">
<h3>Username</h3> <h3>Username</h3>
<input type="text" name="name" class="input-block" value="{{ current_user.username }}" /> <input type="text" name="name" class="input-block" value="{{ current_user.username }}" />
<input type="submit" value="Upload" class="btn-block"/> <input type="submit" value="Upload" class="btn-block"/>

View file

@ -1 +0,0 @@
# :3

View file

@ -3,19 +3,18 @@ Colour tools used by OnlyLegs
Source 1: https://gist.github.com/mathebox/e0805f72e7db3269ec22 Source 1: https://gist.github.com/mathebox/e0805f72e7db3269ec22
""" """
import math
def contrast(background, light, dark, threshold=0.179): class Colour:
def __init__(self, rgb):
self.rgb = rgb
def is_light(self, threshold=0.179):
""" """
background: tuple of (r, g, b) values returns True if background is light, False if dark
light: color to use if the background is light
dark: color to use if the background is dark
threshold: the threshold to use for determining lightness, the default is w3 recommended threshold: the threshold to use for determining lightness, the default is w3 recommended
""" """
red = background[0] red, green, blue = self.rgb
green = background[1]
blue = background[2]
# Calculate contrast # Calculate contrast
colors = [red / 255, green / 255, blue / 255] colors = [red / 255, green / 255, blue / 255]
@ -25,19 +24,16 @@ def contrast(background, light, dark, threshold=0.179):
] ]
lightness = (0.2126 * cont[0]) + (0.7152 * cont[1]) + (0.0722 * cont[2]) lightness = (0.2126 * cont[0]) + (0.7152 * cont[1]) + (0.0722 * cont[2])
return light if lightness > threshold else dark return True if lightness > threshold else False
def to_hsv(self):
def rgb_to_hsv(r, g, b): r, g, b = self.rgb
r = float(r)
g = float(g)
b = float(b)
high = max(r, g, b) high = max(r, g, b)
low = min(r, g, b) low = min(r, g, b)
h, s, v = high, high, high h, s, v = high, high, high
d = high - low d = high - low
s = 0 if high == 0 else d/high s = 0 if high == 0 else d / high
if high == low: if high == low:
h = 0.0 h = 0.0
@ -51,33 +47,11 @@ def rgb_to_hsv(r, g, b):
return h, s, v return h, s, v
def to_hsl(self):
def hsv_to_rgb(h, s, v): r, g, b = self.rgb
i = math.floor(h*6)
f = h*6 - i
p = v * (1-s)
q = v * (1-f*s)
t = v * (1-(1-f)*s)
r, g, b = [
(v, t, p),
(q, v, p),
(p, v, t),
(p, q, v),
(t, p, v),
(v, p, q),
][int(i % 6)]
return r, g, b
def rgb_to_hsl(r, g, b):
r = float(r)
g = float(g)
b = float(b)
high = max(r, g, b) high = max(r, g, b)
low = min(r, g, b) low = min(r, g, b)
h, s, v = ((high + low) / 2,)*3 h, s, v = ((high + low) / 2,) * 3
if high == low: if high == low:
h = 0.0 h = 0.0
@ -93,39 +67,3 @@ def rgb_to_hsl(r, g, b):
h /= 6 h /= 6
return h, s, v return h, s, v
def hsl_to_rgb(h, s, l):
def hue_to_rgb(p, q, t):
t += 1 if t < 0 else 0
t -= 1 if t > 1 else 0
if t < 1/6:
return p + (q - p) * 6 * t
if t < 1/2:
return q
if t < 2/3:
p + (q - p) * (2/3 - t) * 6
return p
if s == 0:
r, g, b = l, l, l
else:
q = l * (1 + s) if l < 0.5 else l + s - l * s
p = 2 * l - q
r = hue_to_rgb(p, q, h + 1/3)
g = hue_to_rgb(p, q, h)
b = hue_to_rgb(p, q, h - 1/3)
return r, g, b
def hsv_to_hsl(h, s, v):
l = 0.5 * v * (2 - s)
s = v * s / (1 - math.fabs(2*l-1))
return h, s, l
def hsl_to_hsv(h, s, l):
v = (2*l + s*(1-math.fabs(2*l-1)))/2
s = 2*(v-l)/v
return h, s, v

View file

@ -5,9 +5,9 @@ sounds more limiting that it actually is in this gallery
""" """
from flask import Blueprint, render_template, url_for, request from flask import Blueprint, render_template, url_for, request
from onlylegs.models import Post, User, GroupJunction, Group from onlylegs.models import Pictures, Users, AlbumJunction, Albums
from onlylegs.extensions import db from onlylegs.extensions import db
from onlylegs.utils.colour import contrast from onlylegs.utils import colour
blueprint = Blueprint("group", __name__, url_prefix="/group") blueprint = Blueprint("group", __name__, url_prefix="/group")
@ -18,21 +18,21 @@ def groups():
""" """
Group overview, shows all image groups Group overview, shows all image groups
""" """
groups = Group.query.all() groups = Albums.query.all()
# For each group, get the 3 most recent images # For each group, get the 3 most recent images
for group in groups: for group in groups:
group.author_username = ( group.author_username = (
User.query.with_entities(User.username) Users.query.with_entities(Users.username)
.filter(User.id == group.author_id) .filter(Users.id == group.author_id)
.first()[0] .first()[0]
) )
# Get the 3 most recent images # Get the 3 most recent images
images = ( images = (
GroupJunction.query.with_entities(GroupJunction.post_id) AlbumJunction.query.with_entities(AlbumJunction.picture_id)
.filter(GroupJunction.group_id == group.id) .filter(AlbumJunction.album_id == group.id)
.order_by(GroupJunction.date_added.desc()) .order_by(AlbumJunction.date_added.desc())
.limit(3) .limit(3)
) )
@ -40,8 +40,10 @@ def groups():
group.images = [] group.images = []
for image in images: for image in images:
group.images.append( group.images.append(
Post.query.with_entities(Post.filename, Post.alt, Post.colours, Post.id) Pictures.query.with_entities(
.filter(Post.id == image[0]) Pictures.filename, Pictures.alt, Pictures.colours, Pictures.id
)
.filter(Pictures.id == image[0])
.first() .first()
) )
@ -54,26 +56,29 @@ def group(group_id):
Group view, shows all images in a group Group view, shows all images in a group
""" """
# Get the group, if it doesn't exist, 404 # Get the group, if it doesn't exist, 404
group = db.get_or_404(Group, group_id, description="Group not found! D:") group = db.get_or_404(Albums, group_id, description="Group not found! D:")
# Get all images in the group from the junction table # Get all images in the group from the junction table
junction = ( junction = (
GroupJunction.query.with_entities(GroupJunction.post_id) AlbumJunction.query.with_entities(AlbumJunction.picture_id)
.filter(GroupJunction.group_id == group_id) .filter(AlbumJunction.album_id == group_id)
.order_by(GroupJunction.date_added.desc()) .order_by(AlbumJunction.date_added.desc())
.all() .all()
) )
# Get the image data for each image in the group # Get the image data for each image in the group
images = [] images = []
for image in junction: for image in junction:
images.append(Post.query.filter(Post.id == image[0]).first()) images.append(Pictures.query.filter(Pictures.id == image[0]).first())
# Check contrast for the first image in the group for the banner # Check contrast for the first image in the group for the banner
text_colour = "rgb(var(--fg-black))" text_colour = "rgb(var(--fg-black))"
if images: if images:
text_colour = contrast( colour_obj = colour.Colour(images[0].colours[0])
images[0].colours[0], "rgb(var(--fg-black))", "rgb(var(--fg-white))" text_colour = (
"rgb(var(--fg-black));"
if colour_obj.is_light()
else "rgb(var(--fg-white));"
) )
return render_template( return render_template(
@ -87,12 +92,12 @@ def group_post(group_id, image_id):
Image view, shows the image and its metadata from a specific group Image view, shows the image and its metadata from a specific group
""" """
# Get the image, if it doesn't exist, 404 # Get the image, if it doesn't exist, 404
image = db.get_or_404(Post, image_id, description="Image not found :<") image = db.get_or_404(Pictures, image_id, description="Image not found :<")
# Get all groups the image is in # Get all groups the image is in
groups = ( groups = (
GroupJunction.query.with_entities(GroupJunction.group_id) AlbumJunction.query.with_entities(AlbumJunction.album_id)
.filter(GroupJunction.post_id == image_id) .filter(AlbumJunction.picture_id == image_id)
.all() .all()
) )
@ -100,24 +105,24 @@ def group_post(group_id, image_id):
image.groups = [] image.groups = []
for group in groups: for group in groups:
image.groups.append( image.groups.append(
Group.query.with_entities(Group.id, Group.name) Albums.query.with_entities(Albums.id, Albums.name)
.filter(Group.id == group[0]) .filter(Albums.id == group[0])
.first() .first()
) )
# Get the next and previous images in the group # Get the next and previous images in the group
next_url = ( next_url = (
GroupJunction.query.with_entities(GroupJunction.post_id) AlbumJunction.query.with_entities(AlbumJunction.picture_id)
.filter(GroupJunction.group_id == group_id) .filter(AlbumJunction.album_id == group_id)
.filter(GroupJunction.post_id > image_id) .filter(AlbumJunction.picture_id > image_id)
.order_by(GroupJunction.date_added.asc()) .order_by(AlbumJunction.date_added.asc())
.first() .first()
) )
prev_url = ( prev_url = (
GroupJunction.query.with_entities(GroupJunction.post_id) AlbumJunction.query.with_entities(AlbumJunction.picture_id)
.filter(GroupJunction.group_id == group_id) .filter(AlbumJunction.album_id == group_id)
.filter(GroupJunction.post_id < image_id) .filter(AlbumJunction.picture_id < image_id)
.order_by(GroupJunction.date_added.desc()) .order_by(AlbumJunction.date_added.desc())
.first() .first()
) )

View file

@ -3,7 +3,7 @@ Onlylegs - Image View
""" """
from math import ceil from math import ceil
from flask import Blueprint, render_template, url_for, current_app, request from flask import Blueprint, render_template, url_for, current_app, request
from onlylegs.models import Post, GroupJunction, Group from onlylegs.models import Pictures, AlbumJunction, Albums
from onlylegs.extensions import db from onlylegs.extensions import db
@ -16,12 +16,12 @@ def image(image_id):
Image view, shows the image and its metadata Image view, shows the image and its metadata
""" """
# Get the image, if it doesn't exist, 404 # Get the image, if it doesn't exist, 404
image = db.get_or_404(Post, image_id, description="Image not found :<") image = db.get_or_404(Pictures, image_id, description="Image not found :<")
# Get all groups the image is in # Get all groups the image is in
groups = ( groups = (
GroupJunction.query.with_entities(GroupJunction.group_id) AlbumJunction.query.with_entities(AlbumJunction.album_id)
.filter(GroupJunction.post_id == image_id) .filter(AlbumJunction.picture_id == image_id)
.all() .all()
) )
@ -29,23 +29,23 @@ def image(image_id):
image.groups = [] image.groups = []
for group in groups: for group in groups:
image.groups.append( image.groups.append(
Group.query.with_entities(Group.id, Group.name) Albums.query.with_entities(Albums.id, Albums.name)
.filter(Group.id == group[0]) .filter(Albums.id == group[0])
.first() .first()
) )
# Get the next and previous images # Get the next and previous images
# Check if there is a group ID set # Check if there is a group ID set
next_url = ( next_url = (
Post.query.with_entities(Post.id) Pictures.query.with_entities(Pictures.id)
.filter(Post.id > image_id) .filter(Pictures.id > image_id)
.order_by(Post.id.asc()) .order_by(Pictures.id.asc())
.first() .first()
) )
prev_url = ( prev_url = (
Post.query.with_entities(Post.id) Pictures.query.with_entities(Pictures.id)
.filter(Post.id < image_id) .filter(Pictures.id < image_id)
.order_by(Post.id.desc()) .order_by(Pictures.id.desc())
.first() .first()
) )
@ -56,7 +56,9 @@ def image(image_id):
prev_url = url_for("image.image", image_id=prev_url[0]) prev_url = url_for("image.image", image_id=prev_url[0])
# Yoink all the images in the database # Yoink all the images in the database
total_images = Post.query.with_entities(Post.id).order_by(Post.id.desc()).all() total_images = (
Pictures.query.with_entities(Pictures.id).order_by(Pictures.id.desc()).all()
)
limit = current_app.config["UPLOAD_CONF"]["max-load"] limit = current_app.config["UPLOAD_CONF"]["max-load"]
# If the number of items is less than the limit, no point of calculating the page # If the number of items is less than the limit, no point of calculating the page

View file

@ -2,11 +2,9 @@
Onlylegs Gallery - Index view Onlylegs Gallery - Index view
""" """
from math import ceil from math import ceil
from flask import Blueprint, render_template, request, current_app from flask import Blueprint, render_template, request, current_app
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from onlylegs.models import Pictures, Users
from onlylegs.models import Post
blueprint = Blueprint("gallery", __name__) blueprint = Blueprint("gallery", __name__)
@ -17,31 +15,32 @@ def index():
""" """
Home page of the website, shows the feed of the latest images Home page of the website, shows the feed of the latest images
""" """
# meme
if request.args.get("coffee") == "please":
abort(418)
# pagination, defaults to page 1 if no page is specified # pagination, defaults to page 1 if no page is specified
page = request.args.get("page", default=1, type=int) page = request.args.get("page", default=1, type=int)
limit = current_app.config["UPLOAD_CONF"]["max-load"] limit = current_app.config["UPLOAD_CONF"]["max-load"]
# get the total number of images in the database # get the total number of images in the database
# calculate the total number of pages, and make sure the page number is valid # calculate the total number of pages, and make sure the page number is valid
total_images = Post.query.with_entities(Post.id).count() total_images = Pictures.query.with_entities(Pictures.id).count()
pages = ceil(max(total_images, limit) / limit) pages = ceil(max(total_images, limit) / limit)
if page > pages: if page > pages:
abort( return abort(
404, 404,
"You have reached the far and beyond, " "You have reached the far and beyond, but you will not find your answers here.",
+ "but you will not find your answers here.",
) )
# get the images for the current page # get the images for the current page
images = ( images = (
Post.query.with_entities( Pictures.query.with_entities(
Post.filename, Post.alt, Post.colours, Post.created_at, Post.id Pictures.filename,
Pictures.alt,
Pictures.colours,
Pictures.created_at,
Pictures.id,
Users.username,
) )
.order_by(Post.id.desc()) .join(Users)
.order_by(Pictures.id.desc())
.offset((page - 1) * limit) .offset((page - 1) * limit)
.limit(limit) .limit(limit)
.all() .all()

View file

@ -5,7 +5,7 @@ from flask import Blueprint, render_template, request
from werkzeug.exceptions import abort from werkzeug.exceptions import abort
from flask_login import current_user from flask_login import current_user
from onlylegs.models import Post, User, Group from onlylegs.models import Pictures, Users, Albums
from onlylegs.extensions import db from onlylegs.extensions import db
@ -27,9 +27,9 @@ def profile():
abort(404, "You must be logged in to view your own profile!") abort(404, "You must be logged in to view your own profile!")
# Get the user's data # Get the user's data
user = db.get_or_404(User, user_id, description="User not found :<") user = db.get_or_404(Users, user_id, description="User not found :<")
images = Post.query.filter(Post.author_id == user_id).all() images = Pictures.query.filter(Pictures.author_id == user_id).all()
groups = Group.query.filter(Group.author_id == user_id).all() groups = Albums.query.filter(Albums.author_id == user_id).all()
return render_template("profile.html", user=user, images=images, groups=groups) return render_template("profile.html", user=user, images=images, groups=groups)

307
poetry.lock generated
View file

@ -2,14 +2,14 @@
[[package]] [[package]]
name = "alembic" name = "alembic"
version = "1.11.1" version = "1.11.2"
description = "A database migration tool for SQLAlchemy." description = "A database migration tool for SQLAlchemy."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "alembic-1.11.1-py3-none-any.whl", hash = "sha256:dc871798a601fab38332e38d6ddb38d5e734f60034baeb8e2db5b642fccd8ab8"}, {file = "alembic-1.11.2-py3-none-any.whl", hash = "sha256:7981ab0c4fad4fe1be0cf183aae17689fe394ff874fd2464adb774396faf0796"},
{file = "alembic-1.11.1.tar.gz", hash = "sha256:6a810a6b012c88b33458fceb869aef09ac75d6ace5291915ba7fae44de372c01"}, {file = "alembic-1.11.2.tar.gz", hash = "sha256:678f662130dc540dac12de0ea73de9f89caea9dbea138f60ef6263149bf84657"},
] ]
[package.dependencies] [package.dependencies]
@ -24,14 +24,14 @@ tz = ["python-dateutil"]
[[package]] [[package]]
name = "astroid" name = "astroid"
version = "2.15.5" version = "2.15.6"
description = "An abstract syntax tree for Python with inference support." description = "An abstract syntax tree for Python with inference support."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7.2" python-versions = ">=3.7.2"
files = [ files = [
{file = "astroid-2.15.5-py3-none-any.whl", hash = "sha256:078e5212f9885fa85fbb0cf0101978a336190aadea6e13305409d099f71b2324"}, {file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"},
{file = "astroid-2.15.5.tar.gz", hash = "sha256:1039262575027b441137ab4a62a793a9b43defb42c32d5670f38686207cd780f"}, {file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"},
] ]
[package.dependencies] [package.dependencies]
@ -44,37 +44,34 @@ wrapt = [
[[package]] [[package]]
name = "black" name = "black"
version = "23.3.0" version = "23.7.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
{file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
{file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
{file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
{file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
{file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
{file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
{file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
{file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
{file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
{file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
{file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
{file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
{file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
{file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
{file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
{file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
{file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
{file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
{file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
{file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
{file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
{file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
{file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
{file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
] ]
[package.dependencies] [package.dependencies]
@ -210,14 +207,14 @@ files = [
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.3" version = "8.1.6"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
] ]
[package.dependencies] [package.dependencies]
@ -263,14 +260,14 @@ files = [
[[package]] [[package]]
name = "dill" name = "dill"
version = "0.3.6" version = "0.3.7"
description = "serialize all of python" description = "serialize all of Python"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "dill-0.3.6-py3-none-any.whl", hash = "sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0"}, {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"},
{file = "dill-0.3.6.tar.gz", hash = "sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"}, {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"},
] ]
[package.extras] [package.extras]
@ -493,14 +490,14 @@ tornado = ["tornado (>=0.2)"]
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
version = "6.7.0" version = "6.8.0"
description = "Read metadata from Python packages" description = "Read metadata from Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"},
{file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"},
] ]
[package.dependencies] [package.dependencies]
@ -509,26 +506,26 @@ zipp = ">=0.5"
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
perf = ["ipython"] perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"]
[[package]] [[package]]
name = "importlib-resources" name = "importlib-resources"
version = "5.12.0" version = "6.0.0"
description = "Read resources from Python packages" description = "Read resources from Python packages"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, {file = "importlib_resources-6.0.0-py3-none-any.whl", hash = "sha256:d952faee11004c045f785bb5636e8f885bed30dc3c940d5d42798a2a4541c185"},
{file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, {file = "importlib_resources-6.0.0.tar.gz", hash = "sha256:4cf94875a8368bd89531a756df9a9ebe1f150e0f885030b461237bc7f2d905f2"},
] ]
[package.dependencies] [package.dependencies]
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
[[package]] [[package]]
name = "isort" name = "isort"
@ -768,14 +765,14 @@ files = [
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.11.1" version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"}, {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
] ]
[[package]] [[package]]
@ -860,34 +857,34 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "3.8.0" version = "3.10.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "platformdirs-3.8.0-py3-none-any.whl", hash = "sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"}, {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
{file = "platformdirs-3.8.0.tar.gz", hash = "sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc"}, {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]] [[package]]
name = "pylint" name = "pylint"
version = "2.17.4" version = "2.17.5"
description = "python code static checker" description = "python code static checker"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7.2" python-versions = ">=3.7.2"
files = [ files = [
{file = "pylint-2.17.4-py3-none-any.whl", hash = "sha256:7a1145fb08c251bdb5cca11739722ce64a63db479283d10ce718b2460e54123c"}, {file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"},
{file = "pylint-2.17.4.tar.gz", hash = "sha256:5dcf1d9e19f41f38e4e85d10f511e5b9c35e1aa74251bf95cdd8cb23584e2db1"}, {file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"},
] ]
[package.dependencies] [package.dependencies]
astroid = ">=2.15.4,<=2.17.0-dev0" astroid = ">=2.15.6,<=2.17.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = [ dill = [
{version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.2", markers = "python_version < \"3.11\""},
@ -921,52 +918,52 @@ cli = ["click (>=5.0)"]
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0" version = "6.0.1"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{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.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
{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.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
{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.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{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.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{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.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
] ]
[[package]] [[package]]
@ -988,53 +985,53 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "2.0.17" version = "2.0.19"
description = "Database Abstraction Library" description = "Database Abstraction Library"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "SQLAlchemy-2.0.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:04383f1e3452f6739084184e427e9d5cb4e68ddc765d52157bf5ef30d5eca14f"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:724355973297bbe547f3eb98b46ade65a67a3d5a6303f17ab59a2dc6fb938943"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf07ff9920cb3ca9d73525dfd4f36ddf9e1a83734ea8b4f724edfd9a2c6e82d9"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f389f77c68dc22cb51f026619291c4a38aeb4b7ecb5f998fd145b2d81ca513"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ba03518e64d86f000dc24ab3d3a1aa876bcbaa8aa15662ac2df5e81537fa3394"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:218fb20c01e95004f50a3062bf4c447dcb360cab8274232f31947e254f118298"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-win32.whl", hash = "sha256:b47be4c6281a86670ea5cfbbbe6c3a65366a8742f5bc8b986f790533c60b5ddb"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-win32.whl", hash = "sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2"},
{file = "SQLAlchemy-2.0.17-cp310-cp310-win_amd64.whl", hash = "sha256:74ddcafb6488f382854a7da851c404c394be3729bb3d91b02ad86c5458140eff"}, {file = "SQLAlchemy-2.0.19-cp310-cp310-win_amd64.whl", hash = "sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:51736cfb607cf4e8fafb693906f9bc4e5ee55be0b096d44bd7f20cd8489b8571"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8741d3d401383e54b2aada37cbd10f55c5d444b360eae3a82f74a2be568a7710"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ead58cae2a089eee1b0569060999cb5f2b2462109498a0937cc230a7556945a1"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f40e3a7d0a464f1c8593f2991e5520b2f5b26da24e88000bbd4423f86103d4f"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21583808d37f126a647652c90332ac1d3a102edf3c94bcc3319edcc0ea2300cc"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f593170fc09c5abb1205a738290b39532f7380094dc151805009a07ae0e85330"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-win32.whl", hash = "sha256:b0eaf82cc844f6b46defe15ad243ea00d1e39ed3859df61130c263dc7204da6e"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-win32.whl", hash = "sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145"},
{file = "SQLAlchemy-2.0.17-cp311-cp311-win_amd64.whl", hash = "sha256:1822620c89779b85f7c23d535c8e04b79c517739ae07aaed48c81e591ed5498e"}, {file = "SQLAlchemy-2.0.19-cp311-cp311-win_amd64.whl", hash = "sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2269b1f9b8be47e52b70936069a25a3771eff53367aa5cc59bb94f28a6412e13"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48111d56afea5699bab72c38ec95561796b81befff9e13d1dd5ce251ab25f51d"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28da17059ecde53e2d10ba813d38db942b9f6344360b2958b25872d5cb729d35"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:48b40dc2895841ea89d89df9eb3ac69e2950a659db20a369acf4259f68e6dc1f"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7f31d4e7ca1dd8ca5a27fd5eaa0f9e2732fe769ff7dd35bf7bba179597e4df07"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-win32.whl", hash = "sha256:7830e01b02d440c27f2a5be68296e74ccb55e6a5b5962ffafd360b98930b2e5e"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-win32.whl", hash = "sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b"},
{file = "SQLAlchemy-2.0.17-cp37-cp37m-win_amd64.whl", hash = "sha256:234678ed6576531b8e4be255b980f20368bf07241a2e67b84e6b0fe679edb9c4"}, {file = "SQLAlchemy-2.0.19-cp37-cp37m-win_amd64.whl", hash = "sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c6ff5767d954f6091113fedcaaf49cdec2197ae4c5301fe83d5ae4393c82f33"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa995b21f853864996e4056d9fde479bcecf8b7bff4beb3555eebbbba815f35d"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:125f9f7e62ddf8b590c069729080ffe18b68a20d9882eb0947f72e06274601d7"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b114a16bc03dfe20b625062e456affd7b9938286e05a3f904a025b9aacc29dd4"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf175d26f6787cce30fe6c04303ca0aeeb0ad40eeb22e3391f24b32ec432a1e1"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e2d5c3596254cf1a96474b98e7ce20041c74c008b0f101c1cb4f8261cb77c6d3"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-win32.whl", hash = "sha256:513411d73503a6fc5804f01fae3b3d44f267c1b3a06cfeac02e9286a7330e857"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-win32.whl", hash = "sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531"},
{file = "SQLAlchemy-2.0.17-cp38-cp38-win_amd64.whl", hash = "sha256:40a3dc52b2b16f08b5c16b9ee7646329e4b3411e9280e5e8d57b19eaa51cbef4"}, {file = "SQLAlchemy-2.0.19-cp38-cp38-win_amd64.whl", hash = "sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e3189432db2f5753b4fde1aa90a61c69976f4e7e31d1cf4611bfe3514ed07478"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6150560fcffc6aee5ec9a97419ac768c7a9f56baf7a7eb59cb4b1b6a4d463ad9"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:910d45bf3673f0e4ef13858674bd23cfdafdc8368b45b948bf511797dbbb401d"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0aeb3afaa19f187a70fa592fbe3c20a056b57662691fd3abf60f016aa5c1848"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36a87e26fe8fa8c466fae461a8fcb780d0a1cbf8206900759fc6fe874475a3ce"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e3a6b2788f193756076061626679c5c5a6d600ddf8324f986bc72004c3e9d92e"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-win32.whl", hash = "sha256:af7e2ba75bf84b64adb331918188dda634689a2abb151bc1a583e488363fd2f8"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-win32.whl", hash = "sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb"},
{file = "SQLAlchemy-2.0.17-cp39-cp39-win_amd64.whl", hash = "sha256:394ac3adf3676fad76d4b8fcecddf747627f17f0738dc94bac15f303d05b03d4"}, {file = "SQLAlchemy-2.0.19-cp39-cp39-win_amd64.whl", hash = "sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0"},
{file = "SQLAlchemy-2.0.17-py3-none-any.whl", hash = "sha256:cc9c2630c423ac4973492821b2969f5fe99d9736f3025da670095668fbfcd4d5"}, {file = "SQLAlchemy-2.0.19-py3-none-any.whl", hash = "sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972"},
{file = "SQLAlchemy-2.0.17.tar.gz", hash = "sha256:e186e9e95fb5d993b075c33fe4f38a22105f7ce11cecb5c17b5618181e356702"}, {file = "SQLAlchemy-2.0.19.tar.gz", hash = "sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f"},
] ]
[package.dependencies] [package.dependencies]
@ -1079,14 +1076,14 @@ files = [
[[package]] [[package]]
name = "tomlkit" name = "tomlkit"
version = "0.11.8" version = "0.12.1"
description = "Style preserving TOML library" description = "Style preserving TOML library"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "tomlkit-0.11.8-py3-none-any.whl", hash = "sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171"}, {file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"},
{file = "tomlkit-0.11.8.tar.gz", hash = "sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3"}, {file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"},
] ]
[[package]] [[package]]
@ -1218,19 +1215,19 @@ files = [
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.15.0" version = "3.16.2"
description = "Backport of pathlib-compatible object wrapper for zip files" description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"},
{file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"},
] ]
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "OnlyLegs" name = "OnlyLegs"
version = "0.1.4" version = "0.1.5"
repository = "https://github.com/Fluffy-Bean/onlylegs" repository = "https://github.com/Fluffy-Bean/onlylegs"
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"

4
run.py
View file

@ -25,9 +25,9 @@ Configuration()
if DEBUG: if DEBUG:
from onlylegs import create_app from onlylegs.app import app
create_app().run(host=ADDRESS, port=PORT, debug=True, threaded=True) app.run(host=ADDRESS, port=PORT, debug=True, threaded=True)
else: else:
from setup.runner import OnlyLegs # pylint: disable=C0412 from setup.runner import OnlyLegs # pylint: disable=C0412
import sys import sys