diff --git a/amanuensis/backend/article.py b/amanuensis/backend/article.py index 932f70b..53e82af 100644 --- a/amanuensis/backend/article.py +++ b/amanuensis/backend/article.py @@ -8,21 +8,17 @@ from amanuensis.db import * from amanuensis.errors import ArgumentError -def create( - db: DbContext, - lexicon_id: int, - user_id: int, - character_id: int) -> Article: +def create(db: DbContext, lexicon_id: int, user_id: int, character_id: int) -> Article: """ Create a new article in a lexicon. """ # Verify argument types are correct if not isinstance(lexicon_id, int): - raise ArgumentError('lexicon_id') + raise ArgumentError("lexicon_id") if not isinstance(user_id, int): - raise ArgumentError('user_id') + raise ArgumentError("user_id") if character_id is not None and not isinstance(character_id, int): - raise ArgumentError('character_id') + raise ArgumentError("character_id") # Check that the user is a member of this lexicon mem: Membership = db( @@ -31,31 +27,30 @@ def create( .where(Membership.lexicon_id == lexicon_id) ).scalar_one_or_none() if not mem: - raise ArgumentError('User is not a member of lexicon') + raise ArgumentError("User is not a member of lexicon") # If the character id is provided, check that the user owns the character # and the character belongs to the lexicon if character_id is not None: character: Character = db( - select(Character) - .where(Character.id == character_id) + select(Character).where(Character.id == character_id) ).scalar_one_or_none() if not character: - raise ArgumentError('Character does not exist') + raise ArgumentError("Character does not exist") if character.user.id != user_id: - raise ArgumentError('Character is owned by the wrong player') + raise ArgumentError("Character is owned by the wrong player") if character.lexicon.id != lexicon_id: - raise ArgumentError('Character belongs to the wrong lexicon') + raise ArgumentError("Character belongs to the wrong lexicon") signature = character.signature else: - signature = '~Ersatz Scrivener' + signature = "~Ersatz Scrivener" new_article = Article( lexicon_id=lexicon_id, user_id=user_id, character_id=character_id, - title='Article title', - body=f'\n\n{signature}', + title="Article title", + body=f"\n\n{signature}", ) db.session.add(new_article) db.session.commit() diff --git a/amanuensis/backend/character.py b/amanuensis/backend/character.py index c8f04da..bedda1f 100644 --- a/amanuensis/backend/character.py +++ b/amanuensis/backend/character.py @@ -9,31 +9,28 @@ from amanuensis.errors import ArgumentError def create( - db: DbContext, - lexicon_id: int, - user_id: int, - name: str, - signature: str) -> Character: + db: DbContext, lexicon_id: int, user_id: int, name: str, signature: str +) -> Character: """ Create a new character for a user. """ # Verify argument types are correct if not isinstance(lexicon_id, int): - raise ArgumentError('lexicon_id') + raise ArgumentError("lexicon_id") if not isinstance(user_id, int): - raise ArgumentError('user_id') + raise ArgumentError("user_id") if not isinstance(name, str): - raise ArgumentError('name') + raise ArgumentError("name") if signature is not None and not isinstance(signature, str): - raise ArgumentError('signature') + raise ArgumentError("signature") # Verify character name is valid if not name.strip(): - raise ArgumentError('Character name cannot be blank') + raise ArgumentError("Character name cannot be blank") # If no signature is provided, use a default signature if not signature or not signature.strip(): - signature = f'~{name}' + signature = f"~{name}" # Check that the user is a member of this lexicon mem: Membership = db( @@ -42,7 +39,7 @@ def create( .where(Membership.lexicon_id == lexicon_id) ).scalar_one_or_none() if not mem: - raise ArgumentError('User is not a member of lexicon') + raise ArgumentError("User is not a member of lexicon") # Check that this user is below the limit for creating characters num_user_chars = db( @@ -50,8 +47,11 @@ def create( .where(Character.lexicon_id == lexicon_id) .where(Character.user_id == user_id) ).scalar() - if mem.lexicon.character_limit is not None and num_user_chars >= mem.lexicon.character_limit: - raise ArgumentError('User is at character limit') + if ( + mem.lexicon.character_limit is not None + and num_user_chars >= mem.lexicon.character_limit + ): + raise ArgumentError("User is at character limit") new_character = Character( lexicon_id=lexicon_id, diff --git a/amanuensis/backend/lexicon.py b/amanuensis/backend/lexicon.py index 7e20790..925f5e9 100644 --- a/amanuensis/backend/lexicon.py +++ b/amanuensis/backend/lexicon.py @@ -10,39 +10,34 @@ from amanuensis.db import DbContext, Lexicon from amanuensis.errors import ArgumentError -RE_ALPHANUM_DASH_UNDER = re.compile(r'^[A-Za-z0-9-_]*$') +RE_ALPHANUM_DASH_UNDER = re.compile(r"^[A-Za-z0-9-_]*$") -def create( - db: DbContext, - name: str, - title: str, - prompt: str) -> Lexicon: +def create(db: DbContext, name: str, title: str, prompt: str) -> Lexicon: """ Create a new lexicon. """ # Verify name if not isinstance(name, str): - raise ArgumentError('Lexicon name must be a string') + raise ArgumentError("Lexicon name must be a string") if not name.strip(): - raise ArgumentError('Lexicon name must not be blank') + raise ArgumentError("Lexicon name must not be blank") if not RE_ALPHANUM_DASH_UNDER.match(name): - raise ArgumentError('Lexicon name may only contain alphanumerics, dash, and underscore') + raise ArgumentError( + "Lexicon name may only contain alphanumerics, dash, and underscore" + ) # Verify title if title is not None and not isinstance(name, str): - raise ArgumentError('Lexicon name must be a string') + raise ArgumentError("Lexicon name must be a string") # Verify prompt if not isinstance(prompt, str): - raise ArgumentError('Lexicon prompt must be a string') + raise ArgumentError("Lexicon prompt must be a string") # Query the db to make sure the lexicon name isn't taken - if db( - select(func.count(Lexicon.id)) - .where(Lexicon.name == name) - ).scalar() > 0: - raise ArgumentError('Lexicon name is already taken') + if db(select(func.count(Lexicon.id)).where(Lexicon.name == name)).scalar() > 0: + raise ArgumentError("Lexicon name is already taken") new_lexicon = Lexicon( name=name, diff --git a/amanuensis/backend/membership.py b/amanuensis/backend/membership.py index c112b01..511b138 100644 --- a/amanuensis/backend/membership.py +++ b/amanuensis/backend/membership.py @@ -8,29 +8,28 @@ from amanuensis.db import DbContext, Membership from amanuensis.errors import ArgumentError -def create( - db: DbContext, - user_id: int, - lexicon_id: int, - is_editor: bool) -> Membership: +def create(db: DbContext, user_id: int, lexicon_id: int, is_editor: bool) -> Membership: """ Create a new user membership in a lexicon. """ # Verify argument types are correct if not isinstance(user_id, int): - raise ArgumentError('user_id') + raise ArgumentError("user_id") if not isinstance(lexicon_id, int): - raise ArgumentError('lexicon_id') + raise ArgumentError("lexicon_id") if not isinstance(is_editor, bool): - raise ArgumentError('is_editor') + raise ArgumentError("is_editor") # Verify user has not already joined lexicon - if db( - select(func.count(Membership.id)) - .where(Membership.user_id == user_id) - .where(Membership.lexicon_id == lexicon_id) - ).scalar() > 0: - raise ArgumentError('User is already a member of lexicon') + if ( + db( + select(func.count(Membership.id)) + .where(Membership.user_id == user_id) + .where(Membership.lexicon_id == lexicon_id) + ).scalar() + > 0 + ): + raise ArgumentError("User is already a member of lexicon") new_membership = Membership( user_id=user_id, diff --git a/amanuensis/backend/post.py b/amanuensis/backend/post.py index 9ce635f..53161b4 100644 --- a/amanuensis/backend/post.py +++ b/amanuensis/backend/post.py @@ -9,34 +9,27 @@ from sqlalchemy import select, func from amanuensis.db import DbContext, Post from amanuensis.errors import ArgumentError -def create( - db: DbContext, - lexicon_id: int, - user_id: int, - body: str) -> Post: + +def create(db: DbContext, lexicon_id: int, user_id: int, body: str) -> Post: """ Create a new post """ # Verify lexicon id if not isinstance(lexicon_id, int): - raise ArgumentError('Lexicon id must be an integer.') + raise ArgumentError("Lexicon id must be an integer.") # Verify user_id if not (isinstance(user_id, int) or user_id is None): - raise ArgumentError('User id must be an integer.') + raise ArgumentError("User id must be an integer.") # Verify body if not isinstance(body, str): - raise ArgumentError('Post body must be a string.') + raise ArgumentError("Post body must be a string.") if not body.strip(): - raise ArgumentError('Post body cannot be empty.') + raise ArgumentError("Post body cannot be empty.") - new_post = Post( - lexicon_id=lexicon_id, - user_id=user_id, - body=body - ) + new_post = Post(lexicon_id=lexicon_id, user_id=user_id, body=body) db.session.add(new_post) db.session.commit() return new_post diff --git a/amanuensis/backend/user.py b/amanuensis/backend/user.py index 6b30032..5411079 100644 --- a/amanuensis/backend/user.py +++ b/amanuensis/backend/user.py @@ -11,8 +11,8 @@ from amanuensis.db import DbContext, User from amanuensis.errors import ArgumentError -RE_NO_LETTERS = re.compile(r'^[0-9-_]*$') -RE_ALPHANUM_DASH_UNDER = re.compile(r'^[A-Za-z0-9-_]*$') +RE_NO_LETTERS = re.compile(r"^[0-9-_]*$") +RE_ALPHANUM_DASH_UNDER = re.compile(r"^[A-Za-z0-9-_]*$") def create( @@ -21,41 +21,41 @@ def create( password: str, display_name: str, email: str, - is_site_admin: bool) -> User: + is_site_admin: bool, +) -> User: """ Create a new user. """ # Verify username if not isinstance(username, str): - raise ArgumentError('Username must be a string') + raise ArgumentError("Username must be a string") if len(username) < 3 or len(username) > 32: - raise ArgumentError('Username must be between 3 and 32 characters') + raise ArgumentError("Username must be between 3 and 32 characters") if RE_NO_LETTERS.match(username): - raise ArgumentError('Username must contain a letter') + raise ArgumentError("Username must contain a letter") if not RE_ALPHANUM_DASH_UNDER.match(username): - raise ArgumentError('Username may only contain alphanumerics, dash, and underscore') + raise ArgumentError( + "Username may only contain alphanumerics, dash, and underscore" + ) # Verify password if not isinstance(password, str): - raise ArgumentError('Password must be a string') + raise ArgumentError("Password must be a string") # Verify display name if display_name is not None and not isinstance(display_name, str): - raise ArgumentError('Display name must be a string') + raise ArgumentError("Display name must be a string") # If display name is not provided, use the username if not display_name or not display_name.strip(): display_name = username # Verify email if not isinstance(email, str): - raise ArgumentError('Email must be a string') + raise ArgumentError("Email must be a string") # Query the db to make sure the username isn't taken - if db( - select(func.count(User.id)) - .where(User.username == username) - ).scalar() > 0: - raise ArgumentError('Username is already taken') + if db(select(func.count(User.id)).where(User.username == username)).scalar() > 0: + raise ArgumentError("Username is already taken") new_user = User( username=username, diff --git a/amanuensis/db/__init__.py b/amanuensis/db/__init__.py index ecec332..0a926d7 100644 --- a/amanuensis/db/__init__.py +++ b/amanuensis/db/__init__.py @@ -15,17 +15,17 @@ from .models import ( ) __all__ = [ - 'DbContext', - 'User', - 'Lexicon', - 'Membership', - 'Character', - 'ArticleState', - 'Article', - 'IndexType', - 'ArticleIndex', - 'ArticleIndexRule', - 'ArticleContentRuleType', - 'ArticleContentRule', - 'Post', -] \ No newline at end of file + "DbContext", + "User", + "Lexicon", + "Membership", + "Character", + "ArticleState", + "Article", + "IndexType", + "ArticleIndex", + "ArticleIndexRule", + "ArticleContentRuleType", + "ArticleContentRule", + "Post", +] diff --git a/amanuensis/db/database.py b/amanuensis/db/database.py index 988a6ba..254a488 100644 --- a/amanuensis/db/database.py +++ b/amanuensis/db/database.py @@ -8,22 +8,25 @@ from sqlalchemy.orm import sessionmaker # Define naming conventions for generated constraints -metadata = MetaData(naming_convention={ - "ix": "ix_%(column_0_label)s", - "uq": "uq_%(table_name)s_%(column_0_name)s", - "ck": "ck_%(table_name)s_%(constraint_name)s", - "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", - "pk": "pk_%(table_name)s" -}) +metadata = MetaData( + naming_convention={ + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", + } +) # Base class for ORM models ModelBase = declarative_base(metadata=metadata) -class DbContext(): +class DbContext: def __init__(self, db_uri, debug=False): # Create an engine and enable foreign key constraints in sqlite self.engine = create_engine(db_uri, echo=debug) + @event.listens_for(self.engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() diff --git a/amanuensis/db/models.py b/amanuensis/db/models.py index 1aca951..471e05a 100644 --- a/amanuensis/db/models.py +++ b/amanuensis/db/models.py @@ -28,15 +28,16 @@ class Uuid(TypeDecorator): """ A uuid backed by a char(32) field in sqlite. """ + impl = CHAR(32) def process_bind_param(self, value, dialect): if value is None: return value elif not isinstance(value, uuid.UUID): - return f'{uuid.UUID(value).int:32x}' + return f"{uuid.UUID(value).int:32x}" else: - return f'{value.int:32x}' + return f"{value.int:32x}" def process_result_value(self, value, dialect): if value is None: @@ -51,7 +52,8 @@ class User(ModelBase): """ Represents a single user of Amanuensis. """ - __tablename__ = 'user' + + __tablename__ = "user" ############# # User info # @@ -73,14 +75,14 @@ class User(ModelBase): email = Column(String, nullable=False) # Whether the user can access site admin functions - is_site_admin = Column(Boolean, nullable=False, server_default=text('FALSE')) + is_site_admin = Column(Boolean, nullable=False, server_default=text("FALSE")) #################### # History tracking # #################### # The timestamp the user was created - created = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')) + created = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP")) # The timestamp the user last logged in # This is NULL if the user has never logged in @@ -94,17 +96,18 @@ class User(ModelBase): # Foreign key relationships # ############################# - memberships = relationship('Membership', back_populates='user') - characters = relationship('Character', back_populates='user') - articles = relationship('Article', back_populates='user') - posts = relationship('Post', back_populates='user') + memberships = relationship("Membership", back_populates="user") + characters = relationship("Character", back_populates="user") + articles = relationship("Article", back_populates="user") + posts = relationship("Post", back_populates="user") class Lexicon(ModelBase): """ Represents a single game of Lexicon. """ - __tablename__ = 'lexicon' + + __tablename__ = "lexicon" ############# # Game info # @@ -128,10 +131,12 @@ class Lexicon(ModelBase): #################### # The timestamp the lexicon was created - created = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')) + created = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP")) # The timestamp of the last change in game state - last_updated = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')) + last_updated = Column( + DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP") + ) # The timestamp the first turn was started # This is NULL until the game starts @@ -221,23 +226,22 @@ class Lexicon(ModelBase): # Foreign key relationships # ############################# - memberships = relationship('Membership', back_populates='lexicon') - characters = relationship('Character', back_populates='lexicon') - articles = relationship('Article', back_populates='lexicon') - indexes = relationship('ArticleIndex', back_populates='lexicon') - index_rules = relationship('ArticleIndexRule', back_populates='lexicon') - content_rules = relationship('ArticleContentRule', back_populates='lexicon') - posts = relationship('Post', back_populates='lexicon') + memberships = relationship("Membership", back_populates="lexicon") + characters = relationship("Character", back_populates="lexicon") + articles = relationship("Article", back_populates="lexicon") + indexes = relationship("ArticleIndex", back_populates="lexicon") + index_rules = relationship("ArticleIndexRule", back_populates="lexicon") + content_rules = relationship("ArticleContentRule", back_populates="lexicon") + posts = relationship("Post", back_populates="lexicon") class Membership(ModelBase): """ Represents a user's participation in a Lexicon game. """ - __tablename__ = 'membership' - __table_args__ = ( - UniqueConstraint('user_id', 'lexicon_id'), - ) + + __tablename__ = "membership" + __table_args__ = (UniqueConstraint("user_id", "lexicon_id"),) ################### # Membership keys # @@ -247,17 +251,17 @@ class Membership(ModelBase): id = Column(Integer, primary_key=True) # The user who is a member of a lexicon - user_id = Column(Integer, ForeignKey('user.id'), nullable=False) + user_id = Column(Integer, ForeignKey("user.id"), nullable=False) # The lexicon of which the user is a member - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) #################### # History tracking # #################### # Timestamp the user joined the game - joined = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')) + joined = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP")) # Timestamp of the last time the user viewed the post feed # This is NULL if the player has never viewed posts @@ -268,7 +272,7 @@ class Membership(ModelBase): ################### # Whether the user can access editor functions - is_editor = Column(Boolean, nullable=False, server_default=text('FALSE')) + is_editor = Column(Boolean, nullable=False, server_default=text("FALSE")) ######################### # Notification settings # @@ -287,15 +291,16 @@ class Membership(ModelBase): # Foreign key relationships # ############################# - user = relationship('User', back_populates='memberships') - lexicon = relationship('Lexicon', back_populates='memberships') + user = relationship("User", back_populates="memberships") + lexicon = relationship("Lexicon", back_populates="memberships") class Character(ModelBase): """ Represents a character played by a uaser in a Lexicon game. """ - __tablename__ = 'character' + + __tablename__ = "character" ################## # Character info # @@ -308,10 +313,10 @@ class Character(ModelBase): public_id = Column(Uuid, nullable=False, unique=True, default=uuid.uuid4) # The lexicon to which this character belongs - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) # The user to whom this character belongs - user_id = Column(Integer, ForeignKey('user.id'), nullable=False) + user_id = Column(Integer, ForeignKey("user.id"), nullable=False) # The character's name name = Column(String, nullable=False) @@ -323,16 +328,17 @@ class Character(ModelBase): # Foreign key relationships # ############################# - user = relationship('User', back_populates='characters') - lexicon = relationship('Lexicon', back_populates='characters') - articles = relationship('Article', back_populates='character') - index_rules = relationship('ArticleIndexRule', back_populates='character') + user = relationship("User", back_populates="characters") + lexicon = relationship("Lexicon", back_populates="characters") + articles = relationship("Article", back_populates="character") + index_rules = relationship("ArticleIndexRule", back_populates="character") class ArticleState(enum.Enum): """ The step of the editorial process an article is in. """ + DRAFT = 0 SUBMITTED = 1 APPROVED = 2 @@ -342,7 +348,8 @@ class Article(ModelBase): """ Represents a single article in a lexicon. """ - __tablename__ = 'article' + + __tablename__ = "article" ################ # Article info # @@ -355,17 +362,17 @@ class Article(ModelBase): public_id = Column(Uuid, nullable=False, unique=True, default=uuid.uuid4) # The lexicon to which this article belongs - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) # The character who is the author of this article # If this is NULL, the article is written by Ersatz Scrivener - character_id = Column(Integer, ForeignKey('character.id'), nullable=True) + character_id = Column(Integer, ForeignKey("character.id"), nullable=True) # The user who owns this article - user_id = Column(Integer, ForeignKey('user.id'), nullable=False) + user_id = Column(Integer, ForeignKey("user.id"), nullable=False) # The article to which this is an addendum - addendum_to = Column(Integer, ForeignKey('article.id'), nullable=True) + addendum_to = Column(Integer, ForeignKey("article.id"), nullable=True) ################# # Article state # @@ -386,7 +393,9 @@ class Article(ModelBase): #################### # Timestamp the content of the article was last updated - last_updated = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')) + last_updated = Column( + DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP") + ) # Timestamp the article was last submitted # This is NULL until the article is submitted @@ -410,16 +419,17 @@ class Article(ModelBase): # Foreign key relationships # ############################# - lexicon = relationship('Lexicon', back_populates='articles') - character = relationship('Character', back_populates='articles') - user = relationship('User', back_populates='articles') - addenda = relationship('Article', backref=backref('parent', remote_side=[id])) + lexicon = relationship("Lexicon", back_populates="articles") + character = relationship("Character", back_populates="articles") + user = relationship("User", back_populates="articles") + addenda = relationship("Article", backref=backref("parent", remote_side=[id])) class IndexType(enum.Enum): """ The title-matching behavior of an article index. """ + CHAR = 0 RANGE = 1 PREFIX = 2 @@ -430,7 +440,8 @@ class ArticleIndex(ModelBase): """ Represents an index definition. """ - __tablename__ = 'article_index' + + __tablename__ = "article_index" ############## # Index info # @@ -440,7 +451,7 @@ class ArticleIndex(ModelBase): id = Column(Integer, primary_key=True) # The lexicon this index is in - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) # The index type index_type = Column(Enum(IndexType), nullable=False) @@ -462,8 +473,8 @@ class ArticleIndex(ModelBase): # Foreign key relationships # ############################# - lexicon = relationship('Lexicon', back_populates='indexes') - index_rules = relationship('ArticleIndexRule', back_populates='index') + lexicon = relationship("Lexicon", back_populates="indexes") + index_rules = relationship("ArticleIndexRule", back_populates="index") class ArticleIndexRule(ModelBase): @@ -472,7 +483,8 @@ class ArticleIndexRule(ModelBase): A character with multiple index rules may write in any index that satisfies a rule. A character with no index rules may write in any index. """ - __tablename__ = 'article_index_rule' + + __tablename__ = "article_index_rule" ################### # Index rule info # @@ -482,17 +494,17 @@ class ArticleIndexRule(ModelBase): id = Column(Integer, primary_key=True) # The lexicon of this index rule - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) #################### # Index rule scope # #################### # The character to whom this rule applies - character_id = Column(Integer, ForeignKey('character.id'), nullable=False) + character_id = Column(Integer, ForeignKey("character.id"), nullable=False) # The index to which the character is restricted - index_id = Column(Integer, ForeignKey('article_index.id'), nullable=False) + index_id = Column(Integer, ForeignKey("article_index.id"), nullable=False) # The turn in which this rule applies turn = Column(Integer, nullable=False) @@ -501,15 +513,16 @@ class ArticleIndexRule(ModelBase): # Foreign key relationships # ############################# - lexicon = relationship('Lexicon', back_populates='index_rules') - index = relationship('ArticleIndex', back_populates='index_rules') - character = relationship('Character', back_populates='index_rules') + lexicon = relationship("Lexicon", back_populates="index_rules") + index = relationship("ArticleIndex", back_populates="index_rules") + character = relationship("Character", back_populates="index_rules") class ArticleContentRuleType(enum.Enum): """ The possible article content rules. """ + # Whether characters can cite themselves ALLOW_SELF_CITE = 0 # Whether characters can write new articles instead of phantoms @@ -543,7 +556,8 @@ class ArticleContentRule(ModelBase): """ Represents a restriction on the content of an article for a turn. """ - __tablename__ = 'article_content_rule' + + __tablename__ = "article_content_rule" ##################### # Content rule info # @@ -553,7 +567,7 @@ class ArticleContentRule(ModelBase): id = Column(Integer, primary_key=True) # The lexicon of this content rule - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) ###################### # Content rule scope # @@ -577,14 +591,15 @@ class ArticleContentRule(ModelBase): # Foreign key relationships # ############################# - lexicon = relationship('Lexicon', back_populates='content_rules') + lexicon = relationship("Lexicon", back_populates="content_rules") class Post(ModelBase): """ Represents a post in the game feed. """ - __tablename__ = 'post' + + __tablename__ = "post" ############# # Post info # @@ -594,18 +609,18 @@ class Post(ModelBase): id = Column(Integer, primary_key=True) # The lexicon in which the post was made - lexicon_id = Column(Integer, ForeignKey('lexicon.id'), nullable=False) + lexicon_id = Column(Integer, ForeignKey("lexicon.id"), nullable=False) # The user who made the post # This may be NULL if the post was made by Amanuensis - user_id = Column(Integer, ForeignKey('user.id'), nullable=True) + user_id = Column(Integer, ForeignKey("user.id"), nullable=True) ################ # Post content # ################ # The timestamp the post was created - created = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')) + created = Column(DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP")) # The body of the post body = Column(Text, nullable=False) @@ -614,5 +629,5 @@ class Post(ModelBase): # Foreign key relationships # ############################# - user = relationship('User', back_populates='posts') - lexicon = relationship('Lexicon', back_populates='posts') + user = relationship("User", back_populates="posts") + lexicon = relationship("Lexicon", back_populates="posts") diff --git a/amanuensis/errors.py b/amanuensis/errors.py index dca7c6f..b6a9145 100644 --- a/amanuensis/errors.py +++ b/amanuensis/errors.py @@ -2,6 +2,7 @@ Submodule of custom exception types """ + class AmanuensisError(Exception): """Base class for exceptions in amanuensis""" diff --git a/poetry.lock b/poetry.lock index 4cf5c7e..ab419ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,11 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "atomicwrites" version = "1.4.0" @@ -8,17 +16,39 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "20.3.0" +version = "21.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] + +[[package]] +name = "black" +version = "21.5b2" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.8.1,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +python2 = ["typed-ast (>=1.4.2)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "click" @@ -38,17 +68,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "flask" -version = "1.1.2" +version = "1.1.4" description = "A simple framework for building complex web applications." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.dependencies] -click = ">=5.1" -itsdangerous = ">=0.24" -Jinja2 = ">=2.10.1" -Werkzeug = ">=0.15" +click = ">=5.1,<8.0" +itsdangerous = ">=0.24,<2.0" +Jinja2 = ">=2.10.1,<3.0" +Werkzeug = ">=0.15,<2.0" [package.extras] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] @@ -81,7 +111,7 @@ WTForms = "*" [[package]] name = "greenlet" -version = "1.0.0" +version = "1.1.0" description = "Lightweight in-process concurrent programming" category = "main" optional = false @@ -114,20 +144,28 @@ i18n = ["Babel (>=0.8)"] [[package]] name = "markupsafe" -version = "1.1.1" +version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" [[package]] name = "more-itertools" -version = "8.7.0" +version = "8.8.0" description = "More routines for operating on iterables, beyond itertools" category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "packaging" version = "20.9" @@ -139,6 +177,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pyparsing = ">=2.0.2" +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + [[package]] name = "pluggy" version = "0.13.1" @@ -188,9 +234,17 @@ wcwidth = "*" checkqa-mypy = ["mypy (==v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +[[package]] +name = "regex" +version = "2021.4.4" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "sqlalchemy" -version = "1.4.12" +version = "1.4.17" description = "Database Abstraction Library" category = "main" optional = false @@ -219,6 +273,14 @@ postgresql_psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql (<1)", "pymysql"] sqlcipher = ["sqlcipher3-binary"] +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + [[package]] name = "wcwidth" version = "0.2.5" @@ -258,16 +320,24 @@ locale = ["Babel (>=1.3)"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "48928fcd093c025ed3b9ed6153fd54310cbdefdaba133a57b4a2bfb6c9f5941f" +content-hash = "e81ba4ffeb172410ef80bd6500c0af4e3be6b800d9cfda85df2f6a5c74319d87" [metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] +black = [ + {file = "black-21.5b2-py3-none-any.whl", hash = "sha256:e5cf21ebdffc7a9b29d73912b6a6a9a4df4ce70220d523c21647da2eae0751ef"}, + {file = "black-21.5b2.tar.gz", hash = "sha256:1fc0e0a2c8ae7d269dfcf0c60a89afa299664f3e811395d40b1922dff8f854b5"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -278,8 +348,8 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] flask = [ - {file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, - {file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, + {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"}, + {file = "Flask-1.1.4.tar.gz", hash = "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196"}, ] flask-login = [ {file = "Flask-Login-0.5.0.tar.gz", hash = "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b"}, @@ -290,49 +360,55 @@ flask-wtf = [ {file = "Flask_WTF-0.14.3-py2.py3-none-any.whl", hash = "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2"}, ] greenlet = [ - {file = "greenlet-1.0.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2"}, - {file = "greenlet-1.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c"}, - {file = "greenlet-1.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203"}, - {file = "greenlet-1.0.0-cp27-cp27m-win32.whl", hash = "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476"}, - {file = "greenlet-1.0.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b"}, - {file = "greenlet-1.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379"}, - {file = "greenlet-1.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce"}, - {file = "greenlet-1.0.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c"}, - {file = "greenlet-1.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5"}, - {file = "greenlet-1.0.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f"}, - {file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6"}, - {file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683"}, - {file = "greenlet-1.0.0-cp35-cp35m-win32.whl", hash = "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70"}, - {file = "greenlet-1.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770"}, - {file = "greenlet-1.0.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee"}, - {file = "greenlet-1.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd"}, - {file = "greenlet-1.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a"}, - {file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d"}, - {file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2"}, - {file = "greenlet-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7"}, - {file = "greenlet-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be"}, - {file = "greenlet-1.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef"}, - {file = "greenlet-1.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128"}, - {file = "greenlet-1.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7"}, - {file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c"}, - {file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f"}, - {file = "greenlet-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c"}, - {file = "greenlet-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85"}, - {file = "greenlet-1.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7"}, - {file = "greenlet-1.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06"}, - {file = "greenlet-1.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36"}, - {file = "greenlet-1.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378"}, - {file = "greenlet-1.0.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196"}, - {file = "greenlet-1.0.0-cp38-cp38-win32.whl", hash = "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e"}, - {file = "greenlet-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c"}, - {file = "greenlet-1.0.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243"}, - {file = "greenlet-1.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df"}, - {file = "greenlet-1.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218"}, - {file = "greenlet-1.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7"}, - {file = "greenlet-1.0.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664"}, - {file = "greenlet-1.0.0-cp39-cp39-win32.whl", hash = "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139"}, - {file = "greenlet-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0"}, - {file = "greenlet-1.0.0.tar.gz", hash = "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8"}, + {file = "greenlet-1.1.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c"}, + {file = "greenlet-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3"}, + {file = "greenlet-1.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922"}, + {file = "greenlet-1.1.0-cp27-cp27m-win32.whl", hash = "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821"}, + {file = "greenlet-1.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6"}, + {file = "greenlet-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f"}, + {file = "greenlet-1.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56"}, + {file = "greenlet-1.1.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16"}, + {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a"}, + {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831"}, + {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22"}, + {file = "greenlet-1.1.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5"}, + {file = "greenlet-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47"}, + {file = "greenlet-1.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08"}, + {file = "greenlet-1.1.0-cp35-cp35m-win32.whl", hash = "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131"}, + {file = "greenlet-1.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5"}, + {file = "greenlet-1.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919"}, + {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e"}, + {file = "greenlet-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8"}, + {file = "greenlet-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb"}, + {file = "greenlet-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319"}, + {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05"}, + {file = "greenlet-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f"}, + {file = "greenlet-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a"}, + {file = "greenlet-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99"}, + {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da"}, + {file = "greenlet-1.1.0-cp38-cp38-win32.whl", hash = "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad"}, + {file = "greenlet-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8"}, + {file = "greenlet-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:7db68f15486d412b8e2cfcd584bf3b3a000911d25779d081cbbae76d71bd1a7e"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70bd1bb271e9429e2793902dfd194b653221904a07cbf207c3139e2672d17959"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f92731609d6625e1cc26ff5757db4d32b6b810d2a3363b0ff94ff573e5901f6f"}, + {file = "greenlet-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06d7ac89e6094a0a8f8dc46aa61898e9e1aec79b0f8b47b2400dd51a44dbc832"}, + {file = "greenlet-1.1.0-cp39-cp39-win32.whl", hash = "sha256:adb94a28225005890d4cf73648b5131e885c7b4b17bc762779f061844aabcc11"}, + {file = "greenlet-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa4230234d02e6f32f189fd40b59d5a968fe77e80f59c9c933384fe8ba535535"}, + {file = "greenlet-1.1.0.tar.gz", hash = "sha256:c87df8ae3f01ffb4483c796fe1b15232ce2b219f0b18126948616224d3f658ee"}, ] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, @@ -343,48 +419,57 @@ jinja2 = [ {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, ] markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, + {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] more-itertools = [ - {file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, - {file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, + {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"}, + {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, ] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, @@ -401,41 +486,84 @@ pytest = [ {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] +regex = [ + {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, + {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, + {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, + {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, + {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, + {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, + {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, + {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, + {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, + {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, + {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, + {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, + {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, + {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, + {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, + {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, + {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, +] sqlalchemy = [ - {file = "SQLAlchemy-1.4.12-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:8c71a80a5474e6e9c9bbf1957ab1c73cdece9d33cfb26d9ea6e7aed41f535cd6"}, - {file = "SQLAlchemy-1.4.12-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b1d513ebb16a204c87296d774c2317950191583b34032540948f20096b63efe4"}, - {file = "SQLAlchemy-1.4.12-cp27-cp27m-win32.whl", hash = "sha256:4b749cdedf1afb613c3d31235258110e1f36231c15df9b8b63b3f13c712e4790"}, - {file = "SQLAlchemy-1.4.12-cp27-cp27m-win_amd64.whl", hash = "sha256:b58f09f4ea42a92e0a8923f4598001f8935bd2ed0c4c6abb9903c5b4cd0d4015"}, - {file = "SQLAlchemy-1.4.12-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b4bf83b05056349265b40de37c836517649ea9edd174301072f5a58c7b374f94"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c94fe5ec27dec6a994293d1f194a97fcb904252526bbe72698229ec62c0f7281"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ac4a48e49e863a4d00d8a5ec94ff5540de1f5bcf96d8d54273a75c3278d8b4af"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e815a729b427bd997d681711dc0b22330e445a0a0c47e16b05d2038e814bd29f"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:aeb389136f3a39399ebb8e8ee17beba18d361cde9638059cfbf7e896354412b7"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0c839000817201310a51af390545d7b316fafd6969ef250dad0a6d28c025214d"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-win32.whl", hash = "sha256:1e8a884d766fcc918199576bf37f1870327582640fa3302489d7415d815be8a9"}, - {file = "SQLAlchemy-1.4.12-cp36-cp36m-win_amd64.whl", hash = "sha256:e11ccaa08975e414df6a16466377bb11af692b2a62255c3a70c0993cb2d7f2d7"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:deef50c730ddfb4169417a3a3b6393f1e90b0d5c1e62e1d090c1eb1132529f3f"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a21f41c4cdb76d7f68a6986b9f5c56bdc8eafbc366893d1031df0c367e832388"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:aec20f0ec5788bee91ecf667e9e30e5ed0add9233b63b0e34e916b21eb5bc850"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d5da8fff36593ac96dd3d60a4eb9495a142fb6d3f0ed23baf5567c0ef7aa9b47"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:a4c9c947fc08d2ac48116c64b7dfbac22b9896619cb74923ba59876504ff6256"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-win32.whl", hash = "sha256:4c8c335b072967da27fef54fb53e74fadadd7d2167c5eb98f0bfb4bfeb3a6948"}, - {file = "SQLAlchemy-1.4.12-cp37-cp37m-win_amd64.whl", hash = "sha256:01b610951c83452ee5e7d912c4ed9db4538b15d66e96ca6696ec38f0c5ce2908"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:6b77880e23d3758db7ad65732304ab1c3a42f0cd20505f4a211750862563a161"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f04acd3840bcf33f941b049e24aeef0be5145b2cd5489a89559c11be2d25e262"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:691568d8238c756011d97a655a76820715cbc0295b7d294aa2f1d62fb0be4361"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0646a4caab207279532ffd3f173b4756ae3863f3a94e369b7d1b82831a7ad433"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:2b35206c11c415448caf5b7abddbfac6acbe37f79832ae2d1be013f0dfe252ea"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-win32.whl", hash = "sha256:89e755688476b7a925554a1e8a756e0dd6124dfb8fac80470a90cd8424326bee"}, - {file = "SQLAlchemy-1.4.12-cp38-cp38-win_amd64.whl", hash = "sha256:1bc9ea9e54bbaf65fece8b719f56472748f75777806f4f5fadd8112a165eab19"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:1bdf65dc5263be4651aa34ebe07aa035c61421f145b0d43f4c0b1f3c33bec673"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f90a42db44427bf98128d823502e0af3f4b83f208e09a3d51df5c2cd7f2a76cf"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9047989b8645d8830067dddb2bda544c625419b22b0f546660fd0bfe73341f6"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b7ed6ce2e32a68a3b417a848a409ed5b7e4c8e5fa8911b06c77a6be1cc767658"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:5ffbd23ac4324e64a100310cd2cab6534f972ecf26bf3652e6847187c2e9e72d"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-win32.whl", hash = "sha256:ac7db7276c0807db73b58984d630404ab294c4ca59cf16157fdc15894dec4507"}, - {file = "SQLAlchemy-1.4.12-cp39-cp39-win_amd64.whl", hash = "sha256:ce5fc1099d194fbecc8d7c038c927d9daf75cbb83b3b314df3e43e308d67c33e"}, - {file = "SQLAlchemy-1.4.12.tar.gz", hash = "sha256:968e8cf7f269eaeed1b753cb5df4112be998c933df39421229fc7726c413672c"}, + {file = "SQLAlchemy-1.4.17-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c367ed95d41df584f412a9419b5ece85b0d6c2a08a51ae13ae47ef74ff9a9349"}, + {file = "SQLAlchemy-1.4.17-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdad4a33140b77df61d456922b7974c1f1bb2c35238f6809f078003a620c4734"}, + {file = "SQLAlchemy-1.4.17-cp27-cp27m-win32.whl", hash = "sha256:f1c68f7bd4a57ffdb85eab489362828dddf6cd565a4c18eda4c446c1d5d3059d"}, + {file = "SQLAlchemy-1.4.17-cp27-cp27m-win_amd64.whl", hash = "sha256:ee6e7ca09ff274c55d19a1e15ee6f884fa0230c0d9b8d22a456e249d08dee5bf"}, + {file = "SQLAlchemy-1.4.17-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5f00a2be7d777119e15ccfb5ba0b2a92e8a193959281089d79821a001095f80"}, + {file = "SQLAlchemy-1.4.17-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1dd77acbc19bee9c0ba858ff5e4e5d5c60895495c83b4df9bcdf4ad5e9b74f21"}, + {file = "SQLAlchemy-1.4.17-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5732858e56d32fa7e02468f4fd2d8f01ddf709e5b93d035c637762890f8ed8b6"}, + {file = "SQLAlchemy-1.4.17-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:949ac299903d2ed8419086f81847381184e2264f3431a33af4679546dcc87f01"}, + {file = "SQLAlchemy-1.4.17-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:196fb6bb2733834e506c925d7532f8eabad9d2304deef738a40846e54c31e236"}, + {file = "SQLAlchemy-1.4.17-cp36-cp36m-win32.whl", hash = "sha256:bde055c019e6e449ebc4ec61abd3e08690abeb028c7ada2a3b95d8e352b7b514"}, + {file = "SQLAlchemy-1.4.17-cp36-cp36m-win_amd64.whl", hash = "sha256:b0ad951a6e590bbcfbfeadc5748ef5ec8ede505a8119a71b235f7481cc08371c"}, + {file = "SQLAlchemy-1.4.17-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:82922a320d38d7d6aa3a8130523ec7e8c70fa95f7ca7d0fd6ec114b626e4b10b"}, + {file = "SQLAlchemy-1.4.17-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e133e2551fa99c75849848a4ac08efb79930561eb629dd7d2dc9b7ee05256e6"}, + {file = "SQLAlchemy-1.4.17-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7e45043fe11d503e1c3f9dcf5b42f92d122a814237cd9af68a11dae46ecfcae1"}, + {file = "SQLAlchemy-1.4.17-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:461a4ea803ce0834822f372617a68ac97f9fa1281f2a984624554c651d7c3ae1"}, + {file = "SQLAlchemy-1.4.17-cp37-cp37m-win32.whl", hash = "sha256:4d93b62e98248e3e1ac1e91c2e6ee1e7316f704be1f734338b350b6951e6c175"}, + {file = "SQLAlchemy-1.4.17-cp37-cp37m-win_amd64.whl", hash = "sha256:a2d225c8863a76d15468896dc5af36f1e196b403eb9c7e0151e77ffab9e7df57"}, + {file = "SQLAlchemy-1.4.17-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b59b2c0a3b1d93027f6b6b8379a50c354483fe1ebe796c6740e157bb2e06d39a"}, + {file = "SQLAlchemy-1.4.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7222f3236c280fab3a2d76f903b493171f0ffc29667538cc388a5d5dd0216a88"}, + {file = "SQLAlchemy-1.4.17-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4b09191ed22af149c07a880f309b7740f3f782ff13325bae5c6168a6aa57e715"}, + {file = "SQLAlchemy-1.4.17-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216ff28fe803885ceb5b131dcee6507d28d255808dd5bcffcb3b5fa75be2e102"}, + {file = "SQLAlchemy-1.4.17-cp38-cp38-win32.whl", hash = "sha256:dde05ae0987e43ec84e64d6722ce66305eda2a5e2b7d6fda004b37aabdfbb909"}, + {file = "SQLAlchemy-1.4.17-cp38-cp38-win_amd64.whl", hash = "sha256:bc89e37c359dcd4d75b744e5e81af128ba678aa2ecea4be957e80e6e958a1612"}, + {file = "SQLAlchemy-1.4.17-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4c5e20666b33b03bf7f14953f0deb93007bf8c1342e985bd7c7cf25f46fac579"}, + {file = "SQLAlchemy-1.4.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f63e1f531a8bf52184e2afb53648511f3f8534decb7575b483a583d3cd8d13ed"}, + {file = "SQLAlchemy-1.4.17-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7dc3d3285fb682316d580d84e6e0840fdd8ffdc05cb696db74b9dd746c729908"}, + {file = "SQLAlchemy-1.4.17-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c02d1771bb0e61bc9ced8f3b36b5714d9ece8fd4bdbe2a44a892574c3bbc3c"}, + {file = "SQLAlchemy-1.4.17-cp39-cp39-win32.whl", hash = "sha256:6fe1c8dc26bc0005439cb78ebc78772a22cccc773f5a0e67cb3002d791f53f0f"}, + {file = "SQLAlchemy-1.4.17-cp39-cp39-win_amd64.whl", hash = "sha256:7eb55d5583076c03aaf1510473fad2a61288490809049cb31028af56af7068ee"}, + {file = "SQLAlchemy-1.4.17.tar.gz", hash = "sha256:651cdb3adcee13624ba22d5ff3e96f91e16a115d2ca489ddc16a8e4c217e8509"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, diff --git a/pyproject.toml b/pyproject.toml index 8cd59b7..cf38106 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,21 @@ SQLAlchemy = "^1.4.12" [tool.poetry.dev-dependencies] pytest = "^5.2" +black = "^21.5b2" + +[tool.black] +extend-exclude = ''' +^/amanuensis/cli/.*| +^/amanuensis/config/.*| +^/amanuensis/lexicon/.*| +^/amanuensis/log/.*| +^/amanuensis/models/.*| +^/amanuensis/parser/.*| +^/amanuensis/resources/.*| +^/amanuensis/server/.*| +^/amanuensis/user/.*| +^/amanuensis/__main__.py +''' [tool.pytest.ini_options] addopts = "--show-capture=log" diff --git a/tests/conftest.py b/tests/conftest.py index dc6662a..68c8f78 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ import amanuensis.backend.user as userq @pytest.fixture def db(): """Provides an initialized database in memory.""" - db = DbContext('sqlite:///:memory:', debug=False) + db = DbContext("sqlite:///:memory:", debug=False) db.create_all() return db @@ -21,58 +21,66 @@ def db(): @pytest.fixture def make_user(db: DbContext): """Provides a factory function for creating users, with valid default values.""" - def user_factory(state={'nonce': 1}, **kwargs): + + def user_factory(state={"nonce": 1}, **kwargs): default_kwargs = { - 'username': f'test_user_{state["nonce"]}', - 'password': 'password', - 'display_name': None, - 'email': 'user@example.com', - 'is_site_admin': False, + "username": f'test_user_{state["nonce"]}', + "password": "password", + "display_name": None, + "email": "user@example.com", + "is_site_admin": False, } - state['nonce'] += 1 + state["nonce"] += 1 updated_kwargs = {**default_kwargs, **kwargs} return userq.create(db, **updated_kwargs) + return user_factory @pytest.fixture def make_lexicon(db: DbContext): """Provides a factory function for creating lexicons, with valid default values.""" - def lexicon_factory(state={'nonce': 1}, **kwargs): + + def lexicon_factory(state={"nonce": 1}, **kwargs): default_kwargs = { - 'name': f'Test_{state["nonce"]}', - 'title': None, - 'prompt': f'Test Lexicon game {state["nonce"]}' + "name": f'Test_{state["nonce"]}', + "title": None, + "prompt": f'Test Lexicon game {state["nonce"]}', } - state['nonce'] += 1 + state["nonce"] += 1 updated_kwargs = {**default_kwargs, **kwargs} return lexiq.create(db, **updated_kwargs) + return lexicon_factory @pytest.fixture def make_membership(db: DbContext): """Provides a factory function for creating memberships, with valid default values.""" + def membership_factory(**kwargs): default_kwargs = { - 'is_editor': False, + "is_editor": False, } updated_kwargs = {**default_kwargs, **kwargs} return memq.create(db, **updated_kwargs) + return membership_factory @pytest.fixture def make_character(db: DbContext): """Provides a factory function for creating characters, with valid default values.""" - def character_factory(state={'nonce': 1}, **kwargs): + + def character_factory(state={"nonce": 1}, **kwargs): default_kwargs = { - 'name': f'Character {state["nonce"]}', - 'signature': None, + "name": f'Character {state["nonce"]}', + "signature": None, } - state['nonce'] += 1 + state["nonce"] += 1 updated_kwargs = {**default_kwargs, **kwargs} return charq.create(db, **updated_kwargs) + return character_factory @@ -87,11 +95,8 @@ class TestFactory: @pytest.fixture def make( - db: DbContext, - make_user, - make_lexicon, - make_membership, - make_character) -> TestFactory: + db: DbContext, make_user, make_lexicon, make_membership, make_character +) -> TestFactory: """Fixture that groups all factory fixtures together.""" return TestFactory( db, @@ -109,6 +114,8 @@ def lexicon_with_editor(make): assert editor lexicon = make.lexicon() assert lexicon - membership = make.membership(user_id=editor.id, lexicon_id=lexicon.id, is_editor=True) + membership = make.membership( + user_id=editor.id, lexicon_id=lexicon.id, is_editor=True + ) assert membership return (lexicon, editor) diff --git a/tests/test_character.py b/tests/test_character.py index 0a1e2e5..55f33b1 100644 --- a/tests/test_character.py +++ b/tests/test_character.py @@ -9,33 +9,33 @@ def test_create_character(db: DbContext, lexicon_with_editor, make): """Test creating a character.""" lexicon, user = lexicon_with_editor kwargs = { - 'db': db, - 'user_id': user.id, - 'lexicon_id': lexicon.id, - 'name': 'Character Name', - 'signature': 'Signature', + "db": db, + "user_id": user.id, + "lexicon_id": lexicon.id, + "name": "Character Name", + "signature": "Signature", } # Bad argument types with pytest.raises(ArgumentError): - charq.create(**{**kwargs, 'name': b'bytestring'}) + charq.create(**{**kwargs, "name": b"bytestring"}) with pytest.raises(ArgumentError): - charq.create(**{**kwargs, 'name': None}) + charq.create(**{**kwargs, "name": None}) with pytest.raises(ArgumentError): - charq.create(**{**kwargs, 'signature': b'bytestring'}) + charq.create(**{**kwargs, "signature": b"bytestring"}) # Bad character name with pytest.raises(ArgumentError): - charq.create(**{**kwargs, 'name': ' '}) + charq.create(**{**kwargs, "name": " "}) # Signature is auto-populated - char = charq.create(**{**kwargs, 'signature': None}) + char = charq.create(**{**kwargs, "signature": None}) assert char.signature is not None # User must be in lexicon new_user = make.user() with pytest.raises(ArgumentError): - charq.create(**{**kwargs, 'user_id': new_user.id}) + charq.create(**{**kwargs, "user_id": new_user.id}) def test_character_limits(db: DbContext, lexicon_with_editor): @@ -47,27 +47,31 @@ def test_character_limits(db: DbContext, lexicon_with_editor): # Set character limit to one and create a character lexicon.character_limit = 1 db.session.commit() - char1 = charq.create(db, lexicon.id, user.id, 'Test Character 1', signature=None) - assert char1.id, 'Failed to create character 1' + char1 = charq.create(db, lexicon.id, user.id, "Test Character 1", signature=None) + assert char1.id, "Failed to create character 1" # Creating a second character should fail with pytest.raises(ArgumentError): - char2 = charq.create(db, lexicon.id, user.id, 'Test Character 2', signature=None) + char2 = charq.create( + db, lexicon.id, user.id, "Test Character 2", signature=None + ) assert char2 # Raising the limit to 2 should allow a second character lexicon.character_limit = 2 db.session.commit() - char2 = charq.create(db, lexicon.id, user.id, 'Test Character 2', signature=None) - assert char2.id, 'Failed to create character 2' + char2 = charq.create(db, lexicon.id, user.id, "Test Character 2", signature=None) + assert char2.id, "Failed to create character 2" # Creating a third character should fail with pytest.raises(ArgumentError): - char3 = charq.create(db, lexicon.id, user.id, 'Test Character 3', signature=None) + char3 = charq.create( + db, lexicon.id, user.id, "Test Character 3", signature=None + ) assert char3 # Setting the limit to null should allow a third character lexicon.character_limit = None db.session.commit() - char3 = charq.create(db, lexicon.id, user.id, 'Test Character 3', signature=None) - assert char3.id, 'Failed to create character 3' + char3 = charq.create(db, lexicon.id, user.id, "Test Character 3", signature=None) + assert char3.id, "Failed to create character 3" diff --git a/tests/test_lexicon.py b/tests/test_lexicon.py index b0dfe9b..0af9546 100644 --- a/tests/test_lexicon.py +++ b/tests/test_lexicon.py @@ -9,24 +9,20 @@ from amanuensis.errors import ArgumentError def test_create_lexicon(db: DbContext): """Test new game creation.""" - kwargs = { - 'name': 'Test', - 'title': None, - 'prompt': 'A test Lexicon game' - } + kwargs = {"name": "Test", "title": None, "prompt": "A test Lexicon game"} # Test name constraints with pytest.raises(ArgumentError): - lexiq.create(db, **{**kwargs, 'name': None}) + lexiq.create(db, **{**kwargs, "name": None}) with pytest.raises(ArgumentError): - lexiq.create(db, **{**kwargs, 'name': ''}) + lexiq.create(db, **{**kwargs, "name": ""}) with pytest.raises(ArgumentError): - lexiq.create(db, **{**kwargs, 'name': ' '}) + lexiq.create(db, **{**kwargs, "name": " "}) with pytest.raises(ArgumentError): - lexiq.create(db, **{**kwargs, 'name': '..'}) + lexiq.create(db, **{**kwargs, "name": ".."}) with pytest.raises(ArgumentError): - lexiq.create(db, **{**kwargs, 'name': '\x00'}) + lexiq.create(db, **{**kwargs, "name": "\x00"}) with pytest.raises(ArgumentError): - lexiq.create(db, **{**kwargs, 'name': 'space in name'}) + lexiq.create(db, **{**kwargs, "name": "space in name"}) # Validate that creation populates fields, including timestamps before = datetime.datetime.utcnow() - datetime.timedelta(seconds=1) diff --git a/tests/test_membership.py b/tests/test_membership.py index d478394..a86c79e 100644 --- a/tests/test_membership.py +++ b/tests/test_membership.py @@ -11,13 +11,13 @@ def test_create_membership(db: DbContext, make): """Test joining a game.""" # Set up a user and a lexicon new_user = make.user() - assert new_user.id, 'Failed to create user' + assert new_user.id, "Failed to create user" new_lexicon = make.lexicon() - assert new_lexicon.id, 'Failed to create lexicon' + assert new_lexicon.id, "Failed to create lexicon" # Add the user to the lexicon as an editor mem = memq.create(db, new_user.id, new_lexicon.id, True) - assert mem, 'Failed to create membership' + assert mem, "Failed to create membership" # Check that the user and lexicon are mutually visible in the ORM relationships assert any(map(lambda mem: mem.lexicon == new_lexicon, new_user.memberships)) diff --git a/tests/test_post.py b/tests/test_post.py index 0e14cdd..bb0b61d 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -11,29 +11,25 @@ def test_create_post(db: DbContext, lexicon_with_editor): lexicon, editor = lexicon_with_editor # argument dictionary for post object - kwargs = { - 'lexicon_id': lexicon.id, - 'user_id': editor.id, - 'body': 'body' - } + kwargs = {"lexicon_id": lexicon.id, "user_id": editor.id, "body": "body"} # ids are integers with pytest.raises(ArgumentError): - postq.create(db, **{**kwargs, 'user_id': 'zero'}) + postq.create(db, **{**kwargs, "user_id": "zero"}) with pytest.raises(ArgumentError): - postq.create(db, **{**kwargs, 'lexicon_id': 'zero'}) + postq.create(db, **{**kwargs, "lexicon_id": "zero"}) # empty arguments don't work with pytest.raises(ArgumentError): - postq.create(db, **{**kwargs, 'lexicon_id': ''}) + postq.create(db, **{**kwargs, "lexicon_id": ""}) with pytest.raises(ArgumentError): - postq.create(db, **{**kwargs, 'user_id': ''}) + postq.create(db, **{**kwargs, "user_id": ""}) with pytest.raises(ArgumentError): - postq.create(db, **{**kwargs, 'body': ''}) + postq.create(db, **{**kwargs, "body": ""}) # post with only whitespace doesn't work with pytest.raises(ArgumentError): - postq.create(db, **{**kwargs, 'body': ' '}) + postq.create(db, **{**kwargs, "body": " "}) # post creation works and populates fields new_post = postq.create(db, **kwargs) @@ -43,6 +39,6 @@ def test_create_post(db: DbContext, lexicon_with_editor): assert new_post.body is not None # post creation works when user is None - new_post = postq.create(db, **{**kwargs, 'user_id': None}) + new_post = postq.create(db, **{**kwargs, "user_id": None}) assert new_post assert new_post.user_id is None diff --git a/tests/test_user.py b/tests/test_user.py index 099f871..71b483a 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -8,24 +8,26 @@ from amanuensis.errors import ArgumentError def test_create_user(db: DbContext): """Test new user creation.""" kwargs = { - 'username': 'username', - 'password': 'password', - 'display_name': 'User Name', - 'email': 'user@example.com', - 'is_site_admin': False + "username": "username", + "password": "password", + "display_name": "User Name", + "email": "user@example.com", + "is_site_admin": False, } # Test length constraints with pytest.raises(ArgumentError): - userq.create(db, **{**kwargs, 'username': 'me'}) + userq.create(db, **{**kwargs, "username": "me"}) with pytest.raises(ArgumentError): - userq.create(db, **{**kwargs, 'username': 'the right honorable user-name, esquire'}) + userq.create( + db, **{**kwargs, "username": "the right honorable user-name, esquire"} + ) # Test allowed characters with pytest.raises(ArgumentError): - userq.create(db, **{**kwargs, 'username': 'user name'}) + userq.create(db, **{**kwargs, "username": "user name"}) # No password with pytest.raises(ArgumentError): - userq.create(db, **{**kwargs, 'password': None}) + userq.create(db, **{**kwargs, "password": None}) # Valid creation works and populates fields new_user = userq.create(db, **kwargs) @@ -38,6 +40,6 @@ def test_create_user(db: DbContext): duplicate = userq.create(db, **kwargs) # Missing display name populates with username - user2_kw = {**kwargs, 'username': 'user2', 'display_name': None} + user2_kw = {**kwargs, "username": "user2", "display_name": None} user2 = userq.create(db, **user2_kw) assert user2.display_name is not None