diff --git a/files/assets/css/main.css b/files/assets/css/main.css index 046382f34..22a898e03 100644 --- a/files/assets/css/main.css +++ b/files/assets/css/main.css @@ -5325,3 +5325,41 @@ div[id^="reply-edit-"] li > p:first-child { .volunteer_janitor .choices { margin-top: 1rem; } + +.volunteer_janitor_result { + border-radius: 1rem; + padding: 0.2rem; + color: var(--primary-dark1); +} + +.volunteer_janitor_result_notbad_0 { + background-color: hsl(240, 100%, 99%); +} + +.volunteer_janitor_result_notbad_1 { + background-color: hsl(240, 100%, 98%); +} + +.volunteer_janitor_result_notbad_2 { + background-color: hsl(240, 100%, 94%); +} + +.volunteer_janitor_result_notbad_3 { + background-color: hsl(240, 100%, 90%); +} + +.volunteer_janitor_result_bad_0 { + background-color: hsl(0, 100%, 98%); +} + +.volunteer_janitor_result_bad_1 { + background-color: hsl(0, 100%, 94%); +} + +.volunteer_janitor_result_bad_2 { + background-color: hsl(0, 100%, 88%); +} + +.volunteer_janitor_result_bad_3 { + background-color: hsl(0, 100%, 80%); +} \ No newline at end of file diff --git a/files/classes/comment.py b/files/classes/comment.py index 564c6f1f5..efbb09e44 100644 --- a/files/classes/comment.py +++ b/files/classes/comment.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Literal, Optional from urllib.parse import parse_qs, urlencode, urlparse from flask import g +import math from sqlalchemy import * from sqlalchemy.orm import relationship @@ -11,6 +12,7 @@ from files.helpers.config.environment import SCORE_HIDING_TIME_HOURS, SITE_FULL from files.helpers.content import (ModerationState, body_displayed, execute_shadowbanned_fake_votes) from files.helpers.lazy import lazy +from files.helpers.math import clamp from files.helpers.time import format_age if TYPE_CHECKING: @@ -48,6 +50,7 @@ class Comment(CreatedBase): body_html = Column(Text, nullable=False) ban_reason = Column(String) filter_state = Column(String, nullable=False) + volunteer_janitor_badness = Column(Float, default=0.5, nullable=False) Index('comment_parent_index', parent_comment_id) Index('comment_post_id_index', parent_submission) @@ -441,3 +444,30 @@ class Comment(CreatedBase): @property def moderation_state(self) -> ModerationState: return ModerationState.from_submittable(self) + + def volunteer_janitor_is_unknown(self): + return self.volunteer_janitor_badness > 0.4 and self.volunteer_janitor_badness < 0.6 + + def volunteer_janitor_is_bad(self): + return self.volunteer_janitor_badness >= 0.6 + + def volunteer_janitor_is_notbad(self): + return self.volunteer_janitor_badness <= 0.4 + + def volunteer_janitor_confidence(self): + unitconfidence = (abs(self.volunteer_janitor_badness - 0.5) * 2) + unitanticonfidence = 1 - unitconfidence + logconfidence = -math.log(unitanticonfidence, 2) + return round(logconfidence * 10) + + def volunteer_janitor_css(self): + if self.volunteer_janitor_is_unknown(): + category = "unknown" + elif self.volunteer_janitor_is_bad(): + category = "bad" + elif self.volunteer_janitor_is_notbad(): + category = "notbad" + + strength = clamp(math.trunc(self.volunteer_janitor_confidence() / 10), 0, 3) + + return f"{category}_{strength}" diff --git a/files/classes/user.py b/files/classes/user.py index 104b1cd65..0ed209d0d 100644 --- a/files/classes/user.py +++ b/files/classes/user.py @@ -111,6 +111,7 @@ class User(CreatedBase): original_username = deferred(Column(String)) referred_by = Column(Integer, ForeignKey("users.id")) volunteer_last_started_utc = Column(DateTime, nullable=True) + volunteer_janitor_correctness = Column(Float, default=0, nullable=False) Index( 'users_original_username_trgm_idx', diff --git a/files/cli.py b/files/cli.py index b702bc31b..061629af7 100644 --- a/files/cli.py +++ b/files/cli.py @@ -4,6 +4,7 @@ from flask_sqlalchemy import SQLAlchemy from files.__main__ import app from files.commands.cron import cron_app_worker from files.commands.seed_db import seed_db +from files.commands.volunteer_janitor_recalc import volunteer_janitor_recalc from files.commands.cron_setup import cron_setup import files.classes diff --git a/files/commands/cron_setup.py b/files/commands/cron_setup.py index b64c6c5cd..e44edf7b2 100644 --- a/files/commands/cron_setup.py +++ b/files/commands/cron_setup.py @@ -19,12 +19,12 @@ def cron_setup(): # I guess in theory we should load this from a file or something, but, ehhhh hardcoded_cron_jobs = { - #'testjob': { - #'frequency_day': DayOfWeek.ALL, - #'time_of_day_utc': datetime.time(0, 0), - #'import_path': 'files.commands.debug_printout', - #'callable': 'printstuff', - #}, + 'volunteer_janitor_recalc': { + 'frequency_day': DayOfWeek.ALL, + 'time_of_day_utc': datetime.time(0, 0), + 'import_path': 'files.commands.volunteer_janitor_recalc', + 'callable': 'volunteer_janitor_recalc_cron', + }, } print(f"{tasklist.count()} tasks") diff --git a/files/commands/volunteer_janitor_recalc.py b/files/commands/volunteer_janitor_recalc.py new file mode 100644 index 000000000..45445887a --- /dev/null +++ b/files/commands/volunteer_janitor_recalc.py @@ -0,0 +1,373 @@ + +import pprint + +import sqlalchemy +from sqlalchemy.orm import Session + +from alive_progress import alive_it +from collections import defaultdict +from files.classes import User, Comment, UserNote, UserTag +from files.classes.cron.tasks import TaskRunContext +from files.classes.volunteer_janitor import VolunteerJanitorRecord, VolunteerJanitorResult +from files.helpers.volunteer_janitor import evaluate_badness_of, userweight_from_user_accuracy, calculate_final_comment_badness, update_comment_badness +from files.helpers.math import saturate, remap, lerp + +import logging +import random +from tabulate import tabulate + +from files.__main__ import app, db_session + +CONFIG_modhat_weight = 4 +CONFIG_admin_volunteer_weight = 4 +CONFIG_new_user_damping = 2 +CONFIG_default_user_accuracy = 0.2 +CONFIG_user_correctness_lerp = 0.2 + +def _compile_records(db): + vrecords = db.query(VolunteerJanitorRecord).order_by(VolunteerJanitorRecord.recorded_utc).all() + + # get the info we need for all mentioned posts + reported_comment_ids = {record.comment_id for record in vrecords} + reported_comments = db.query(Comment).where(Comment.id.in_(reported_comment_ids)).options(sqlalchemy.orm.load_only('id', 'deleted_utc')) + reported_comments = {comment.id: comment for comment in reported_comments} + + # get our compiled data + records_compiled = {} + for record in vrecords: + # we're just going to ignore deleted comments entirely + if reported_comments[record.comment_id].deleted_utc != 0: + continue + + # unique identifier for user/comment report pair + uic = (record.user_id, record.comment_id) + + if record.result == VolunteerJanitorResult.Pending: + if uic in records_compiled: + # something wonky happened, we went back to pending after getting a result? + records_compiled[uic]["status"] = "wonky" + else: + # fill out the pending field + records_compiled[uic] = {"status": "pending"} + else: + if not uic in records_compiled: + # something wonky happened, we never asked them for the info to begin with + records_compiled[uic] = {"status": "wonky"} + elif records_compiled[uic]["status"] != "pending": + # received two submissions; practically we'll just use their first submission + records_compiled[uic]["status"] = "resubmit" + else: + # actually got a result, yay + records_compiled[uic]["status"] = "submit" + records_compiled[uic]["result"] = record.result + + # todo: + # filter out anything submitted *after* a mod chimed in + # filter out anything submitted too long after the request + + users_compiled = defaultdict(lambda: { + "pending": 0, + "wonky": 0, + "submit": 0, + "resubmit": 0, + }) + for key, result in records_compiled.items(): + #pprint.pprint(key) + userid = key[0] + + users_compiled[key[0]][result["status"]] += 1 + + #pprint.pprint(records_compiled) + #pprint.pprint(users_compiled) + + # strip out invalid records + random_removal = -1 # this is sometimes useful for testing that our algorithm is somewhat stable; removing a random half of all responses shouldn't drastically invert people's quality scores, for example + records_compiled = {key: value for key, value in records_compiled.items() if "result" in value and random.random() > random_removal} + + return records_compiled, users_compiled + +def dbg_commentdump(cid, records_compiled, users, user_accuracy): + print(f"Dump for comment {cid}") + + dats = [] + for key, value in [(key, value) for key, value in records_compiled.items() if key[1] == cid]: + uid = key[0] + dats.append({ + "vote": value["result"], + "username": users[uid]["username"], + "accuracy": user_accuracy[uid], + }) + print(tabulate(dats, headers = "keys")) + +def dbg_userdump(uid, records_compiled, users, comment_calculated_badness_user): + print(f"Dump for user {users[uid]['username']}") + + dats = [] + for key, value in [(key, value) for key, value in records_compiled.items() if key[0] == uid]: + cid = key[1] + bad, weight = evaluate_badness_of(value["result"]) + dats.append({ + "cid": cid, + "vote": value["result"], + "calculated": evaluate_badness_of(value["result"]), + "badness": comment_calculated_badness_user[cid], + "correctness": evaluate_correctness_single(bad, weight, comment_calculated_badness_user[cid]), + }) + print(tabulate(dats, headers = "keys")) + +# Calculates how correct a user is, based on whether they thought it was bad, how confident they were, and how bad we think it is +# Returns (IsCorrect, Confidence) +def evaluate_correctness_single(bad, user_weight, calculated_badness): + # Boolean for whether this comment is bad + calculated_badbool = calculated_badness > 0.5 + + # Boolean for whether the user was correct + correctness_result = (bad == calculated_badbool) and 1 or 0 + + # "how confident are we that this is bad/notbad", range [0, 0.5] + calculated_badness_confidence = abs(calculated_badness - 0.5) + + # "how much do we want this to influence the user's correctness" + # there's a deadzone around not-confident where we just push it to 0 and don't make it relevant + calculated_badness_weight = saturate(remap(calculated_badness_confidence, 0.1, 0.5, 0, 1)) + + # see how correct we think the user is + user_correctness = user_weight * calculated_badness_weight + + return correctness_result, user_correctness + +def volunteer_janitor_recalc(db: Session, diagnostics: bool = False): + logging.info("Starting full janitor recalculation") + + # Get our full list of data + records_compiled, users_compiled = _compile_records(db) + + reporting_user_ids = {record[0] for record in records_compiled} + reported_comment_ids = {record[1] for record in records_compiled} + + # Get some metadata for all reported comments + comments = db.query(Comment) \ + .where(Comment.id.in_(reported_comment_ids)) \ + .options(sqlalchemy.orm.load_only('id', 'created_utc', 'author_id')) + comments = {comment.id: comment for comment in comments} + + reported_user_ids = {comment.author_id for comment in comments.values()} + + # Get mod intervention data + modhats_raw = db.query(Comment) \ + .where(Comment.parent_comment_id.in_(reported_comment_ids)) \ + .where(Comment.distinguish_level > 0) \ + .options(sqlalchemy.orm.load_only('parent_comment_id', 'created_utc')) + + modhats = {} + # we jump through some hoops to deduplicate this; I guess we just pick the last one in our list for now + for modhat in modhats_raw: + modhats[modhat.parent_comment_id] = modhat + + usernotes_raw = db.query(UserNote) \ + .where(UserNote.tag.in_([UserTag.Warning, UserTag.Tempban, UserTag.Permban, UserTag.Spam, UserTag.Bot])) \ + .options(sqlalchemy.orm.load_only('reference_user', 'created_utc', 'tag')) + + # Here we're trying to figure out whether modhats are actually warnings/bans + # We don't have a formal connection between "a comment is bad" and "the user got a warning", so we're kind of awkwardly trying to derive it from our database + # In addition, sometimes someone posts a lot of bad comments and only gets modhatted for one of them + # That doesn't mean the other comments weren't bad + # It just means we picked the worst one + # So we ignore comments near the actual modhat time + + commentresults = {} + for uid in reported_user_ids: + # For each user, figure out when modhats happened + # this is slow but whatever + modhat_times = [] + for modhat in modhats.values(): + if comments[modhat.parent_comment_id].author_id != uid: + continue + + modhat_times.append(modhat.created_utc) + + usernote_times = [] + for usernote in usernotes_raw: + if usernote.reference_user != uid: + continue + + usernote_times.append(usernote.created_utc) + + # For each comment . . . + for comment in comments.values(): + if comment.author_id != uid: + continue + + if comment.id in modhats: + modhat_comment = modhats[comment.id] + else: + modhat_comment = None + + # if the comment was modhatted *and* resulted in a negative usernote near the modhat time, it's bad + if modhat_comment is not None and next((time for time in usernote_times if abs(modhat_comment.created_utc - time) < 60 * 15), None) is not None: + commentresults[comment.id] = "bad" + # otherwise, if the comment was posted less than 48 hours before a negative usernote, we ignore it for processing on the assumption that it may just have been part of a larger warning + elif next((time for time in usernote_times if comment.created_utc < time and comment.created_utc + 48 * 60 * 60 > time), None) is not None: + commentresults[comment.id] = "ignored" + # otherwise, we call it not-bad + else: + commentresults[comment.id] = "notbad" + + # get per-user metadata + users = db.query(User) \ + .where(User.id.in_(reporting_user_ids)) \ + .options(sqlalchemy.orm.load_only('id', 'username', 'admin_level')) + users = {user.id: {"username": user.username, "admin": user.admin_level != 0} for user in users} + + user_accuracy = defaultdict(lambda: CONFIG_default_user_accuracy) + + # Do an update loop! + for lid in range(0, 100): + + # Accumulated weight/badness, taking admin flags into account + # This is used for training + comment_weight_admin = defaultdict(lambda: 0) + comment_badness_admin = defaultdict(lambda: 0) + + # Accumulated weight/badness, not taking admin flags into account + # This is used for output and display + comment_weight_user = defaultdict(lambda: 0) + comment_badness_user = defaultdict(lambda: 0) + + # accumulate modhat weights + for cid in reported_comment_ids: + result = commentresults[cid] + + if result == "ignored": + # I guess we'll just let the users decide? + continue + + if result == "bad": + comment_weight_admin[cid] += CONFIG_modhat_weight + comment_badness_admin[cid] += CONFIG_modhat_weight + + if result == "notbad": + comment_weight_admin[cid] += CONFIG_modhat_weight + + # accumulate volunteer weights + for key, value in records_compiled.items(): + uid, cid = key + + # Calculate how much to weight a user; highly inaccurate users are not inverted! They just don't get contribution + # (losers) + userweight_user = userweight_from_user_accuracy(user_accuracy[uid]); + + if users[uid]["admin"]: + userweight_admin = CONFIG_admin_volunteer_weight + else: + userweight_admin = userweight_user + + bad, weight = evaluate_badness_of(value["result"]) + + # Accumulate these to our buffers + comment_weight_admin[cid] += userweight_admin * weight + comment_weight_user[cid] += userweight_user * weight + + if bad: + comment_badness_admin[cid] += userweight_admin * weight + comment_badness_user[cid] += userweight_user * weight + + # Calculated badnesses, both taking admins into account and not doing so, and "theoretical idea" versus a conversative view designed to be more skeptical of low-weighted comments + comment_calculated_badness_admin = {cid: calculate_final_comment_badness(comment_badness_admin[cid], comment_weight_admin[cid], False) for cid in reported_comment_ids} + comment_calculated_badness_admin_conservative = {cid: calculate_final_comment_badness(comment_badness_admin[cid], comment_weight_admin[cid], True) for cid in reported_comment_ids} + comment_calculated_badness_user = {cid: calculate_final_comment_badness(comment_badness_user[cid], comment_weight_user[cid], False) for cid in reported_comment_ids} + comment_calculated_badness_user_conservative = {cid: calculate_final_comment_badness(comment_badness_user[cid], comment_weight_user[cid], True) for cid in reported_comment_ids} + + # go through user submissions and count up how good users seem to be at this + user_correctness_weight = defaultdict(lambda: CONFIG_new_user_damping) + user_correctness_value = defaultdict(lambda: CONFIG_default_user_accuracy * user_correctness_weight[0]) + + for key, value in records_compiled.items(): + uid, cid = key + + # if this is "ignored", I don't trust that we have a real answer, so we just skip it for training purposes + if commentresults[cid] == "ignored": + continue + + bad, weight = evaluate_badness_of(value["result"]) + + correctness, weight = evaluate_correctness_single(bad, weight, comment_calculated_badness_admin[cid]) + + user_correctness_weight[uid] += weight + user_correctness_value[uid] += correctness * weight + + # calculate new correctnesses + for uid in reporting_user_ids: + target_user_correctness = user_correctness_value[uid] / user_correctness_weight[uid] + + # lerp slowly to the new values + user_accuracy[uid] = lerp(user_accuracy[uid], target_user_correctness, CONFIG_user_correctness_lerp) + + if diagnostics: + # debug print + commentscores = [{ + "link": f"https://themotte.org/comment/{cid}", + "badness": comment_calculated_badness_admin[cid], + "badnessuser": comment_calculated_badness_user[cid], + "badnessusercons": comment_calculated_badness_user_conservative[cid], + "participation": comment_weight_user[cid], + "mh": commentresults[cid]} for cid in reported_comment_ids] + commentscores.sort(key = lambda item: item["badnessusercons"] + item["badnessuser"] / 100) + print(tabulate(commentscores, headers = "keys")) + + results = [{ + "user": f"https://themotte.org/@{users[uid]['username']}", + "accuracy": user_accuracy[uid], + "submit": users_compiled[uid]["submit"], + "nonsubmit": sum(users_compiled[uid].values()) - users_compiled[uid]["submit"], + "admin": users[uid]["admin"] and "Admin" or "", + } for uid in reporting_user_ids] + results.sort(key = lambda k: k["accuracy"]) + print(tabulate(results, headers = "keys")) + + dbg_commentdump(89681, records_compiled, users, user_accuracy) + print(calculate_final_comment_badness(comment_badness_user[89681], comment_weight_user[89681], True)) + + #dbg_userdump(131, records_compiled, users, comment_calculated_badness_user) + + # Shove all this in the database, yaaay + # Conditional needed because sqlalchemy breaks if you try passing it zero data + if len(user_accuracy) > 0: + db.query(User) \ + .where(User.id.in_([id for id in user_accuracy.keys()])) \ + .update({ + User.volunteer_janitor_correctness: sqlalchemy.sql.case( + user_accuracy, + value = User.id, + ) + }) + db.commit() + + # We don't bother recalculating comment confidences here; it's a pain to do it and they shouldn't change much + + logging.info("Finished full janitor recalculation") + +@app.cli.command('volunteer_janitor_recalc') +def volunteer_janitor_recalc_cmd(): + volunteer_janitor_recalc(db_session(), diagnostics = True) + +def volunteer_janitor_recalc_cron(ctx:TaskRunContext): + volunteer_janitor_recalc(ctx.db) + +def volunteer_janitor_recalc_all_comments(db: Session): + # may as well do this first + volunteer_janitor_recalc(db) + + # I'm not sure of the details here, but there seems to be some session-related caching cruft left around + # so let's just nuke that + db.expire_all() + + # going through all the comments piecemeal like this is hilariously efficient, but this entire system gets run exactly once ever, so, okay + for comment in alive_it(db.query(Comment).join(Comment.reports)): + update_comment_badness(db, comment.id) + + db.commit() + +@app.cli.command('volunteer_janitor_recalc_all_comments') +def volunteer_janitor_recalc_all_comments_cmd(): + volunteer_janitor_recalc_all_comments(db_session()) diff --git a/files/helpers/math.py b/files/helpers/math.py new file mode 100644 index 000000000..85d95a8ef --- /dev/null +++ b/files/helpers/math.py @@ -0,0 +1,15 @@ + +def remap(input: float, smin: float, smax: float, emin: float, emax: float) -> float: + t = (input - smin) / (smax - smin) + return (1 - t) * emin + t * emax + +def clamp(input: float, min: float, max: float) -> float: + if input < min: return min + if input > max: return max + return input + +def saturate(input: float) -> float: + return clamp(input, 0, 1) + +def lerp(a: float, b: float, t: float) -> float: + return (1 - t) * a + t * b diff --git a/files/helpers/volunteer_janitor.py b/files/helpers/volunteer_janitor.py new file mode 100644 index 000000000..5f91fc59b --- /dev/null +++ b/files/helpers/volunteer_janitor.py @@ -0,0 +1,87 @@ + +from files.classes.comment import Comment +from files.classes.volunteer_janitor import VolunteerJanitorRecord, VolunteerJanitorResult +from files.helpers.math import saturate, remap + +# Returns (IsBad, Confidence) +def evaluate_badness_of(choice): + if choice == VolunteerJanitorResult.Warning: + return True, 1 + if choice == VolunteerJanitorResult.Ban: + return True, 1 + + # treating this like a low-weight bad response + if choice == VolunteerJanitorResult.Bad: + return True, 0.5 + + # treating this like a low-weight not-bad response + if choice == VolunteerJanitorResult.Neutral: + return False, 0.5 + + return False, 1 + +def userweight_from_user_accuracy(accuracy): + return saturate(remap(accuracy, 0.5, 1, 0, 1)) + +def calculate_final_comment_badness(comment_total, comment_weight, conservative): + if not conservative: + # insert an artificial 50%-badness confident vote, to prevent us from ever reaching 1.0 or 0.0 + return (comment_total + 0.5) / (comment_weight + 1.0) + + if comment_weight == 0: + # INSUFFICIENT DATA FOR A MEANINGFUL ANSWER + return 0.5 + + original_badness = calculate_final_comment_badness(comment_total, comment_weight, False) + if original_badness > 0.5: + # fake a not-bad vote, with the confidence being the opposite of our confidence in it being bad + # don't let it invert though + forged_weight = 1.0 - original_badness + calculated_badness = max(comment_total / (comment_weight + forged_weight), 0.5) + else: + # fake a bad vote, with the confidence being the opposite of our confidence in it being not-bad + # don't let it invert though + forged_weight = original_badness + calculated_badness = min((comment_total + forged_weight) / (comment_weight + forged_weight), 0.5) + + return calculated_badness + +def update_comment_badness(db, cid, diagnostics: bool = False): + # Recalculate the comment's confidence values + # This probably does more SQL queries than it should + records = db.query(VolunteerJanitorRecord) \ + .where(VolunteerJanitorRecord.comment_id == cid) \ + .order_by(VolunteerJanitorRecord.recorded_utc) + + user_has_pending = {} + earliest_submission = {} + + for rec in records: + if rec.result == VolunteerJanitorResult.Pending: + user_has_pending[rec.user_id] = True + else: + if rec.user_id in user_has_pending: + if rec.user_id not in earliest_submission or earliest_submission[rec.user_id].recorded_utc > rec.recorded_utc: + earliest_submission[rec.user_id] = rec + + badness = 0 + weight = 0 + + for submission in earliest_submission.values(): + userweight_user = userweight_from_user_accuracy(submission.user.volunteer_janitor_correctness); + submission_bad, submission_weight = evaluate_badness_of(submission.result) + + additive_weight = submission_weight * userweight_user + + weight += additive_weight + if submission_bad: + badness += additive_weight + + comment_badness = calculate_final_comment_badness(badness, weight, True) + + db.query(Comment) \ + .where(Comment.id == cid) \ + .update({Comment.volunteer_janitor_badness: comment_badness}) + + if diagnostics: + print(f"Updated comment {cid} to {comment_badness}") diff --git a/files/routes/volunteer_janitor.py b/files/routes/volunteer_janitor.py index 707610d4e..31130a22b 100644 --- a/files/routes/volunteer_janitor.py +++ b/files/routes/volunteer_janitor.py @@ -5,6 +5,7 @@ from files.classes.comment import Comment from files.classes.flags import CommentFlag from files.classes.user import User from files.classes.volunteer_janitor import VolunteerJanitorRecord, VolunteerJanitorResult +from files.helpers.volunteer_janitor import update_comment_badness from files.routes.volunteer_common import VolunteerDuty from flask import g import pprint @@ -93,4 +94,7 @@ def submitted(v: User, key: str, val: str) -> None: record.recorded_utc = sqlalchemy.func.now() record.result = VolunteerJanitorResult(int(val)) g.db.add(record) + + update_comment_badness(g.db, key) + g.db.commit() diff --git a/files/templates/component/comment/user_info.html b/files/templates/component/comment/user_info.html index 79b398712..f063c2cb1 100644 --- a/files/templates/component/comment/user_info.html +++ b/files/templates/component/comment/user_info.html @@ -27,7 +27,18 @@ {% if c.bannedfor %} {% endif %} - {% if v and c.filter_state == 'reported' and v.can_manage_reports() %}{{c.active_flags(v)}} Reports{% endif %} + {% if v and c.filter_state == 'reported' and v.can_manage_reports() %} + {{c.active_flags(v)}} Reports + + {% if c.volunteer_janitor_is_unknown() %} + Unknown + {% elif c.volunteer_janitor_is_notbad() %} + Not-Bad (confidence {{c.volunteer_janitor_confidence()}}) + {% elif c.volunteer_janitor_is_bad() %} + Bad (confidence {{c.volunteer_janitor_confidence()}}) + {% endif %} + + {% endif %} {% if c.over_18 %}+18{% endif %} {% if v and v.admin_level >= 2 and c.author.shadowbanned %}{% endif %} {% if c.is_pinned %} diff --git a/migrations/versions/2023_04_22_10_48_16_b2373d7b1b84_add_persistent_volunteer_janitor_data_.py b/migrations/versions/2023_04_22_10_48_16_b2373d7b1b84_add_persistent_volunteer_janitor_data_.py new file mode 100644 index 000000000..fc58c7ee4 --- /dev/null +++ b/migrations/versions/2023_04_22_10_48_16_b2373d7b1b84_add_persistent_volunteer_janitor_data_.py @@ -0,0 +1,38 @@ +"""add persistent volunteer-janitor data fields + +Revision ID: b2373d7b1b84 +Revises: 0cd3a7ebef3f +Create Date: 2023-04-22 10:48:16.476497+00:00 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b2373d7b1b84' +down_revision = '0cd3a7ebef3f' +branch_labels = None +depends_on = None + + +def upgrade(): + # manually adjusted to introduce our intended defaults + op.add_column('comments', sa.Column('volunteer_janitor_badness', sa.Float())) + op.execute("UPDATE comments SET volunteer_janitor_badness = 0.5") + op.alter_column('comments', 'volunteer_janitor_badness', nullable=False) + + op.add_column('users', sa.Column('volunteer_janitor_correctness', sa.Float())) + op.execute("UPDATE users SET volunteer_janitor_correctness = 0") + op.alter_column('users', 'volunteer_janitor_correctness', nullable=False) + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.drop_column('volunteer_janitor_correctness') + + with op.batch_alter_table('comments', schema=None) as batch_op: + batch_op.drop_column('volunteer_janitor_badness') + + # ### end Alembic commands ### diff --git a/migrations/versions/2023_04_22_11_33_50_6403c9151c12_recalculate_all_volunteer_janitor_.py b/migrations/versions/2023_04_22_11_33_50_6403c9151c12_recalculate_all_volunteer_janitor_.py new file mode 100644 index 000000000..16767b8ac --- /dev/null +++ b/migrations/versions/2023_04_22_11_33_50_6403c9151c12_recalculate_all_volunteer_janitor_.py @@ -0,0 +1,26 @@ +"""recalculate all volunteer janitor ephemeral data + +Revision ID: 6403c9151c12 +Revises: b2373d7b1b84 +Create Date: 2023-04-22 11:33:50.049868+00:00 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.orm.session import Session + +from files.commands.volunteer_janitor_recalc import volunteer_janitor_recalc_all_comments + +# revision identifiers, used by Alembic. +revision = '6403c9151c12' +down_revision = 'b2373d7b1b84' +branch_labels = None +depends_on = None + + +def upgrade(): + volunteer_janitor_recalc_all_comments(Session(bind=op.get_bind())) + + +def downgrade(): + pass diff --git a/poetry.lock b/poetry.lock index b6de3c2d7..9e61ae763 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,17 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + +[[package]] +name = "about-time" +version = "4.2.1" +description = "Easily measure timing and throughput of code blocks, with beautiful human friendly representations." +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "about-time-4.2.1.tar.gz", hash = "sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece"}, + {file = "about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341"}, +] + [[package]] name = "alembic" version = "1.8.1" @@ -5,6 +19,10 @@ description = "A database migration tool for SQLAlchemy." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "alembic-1.8.1-py3-none-any.whl", hash = "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4"}, + {file = "alembic-1.8.1.tar.gz", hash = "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa"}, +] [package.dependencies] Mako = "*" @@ -13,6 +31,22 @@ SQLAlchemy = ">=1.3.0" [package.extras] tz = ["python-dateutil"] +[[package]] +name = "alive-progress" +version = "3.1.1" +description = "A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!" +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "alive-progress-3.1.1.tar.gz", hash = "sha256:6d9ad57add9c1341aa57d93e6370c76c9fc0c9fdc631e1c56b48e59daa32106e"}, + {file = "alive_progress-3.1.1-py3-none-any.whl", hash = "sha256:5c1e075ff56230bda6bf4104c471c4ea8eaeb24af9e5e1696dcbcfc5b503255a"}, +] + +[package.dependencies] +about-time = "4.2.1" +grapheme = "0.6.0" + [[package]] name = "async-timeout" version = "4.0.2" @@ -20,14 +54,22 @@ description = "Timeout context manager for asyncio programs" category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] [[package]] name = "attrs" version = "22.1.0" description = "Classes Without Boilerplate" -category = "main" +category = "dev" optional = false python-versions = ">=3.5" +files = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -42,6 +84,10 @@ description = "Screen-scraping library" category = "main" optional = false python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] [package.dependencies] soupsieve = ">1.2" @@ -57,6 +103,10 @@ description = "The bidirectional mapping library for Python." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "bidict-0.22.0-py3-none-any.whl", hash = "sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0"}, + {file = "bidict-0.22.0.tar.gz", hash = "sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8"}, +] [[package]] name = "bleach" @@ -65,6 +115,10 @@ description = "An easy safelist-based HTML-sanitizing tool." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, + {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, +] [package.dependencies] packaging = "*" @@ -78,6 +132,10 @@ description = "Fast, simple object-to-object and broadcast signaling" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "blinker-1.5-py2.py3-none-any.whl", hash = "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36"}, + {file = "blinker-1.5.tar.gz", hash = "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"}, +] [[package]] name = "brotli" @@ -86,1028 +144,7 @@ description = "Python bindings for the Brotli compression library" category = "main" optional = false python-versions = "*" - -[[package]] -name = "cachelib" -version = "0.9.0" -description = "A collection of cache libraries in the same API interface." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "certifi" -version = "2022.9.24" -description = "Python package for providing Mozilla's CA Bundle." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "cffi" -version = "1.15.1" -description = "Foreign Function Interface for Python calling C code." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" - -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -category = "main" -optional = false -python-versions = "*" - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - -[[package]] -name = "contourpy" -version = "1.0.6" -description = "Python library for calculating contours of 2D quadrilateral grids" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -numpy = ">=1.16" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["docutils (<0.18)", "sphinx (<=5.2.0)", "sphinx-rtd-theme"] -test = ["Pillow", "flake8", "isort", "matplotlib", "pytest"] -test-minimal = ["pytest"] -test-no-codebase = ["Pillow", "matplotlib", "pytest"] - -[[package]] -name = "cycler" -version = "0.11.0" -description = "Composable style cycles" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "deprecated" -version = "1.2.13" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] - -[[package]] -name = "exceptiongroup" -version = "1.0.4" -description = "Backport of PEP 654 (exception groups)" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "flask" -version = "2.2.2" -description = "A simple framework for building complex web applications." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=8.0" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.2.2" - -[package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] - -[[package]] -name = "flask-caching" -version = "2.0.1" -description = "Adds caching support to Flask applications." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cachelib = ">=0.9.0" -Flask = "<3" - -[[package]] -name = "flask-compress" -version = "1.13" -description = "Compress responses in your Flask app with gzip, deflate or brotli." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -brotli = "*" -flask = "*" - -[[package]] -name = "flask-httpauth" -version = "4.7.0" -description = "HTTP authentication for Flask routes" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -flask = "*" - -[[package]] -name = "flask-limiter" -version = "2.7.0" -description = "Rate limiting for flask applications" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Flask = ">=2" -limits = ">=2.3" -rich = ">=12,<13" -typing-extensions = ">=4" - -[package.extras] -memcached = ["limits[memcached]"] -mongodb = ["limits[mongodb]"] -redis = ["limits[redis]"] - -[[package]] -name = "flask-mail" -version = "0.9.1" -description = "Flask extension for sending email" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -blinker = "*" -Flask = "*" - -[[package]] -name = "flask-migrate" -version = "3.1.0" -description = "SQLAlchemy database migrations for Flask applications using Alembic." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -alembic = ">=0.7" -Flask = ">=0.9" -Flask-SQLAlchemy = ">=1.0" - -[[package]] -name = "flask-profiler" -version = "1.8.1" -description = "API endpoint profiler for Flask framework" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -Flask = "*" -Flask-HTTPAuth = "*" -simplejson = "*" - -[[package]] -name = "flask-socketio" -version = "5.3.1" -description = "Socket.IO integration for Flask applications" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -Flask = ">=0.9" -python-socketio = ">=5.0.2" - -[[package]] -name = "flask-sqlalchemy" -version = "3.0.2" -description = "Add SQLAlchemy support to your Flask application." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -Flask = ">=2.2" -SQLAlchemy = ">=1.4.18" - -[[package]] -name = "fonttools" -version = "4.38.0" -description = "Tools to manipulate font files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "scipy"] -lxml = ["lxml (>=4.0,<5)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=14.0.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "gevent" -version = "22.10.2" -description = "Coroutine-based network library" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" - -[package.dependencies] -cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} -greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\""} -setuptools = "*" -"zope.event" = "*" -"zope.interface" = "*" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] -docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0)"] -recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"] -test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"] - -[[package]] -name = "gevent-websocket" -version = "0.10.1" -description = "Websocket handler for the gevent pywsgi server, a Python network library" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -gevent = "*" - -[[package]] -name = "greenlet" -version = "2.0.1" -description = "Lightweight in-process concurrent programming" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" - -[package.extras] -docs = ["Sphinx", "docutils (<0.18)"] -test = ["faulthandler", "objgraph", "psutil"] - -[[package]] -name = "gunicorn" -version = "20.1.0" -description = "WSGI HTTP Server for UNIX" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -setuptools = ">=3.0" - -[package.extras] -eventlet = ["eventlet (>=0.24.1)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -tornado = ["tornado (>=0.2)"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "itsdangerous" -version = "2.1.2" -description = "Safely pass data to untrusted environments and back." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "kiwisolver" -version = "1.4.4" -description = "A fast implementation of the Cassowary constraint solver" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "limits" -version = "2.7.1" -description = "Rate limiting utilities" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -deprecated = ">=1.2" -packaging = ">=21,<22" -setuptools = "*" -typing-extensions = "*" - -[package.extras] -all = ["coredis (>=3.4.0,<5)", "emcache (>=0.6.1)", "motor (>=2.5,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>3,<5)", "redis (>3,<5.0.0)", "redis (>=4.2.0)"] -async-memcached = ["emcache (>=0.6.1)"] -async-mongodb = ["motor (>=2.5,<4)"] -async-redis = ["coredis (>=3.4.0,<5)"] -memcached = ["pymemcache (>3,<5.0.0)"] -mongodb = ["pymongo (>3,<5)"] -redis = ["redis (>3,<5.0.0)"] -rediscluster = ["redis (>=4.2.0)"] - -[[package]] -name = "lxml" -version = "4.9.1" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html5 = ["html5lib"] -htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.7)"] - -[[package]] -name = "mako" -version = "1.2.3" -description = "A super-fast templating language that borrows the best ideas from the existing templating languages." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=0.9.2" - -[package.extras] -babel = ["Babel"] -lingua = ["lingua"] -testing = ["pytest"] - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "matplotlib" -version = "3.6.2" -description = "Python plotting package" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.0.1" -numpy = ">=1.19" -packaging = ">=20.0" -pillow = ">=6.2.0" -pyparsing = ">=2.2.1" -python-dateutil = ">=2.7" -setuptools_scm = ">=7" - -[[package]] -name = "mistletoe" -version = "0.9.0" -description = "A fast, extensible Markdown parser in pure Python." -category = "main" -optional = false -python-versions = "~=3.5" - -[[package]] -name = "numpy" -version = "1.23.4" -description = "NumPy is the fundamental package for array computing with Python." -category = "main" -optional = false -python-versions = ">=3.8" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pillow" -version = "9.3.0" -description = "Python Imaging Library (Fork)" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "psutil" -version = "5.9.4" -description = "Cross-platform lib for process and system monitoring in Python." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] - -[[package]] -name = "psycopg2-binary" -version = "2.9.5" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pusher-push-notifications" -version = "2.0.1" -description = "Pusher Push Notifications Python server SDK" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pyjwt = ">1.1.0,<3" -requests = ">2.5.0,<3" -six = ">1.4.0,<2" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pygments" -version = "2.13.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pyjwt" -version = "2.6.0" -description = "JSON Web Token implementation in Python" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pyotp" -version = "2.7.0" -description = "Python One Time Password Library" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "main" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "7.2.0" -description = "pytest: simple powerful testing with Python" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-dotenv" -version = "1.0.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -category = "main" -optional = false -python-versions = ">=3.8" - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] -name = "python-engineio" -version = "4.3.4" -description = "Engine.IO server and client for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -asyncio-client = ["aiohttp (>=3.4)"] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] - -[[package]] -name = "python-socketio" -version = "5.7.2" -description = "Socket.IO server and client for Python" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -bidict = ">=0.21.0" -python-engineio = ">=4.3.0" - -[package.extras] -asyncio-client = ["aiohttp (>=3.4)"] -client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] - -[[package]] -name = "qrcode" -version = "7.3.1" -description = "QR Code image generator" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -all = ["pillow", "pytest", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"] -dev = ["pytest", "tox"] -maintainer = ["zest.releaser[recommended]"] -pil = ["pillow"] -test = ["pytest", "pytest-cov"] - -[[package]] -name = "redis" -version = "4.3.4" -description = "Python client for Redis database and key-value store" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -async-timeout = ">=4.0.2" -deprecated = ">=1.2.3" -packaging = ">=20.4" - -[package.extras] -hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "main" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -version = "12.6.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "main" -optional = false -python-versions = ">=3.6.3,<4.0.0" - -[package.dependencies] -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "setuptools" -version = "65.6.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "setuptools-scm" -version = "7.0.5" -description = "the blessed package to manage your versions by scm tags" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -packaging = ">=20.0" -setuptools = "*" -tomli = ">=1.0.0" -typing-extensions = "*" - -[package.extras] -test = ["pytest (>=6.2)", "virtualenv (>20)"] -toml = ["setuptools (>=42)"] - -[[package]] -name = "simplejson" -version = "3.17.6" -description = "Simple, fast, extensible JSON encoder/decoder for Python" -category = "main" -optional = false -python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "soupsieve" -version = "2.3.2.post1" -description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "sqlalchemy" -version = "1.4.43" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} - -[package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "superlance" -version = "2.0.0" -description = "superlance plugins for supervisord" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -supervisor = "*" - -[[package]] -name = "supervisor" -version = "4.2.5" -description = "A system for controlling process state under UNIX" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -setuptools = "*" - -[package.extras] -testing = ["pytest", "pytest-cov"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "ua-parser" -version = "0.16.1" -description = "Python port of Browserscope's user agent parser" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "urllib3" -version = "1.26.12" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "user-agents" -version = "2.2.0" -description = "A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings." -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -ua-parser = ">=0.10.0" - -[[package]] -name = "uuid" -version = "1.30" -description = "UUID object and generation functions (Python 2.3 or higher)" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "webencodings" -version = "0.5.1" -description = "Character encoding aliases for legacy web content" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "webptools" -version = "0.0.9" -description = "webptools is a Webp image conversion package for python" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -uuid = "*" - -[[package]] -name = "werkzeug" -version = "2.2.2" -description = "The comprehensive WSGI web application library." -category = "main" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog"] - -[[package]] -name = "wrapt" -version = "1.14.1" -description = "Module for decorators, wrappers and monkey patching." -category = "main" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "yattag" -version = "1.14.0" -description = "Generate HTML or XML in a pythonic way. Pure python alternative to web template engines.Can fill HTML forms with default values and error messages." -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "zope.event" -version = "4.5.0" -description = "Very basic event publishing system" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["Sphinx"] -test = ["zope.testrunner"] - -[[package]] -name = "zope.interface" -version = "5.5.1" -description = "Interfaces for Python" -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["Sphinx", "repoze.sphinx.autointerface"] -test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] -testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] - -[metadata] -lock-version = "1.1" -python-versions = "~3.10" # updating to 3.11 causes instability; see https://github.com/themotte/rDrama/issues/446 -content-hash = "0607803380bd7078b955572621d5f794e7eeb3fa08a5794c17fac0e14e980431" - -[metadata.files] -alembic = [ - {file = "alembic-1.8.1-py3-none-any.whl", hash = "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4"}, - {file = "alembic-1.8.1.tar.gz", hash = "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa"}, -] -async-timeout = [ - {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, - {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -beautifulsoup4 = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, -] -bidict = [ - {file = "bidict-0.22.0-py3-none-any.whl", hash = "sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0"}, - {file = "bidict-0.22.0.tar.gz", hash = "sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8"}, -] -bleach = [ - {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, - {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, -] -blinker = [ - {file = "blinker-1.5-py2.py3-none-any.whl", hash = "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36"}, - {file = "blinker-1.5.tar.gz", hash = "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"}, -] -brotli = [ +files = [ {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, @@ -1191,15 +228,39 @@ brotli = [ {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, ] -cachelib = [ + +[[package]] +name = "cachelib" +version = "0.9.0" +description = "A collection of cache libraries in the same API interface." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "cachelib-0.9.0-py3-none-any.whl", hash = "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3"}, {file = "cachelib-0.9.0.tar.gz", hash = "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5"}, ] -certifi = [ + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, ] -cffi = [ + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, @@ -1265,23 +326,75 @@ cffi = [ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, ] -charset-normalizer = [ + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" +files = [ {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] -click = [ + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] -colorama = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -commonmark = [ + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, ] -contourpy = [ + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + +[[package]] +name = "contourpy" +version = "1.0.6" +description = "Python library for calculating contours of 2D quadrilateral grids" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "contourpy-1.0.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6"}, {file = "contourpy-1.0.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb"}, {file = "contourpy-1.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b"}, @@ -1352,62 +465,269 @@ contourpy = [ {file = "contourpy-1.0.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675"}, {file = "contourpy-1.0.6.tar.gz", hash = "sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142"}, ] -cycler = [ + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["docutils (<0.18)", "sphinx (<=5.2.0)", "sphinx-rtd-theme"] +test = ["Pillow", "flake8", "isort", "matplotlib", "pytest"] +test-minimal = ["pytest"] +test-no-codebase = ["Pillow", "matplotlib", "pytest"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, ] -deprecated = [ + +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, ] -exceptiongroup = [ + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] + +[[package]] +name = "exceptiongroup" +version = "1.0.4" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, ] -flask = [ + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flask" +version = "2.2.2" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"}, ] -flask-caching = [ + +[package.dependencies] +click = ">=8.0" +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.2.2" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-caching" +version = "2.0.1" +description = "Adds caching support to Flask applications." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Flask-Caching-2.0.1.tar.gz", hash = "sha256:10df200a03f032af60077befe41779dd94898b67c82040d34e87210b71ba2638"}, {file = "Flask_Caching-2.0.1-py3-none-any.whl", hash = "sha256:703df847cbe904d8ddffd5f5fb320e236a31cb7bebac4a93d6b1701dd16dbf37"}, ] -flask-compress = [ + +[package.dependencies] +cachelib = ">=0.9.0" +Flask = "<3" + +[[package]] +name = "flask-compress" +version = "1.13" +description = "Compress responses in your Flask app with gzip, deflate or brotli." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "Flask-Compress-1.13.tar.gz", hash = "sha256:ee96f18bf9b00f2deb4e3406ca4a05093aa80e2ef0578525a3b4d32ecdff129d"}, {file = "Flask_Compress-1.13-py3-none-any.whl", hash = "sha256:1128f71fbd788393ce26830c51f8b5a1a7a4d085e79a21a5cddf4c057dcd559b"}, ] -flask-httpauth = [ + +[package.dependencies] +brotli = "*" +flask = "*" + +[[package]] +name = "flask-httpauth" +version = "4.7.0" +description = "HTTP authentication for Flask routes" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "Flask-HTTPAuth-4.7.0.tar.gz", hash = "sha256:f7199e7bad20d5b68b3f0b62bddfca7637c55087e9d02f605ae26e0de479fd94"}, {file = "Flask_HTTPAuth-4.7.0-py3-none-any.whl", hash = "sha256:a237e4c8ec1d339298a0eb88e5af2ca36117949b621631649462800e57e1ba37"}, ] -flask-limiter = [ + +[package.dependencies] +flask = "*" + +[[package]] +name = "flask-limiter" +version = "2.7.0" +description = "Rate limiting for flask applications" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Flask-Limiter-2.7.0.tar.gz", hash = "sha256:a83164ab3fd264c7e565eeebd1f825cfd014ef4972cc742dbcf0d221e3c38092"}, {file = "Flask_Limiter-2.7.0-py3-none-any.whl", hash = "sha256:805989ef98549385148916f92cc5d73b70c6f8fabda1cdff6e8678fab8d64d24"}, ] -flask-mail = [ + +[package.dependencies] +Flask = ">=2" +limits = ">=2.3" +rich = ">=12,<13" +typing-extensions = ">=4" + +[package.extras] +memcached = ["limits[memcached]"] +mongodb = ["limits[mongodb]"] +redis = ["limits[redis]"] + +[[package]] +name = "flask-mail" +version = "0.9.1" +description = "Flask extension for sending email" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "Flask-Mail-0.9.1.tar.gz", hash = "sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41"}, ] -flask-migrate = [ + +[package.dependencies] +blinker = "*" +Flask = "*" + +[[package]] +name = "flask-migrate" +version = "3.1.0" +description = "SQLAlchemy database migrations for Flask applications using Alembic." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Flask-Migrate-3.1.0.tar.gz", hash = "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9"}, {file = "Flask_Migrate-3.1.0-py3-none-any.whl", hash = "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897"}, ] -flask-profiler = [ + +[package.dependencies] +alembic = ">=0.7" +Flask = ">=0.9" +Flask-SQLAlchemy = ">=1.0" + +[[package]] +name = "flask-profiler" +version = "1.8.1" +description = "API endpoint profiler for Flask framework" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "flask_profiler-1.8.1-py3-none-any.whl", hash = "sha256:56b51e47c98e9f36150fafffd449b075c58a9b404389a071d405222a35183758"}, {file = "flask_profiler-1.8.1.tar.gz", hash = "sha256:fc9f2875a4f22223ddc04ffacd75792854162c4cdbef165598a51f898521ac51"}, ] -flask-socketio = [ + +[package.dependencies] +Flask = "*" +Flask-HTTPAuth = "*" +simplejson = "*" + +[[package]] +name = "flask-socketio" +version = "5.3.1" +description = "Socket.IO integration for Flask applications" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Flask-SocketIO-5.3.1.tar.gz", hash = "sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9"}, {file = "Flask_SocketIO-5.3.1-py3-none-any.whl", hash = "sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353"}, ] -flask-sqlalchemy = [ + +[package.dependencies] +Flask = ">=0.9" +python-socketio = ">=5.0.2" + +[[package]] +name = "flask-sqlalchemy" +version = "3.0.2" +description = "Add SQLAlchemy support to your Flask application." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Flask-SQLAlchemy-3.0.2.tar.gz", hash = "sha256:16199f5b3ddfb69e0df2f52ae4c76aedbfec823462349dabb21a1b2e0a2b65e9"}, {file = "Flask_SQLAlchemy-3.0.2-py3-none-any.whl", hash = "sha256:7d0cd9cf73e64a996bb881a1ebd01633fc5a6d11c36ea27f7b5e251dc45476e7"}, ] -fonttools = [ + +[package.dependencies] +Flask = ">=2.2" +SQLAlchemy = ">=1.4.18" + +[[package]] +name = "fonttools" +version = "4.38.0" +description = "Tools to manipulate font files" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "fonttools-4.38.0-py3-none-any.whl", hash = "sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb"}, {file = "fonttools-4.38.0.zip", hash = "sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1"}, ] -gevent = [ + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=14.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "gevent" +version = "22.10.2" +description = "Coroutine-based network library" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" +files = [ {file = "gevent-22.10.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:97cd42382421779f5d82ec5007199e8a84aa288114975429e4fd0a98f2290f10"}, {file = "gevent-22.10.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1e1286a76f15b5e15f1e898731d50529e249529095a032453f2c101af3fde71c"}, {file = "gevent-22.10.2-cp27-cp27m-win32.whl", hash = "sha256:59b47e81b399d49a5622f0f503c59f1ce57b7705306ea0196818951dfc2f36c8"}, @@ -1461,11 +781,58 @@ gevent = [ {file = "gevent-22.10.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a47a4e77e2bc668856aad92a0b8de7ee10768258d93cd03968e6c7ba2e832f76"}, {file = "gevent-22.10.2.tar.gz", hash = "sha256:1ca01da176ee37b3527a2702f7d40dbc9ffb8cfc7be5a03bfa4f9eec45e55c46"}, ] -gevent-websocket = [ + +[package.dependencies] +cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\""} +setuptools = "*" +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"] +test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"] + +[[package]] +name = "gevent-websocket" +version = "0.10.1" +description = "Websocket handler for the gevent pywsgi server, a Python network library" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, {file = "gevent_websocket-0.10.1-py3-none-any.whl", hash = "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242"}, ] -greenlet = [ + +[package.dependencies] +gevent = "*" + +[[package]] +name = "grapheme" +version = "0.6.0" +description = "Unicode grapheme helpers" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "grapheme-0.6.0.tar.gz", hash = "sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca"}, +] + +[package.extras] +test = ["pytest", "sphinx", "sphinx-autobuild", "twine", "wheel"] + +[[package]] +name = "greenlet" +version = "2.0.1" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ {file = "greenlet-2.0.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c"}, {file = "greenlet-2.0.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515"}, {file = "greenlet-2.0.1-cp27-cp27m-win32.whl", hash = "sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a"}, @@ -1527,27 +894,94 @@ greenlet = [ {file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"}, {file = "greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"}, ] -gunicorn = [ + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["faulthandler", "objgraph", "psutil"] + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, ] -idna = [ + +[package.dependencies] +setuptools = ">=3.0" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" +files = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -iniconfig = [ + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" +files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -itsdangerous = [ + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, ] -jinja2 = [ + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] -kiwisolver = [ + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, @@ -1617,11 +1051,43 @@ kiwisolver = [ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, ] -limits = [ + +[[package]] +name = "limits" +version = "2.7.1" +description = "Rate limiting utilities" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "limits-2.7.1-py3-none-any.whl", hash = "sha256:38fb215ce5185f23c02548481272cabbea92a47b19ac6d4c85b0117085172002"}, {file = "limits-2.7.1.tar.gz", hash = "sha256:cdb58ef59e90d582589a0f5a8dd36ee00995342679109d64df92b060507d8f03"}, ] -lxml = [ + +[package.dependencies] +deprecated = ">=1.2" +packaging = ">=21,<22" +setuptools = "*" +typing-extensions = "*" + +[package.extras] +all = ["coredis (>=3.4.0,<5)", "emcache (>=0.6.1)", "motor (>=2.5,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>3,<5)", "redis (>3,<5.0.0)", "redis (>=4.2.0)"] +async-memcached = ["emcache (>=0.6.1)"] +async-mongodb = ["motor (>=2.5,<4)"] +async-redis = ["coredis (>=3.4.0,<5)"] +memcached = ["pymemcache (>3,<5.0.0)"] +mongodb = ["pymongo (>3,<5)"] +redis = ["redis (>3,<5.0.0)"] +rediscluster = ["redis (>=4.2.0)"] + +[[package]] +name = "lxml" +version = "4.9.1" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +files = [ {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"}, {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"}, {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"}, @@ -1693,11 +1159,41 @@ lxml = [ {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"}, {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"}, ] -mako = [ + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "mako" +version = "1.2.3" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Mako-1.2.3-py3-none-any.whl", hash = "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f"}, {file = "Mako-1.2.3.tar.gz", hash = "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd"}, ] -markupsafe = [ + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, @@ -1739,7 +1235,15 @@ markupsafe = [ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] -matplotlib = [ + +[[package]] +name = "matplotlib" +version = "3.6.2" +description = "Python plotting package" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ {file = "matplotlib-3.6.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5"}, {file = "matplotlib-3.6.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267"}, {file = "matplotlib-3.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c"}, @@ -1782,11 +1286,38 @@ matplotlib = [ {file = "matplotlib-3.6.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1"}, {file = "matplotlib-3.6.2.tar.gz", hash = "sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990"}, ] -mistletoe = [ + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.19" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.2.1" +python-dateutil = ">=2.7" + +[[package]] +name = "mistletoe" +version = "0.9.0" +description = "A fast, extensible Markdown parser in pure Python." +category = "main" +optional = false +python-versions = "~=3.5" +files = [ {file = "mistletoe-0.9.0-py3-none-any.whl", hash = "sha256:11316e2fe0be422a8248293ad0efbee9ad0c6f3683b2f45bc6b989ea17a68c74"}, {file = "mistletoe-0.9.0.tar.gz", hash = "sha256:3cb96d78226d08f0d3bf09efcaf330d23902492006e18b2c06558e8b86bf7faf"}, ] -numpy = [ + +[[package]] +name = "numpy" +version = "1.23.4" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" +files = [ {file = "numpy-1.23.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2"}, {file = "numpy-1.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f"}, {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71"}, @@ -1816,11 +1347,30 @@ numpy = [ {file = "numpy-1.23.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962"}, {file = "numpy-1.23.4.tar.gz", hash = "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c"}, ] -packaging = [ + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -pillow = [ + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pillow" +version = "9.3.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, @@ -1883,11 +1433,35 @@ pillow = [ {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, ] -pluggy = [ + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] -psutil = [ + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "psutil" +version = "5.9.4" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, @@ -1903,7 +1477,18 @@ psutil = [ {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, ] -psycopg2-binary = [ + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "psycopg2-binary" +version = "2.9.5" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "psycopg2-binary-2.9.5.tar.gz", hash = "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c"}, {file = "psycopg2_binary-2.9.5-cp310-cp310-macosx_10_15_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85"}, {file = "psycopg2_binary-2.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632"}, @@ -1976,74 +1561,294 @@ psycopg2-binary = [ {file = "psycopg2_binary-2.9.5-cp39-cp39-win32.whl", hash = "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1"}, {file = "psycopg2_binary-2.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1"}, ] -pusher-push-notifications = [ + +[[package]] +name = "pusher-push-notifications" +version = "2.0.1" +description = "Pusher Push Notifications Python server SDK" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pusher_push_notifications-2.0.1-py2.py3-none-any.whl", hash = "sha256:78a2945e9b4430c43a5dde0f9b15b6008394b86903739ee4968ee339f67d4b7a"}, {file = "pusher_push_notifications-2.0.1.tar.gz", hash = "sha256:69fc3f56913aea0ad04999f5963c3c20894877af6c19a736cbc911dce0d5788d"}, ] -pycparser = [ + +[package.dependencies] +pyjwt = ">1.1.0,<3" +requests = ">2.5.0,<3" +six = ">1.4.0,<2" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] -pygments = [ + +[[package]] +name = "pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, ] -pyjwt = [ + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyjwt" +version = "2.6.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, ] -pyotp = [ + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pyotp" +version = "2.7.0" +description = "Python One Time Password Library" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "pyotp-2.7.0-py3-none-any.whl", hash = "sha256:2e746de4f15685878df6d022c5691627af9941eec18e0d513f05497f5fa7711f"}, {file = "pyotp-2.7.0.tar.gz", hash = "sha256:ce989faba0df77dc032b45e51c6cca42bcf20896c8d3d1e7cd759a53dc7d6cb5"}, ] -pyparsing = [ + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" +files = [ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] -pytest = [ + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.2.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] -python-dateutil = [ + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -python-dotenv = [ + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, ] -python-engineio = [ + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-engineio" +version = "4.3.4" +description = "Engine.IO server and client for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "python-engineio-4.3.4.tar.gz", hash = "sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae"}, {file = "python_engineio-4.3.4-py3-none-any.whl", hash = "sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c"}, ] -python-socketio = [ + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-socketio" +version = "5.7.2" +description = "Socket.IO server and client for Python" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "python-socketio-5.7.2.tar.gz", hash = "sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097"}, {file = "python_socketio-5.7.2-py3-none-any.whl", hash = "sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3"}, ] -qrcode = [ + +[package.dependencies] +bidict = ">=0.21.0" +python-engineio = ">=4.3.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "qrcode" +version = "7.3.1" +description = "QR Code image generator" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "qrcode-7.3.1.tar.gz", hash = "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578"}, ] -redis = [ + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +all = ["pillow", "pytest", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"] +dev = ["pytest", "tox"] +maintainer = ["zest.releaser[recommended]"] +pil = ["pillow"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "redis" +version = "4.3.4" +description = "Python client for Redis database and key-value store" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, ] -requests = [ + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +packaging = ">=20.4" + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" +files = [ {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] -rich = [ + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "12.6.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.6.3,<4.0.0" +files = [ {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, ] -setuptools = [ + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, ] -setuptools-scm = [ - {file = "setuptools_scm-7.0.5-py3-none-any.whl", hash = "sha256:7930f720905e03ccd1e1d821db521bff7ec2ac9cf0ceb6552dd73d24a45d3b02"}, - {file = "setuptools_scm-7.0.5.tar.gz", hash = "sha256:031e13af771d6f892b941adb6ea04545bbf91ebc5ce68c78aaf3fff6e1fb4844"}, -] -simplejson = [ + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "simplejson" +version = "3.17.6" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +category = "main" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "simplejson-3.17.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a89acae02b2975b1f8e4974cb8cdf9bf9f6c91162fb8dec50c259ce700f2770a"}, {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:82ff356ff91be0ab2293fc6d8d262451eb6ac4fd999244c4b5f863e049ba219c"}, {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0de783e9c2b87bdd75b57efa2b6260c24b94605b5c9843517577d40ee0c3cc8a"}, @@ -2106,15 +1911,39 @@ simplejson = [ {file = "simplejson-3.17.6-cp39-cp39-win_amd64.whl", hash = "sha256:3fe87570168b2ae018391e2b43fbf66e8593a86feccb4b0500d134c998983ccc"}, {file = "simplejson-3.17.6.tar.gz", hash = "sha256:cf98038d2abf63a1ada5730e91e84c642ba6c225b0198c3684151b1f80c5f8a6"}, ] -six = [ + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -soupsieve = [ + +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.6" +files = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, ] -sqlalchemy = [ + +[[package]] +name = "sqlalchemy" +version = "1.4.43" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ {file = "SQLAlchemy-1.4.43-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:491d94879f9ec0dea7e1cb053cd9cc65a28d2467960cf99f7b3c286590406060"}, {file = "SQLAlchemy-1.4.43-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eeb55a555eef1a9607c1635bbdddd0b8a2bb9713bcb5bc8da1e8fae8ee46d1d8"}, {file = "SQLAlchemy-1.4.43-cp27-cp27m-win32.whl", hash = "sha256:7d6293010aa0af8bd3b0c9993259f8979db2422d6abf85a31d70ec69cb2ee4dc"}, @@ -2157,49 +1986,210 @@ sqlalchemy = [ {file = "SQLAlchemy-1.4.43-cp39-cp39-win_amd64.whl", hash = "sha256:a7fa3e57a7b0476fbcba72b231150503d53dbcbdd23f4a86be5152912a923b6e"}, {file = "SQLAlchemy-1.4.43.tar.gz", hash = "sha256:c628697aad7a141da8fc3fd81b4874a711cc84af172e1b1e7bbfadf760446496"}, ] -superlance = [ + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""} + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "superlance" +version = "2.0.0" +description = "superlance plugins for supervisord" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "superlance-2.0.0-py2.py3-none-any.whl", hash = "sha256:7826dca1f272ad3cd2302a4e8d71c6573614367a713b1273ba6af230a5019d29"}, {file = "superlance-2.0.0.tar.gz", hash = "sha256:afe221c60924eb056a2f2cac5bdbca1ff49462fa6e3c949cad34e507a76a18bf"}, ] -supervisor = [ + +[package.dependencies] +supervisor = "*" + +[[package]] +name = "supervisor" +version = "4.2.5" +description = "A system for controlling process state under UNIX" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "supervisor-4.2.5-py2.py3-none-any.whl", hash = "sha256:2ecaede32fc25af814696374b79e42644ecaba5c09494c51016ffda9602d0f08"}, {file = "supervisor-4.2.5.tar.gz", hash = "sha256:34761bae1a23c58192281a5115fb07fbf22c9b0133c08166beffc70fed3ebc12"}, ] -tomli = [ + +[package.dependencies] +setuptools = "*" + +[package.extras] +testing = ["pytest", "pytest-cov"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -ua-parser = [ + +[[package]] +name = "ua-parser" +version = "0.16.1" +description = "Python port of Browserscope's user agent parser" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "ua-parser-0.16.1.tar.gz", hash = "sha256:ed3efc695f475ffe56248c9789b3016247e9c20e3556cfa4d5aadc78ab4b26c6"}, {file = "ua_parser-0.16.1-py2.py3-none-any.whl", hash = "sha256:f97126300df8ac0f8f2c9d8559669532d626a1af529265fd253cba56e73ab36e"}, ] -urllib3 = [ + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" +files = [ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, ] -user-agents = [ + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "user-agents" +version = "2.2.0" +description = "A library to identify devices (phones, tablets) and their capabilities by parsing browser user agent strings." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "user-agents-2.2.0.tar.gz", hash = "sha256:d36d25178db65308d1458c5fa4ab39c9b2619377010130329f3955e7626ead26"}, {file = "user_agents-2.2.0-py3-none-any.whl", hash = "sha256:a98c4dc72ecbc64812c4534108806fb0a0b3a11ec3fd1eafe807cee5b0a942e7"}, ] -uuid = [ + +[package.dependencies] +ua-parser = ">=0.10.0" + +[[package]] +name = "uuid" +version = "1.30" +description = "UUID object and generation functions (Python 2.3 or higher)" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "uuid-1.30.tar.gz", hash = "sha256:1f87cc004ac5120466f36c5beae48b4c48cc411968eed0eaecd3da82aa96193f"}, ] -webencodings = [ + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, ] -webptools = [ + +[[package]] +name = "webptools" +version = "0.0.9" +description = "webptools is a Webp image conversion package for python" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "webptools-0.0.9.tar.gz", hash = "sha256:7393b53dc6b7bb38294a93ec5d82e009cb0160eec749bf1f53249c81fba63918"}, ] -werkzeug = [ + +[package.dependencies] +uuid = "*" + +[[package]] +name = "werkzeug" +version = "2.2.2" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, ] -wrapt = [ + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, @@ -2265,14 +2255,45 @@ wrapt = [ {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, ] -yattag = [ + +[[package]] +name = "yattag" +version = "1.14.0" +description = "Generate HTML or XML in a pythonic way. Pure python alternative to web template engines.Can fill HTML forms with default values and error messages." +category = "main" +optional = false +python-versions = "*" +files = [ {file = "yattag-1.14.0.tar.gz", hash = "sha256:5731a31cb7452c0c6930dd1a284e0170b39eee959851a2aceb8d6af4134a5fa8"}, ] -"zope.event" = [ + +[[package]] +name = "zope.event" +version = "4.5.0" +description = "Very basic event publishing system" +category = "main" +optional = false +python-versions = "*" +files = [ {file = "zope.event-4.5.0-py2.py3-none-any.whl", hash = "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42"}, {file = "zope.event-4.5.0.tar.gz", hash = "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"}, ] -"zope.interface" = [ + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx"] +test = ["zope.testrunner"] + +[[package]] +name = "zope.interface" +version = "5.5.1" +description = "Interfaces for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ {file = "zope.interface-5.5.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:dd4b9251e95020c3d5d104b528dbf53629d09c146ce9c8dfaaf8f619ae1cce35"}, {file = "zope.interface-5.5.1-cp27-cp27m-win32.whl", hash = "sha256:061a41a3f96f076686d7f1cb87f3deec6f0c9f0325dcc054ac7b504ae9bb0d82"}, {file = "zope.interface-5.5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:7f2e4ebe0a000c5727ee04227cf0ff5ae612fe599f88d494216e695b1dac744d"}, @@ -2313,3 +2334,16 @@ yattag = [ {file = "zope.interface-5.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:8343536ea4ee15d6525e3e726bb49ffc3f2034f828a49237a36be96842c06e7c"}, {file = "zope.interface-5.5.1.tar.gz", hash = "sha256:6d678475fdeb11394dc9aaa5c564213a1567cc663082e0ee85d52f78d1fbaab2"}, ] + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[metadata] +lock-version = "2.0" +python-versions = "~3.10" # updating to 3.11 causes instability; see https://github.com/themotte/rDrama/issues/446 +content-hash = "8be89e30e9cbd8607184f0fa3f96d18dd7e48b47e9a6cd41440f13c807cdcec6" diff --git a/pyproject.toml b/pyproject.toml index 1412532c2..78f2e79fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,12 +39,14 @@ yattag = "*" webptools = "*" supervisor = "*" superlance = "*" +alive-progress = "*" [tool.poetry.group.dev] optional = true [tool.poetry.group.dev.dependencies] pytest = "*" +tabulate = "*" [build-system] requires = ["poetry-core>=1.0.0"]