Compare commits

..

3 Commits

Author SHA1 Message Date
Tim Van Baak 2982f6ea0f black string quote normalization 2021-06-02 20:12:09 -07:00
Tim Van Baak de09030b1c black style pass 2021-06-02 20:11:56 -07:00
Tim Van Baak 667173329c Add black as a style check for new code 2021-06-02 20:11:30 -07:00
18 changed files with 539 additions and 390 deletions

View File

@ -8,21 +8,17 @@ from amanuensis.db import *
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
def create( def create(db: DbContext, lexicon_id: int, user_id: int, character_id: int) -> Article:
db: DbContext,
lexicon_id: int,
user_id: int,
character_id: int) -> Article:
""" """
Create a new article in a lexicon. Create a new article in a lexicon.
""" """
# Verify argument types are correct # Verify argument types are correct
if not isinstance(lexicon_id, int): if not isinstance(lexicon_id, int):
raise ArgumentError('lexicon_id') raise ArgumentError("lexicon_id")
if not isinstance(user_id, int): 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): 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 # Check that the user is a member of this lexicon
mem: Membership = db( mem: Membership = db(
@ -31,31 +27,30 @@ def create(
.where(Membership.lexicon_id == lexicon_id) .where(Membership.lexicon_id == lexicon_id)
).scalar_one_or_none() ).scalar_one_or_none()
if not mem: 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 # If the character id is provided, check that the user owns the character
# and the character belongs to the lexicon # and the character belongs to the lexicon
if character_id is not None: if character_id is not None:
character: Character = db( character: Character = db(
select(Character) select(Character).where(Character.id == character_id)
.where(Character.id == character_id)
).scalar_one_or_none() ).scalar_one_or_none()
if not character: if not character:
raise ArgumentError('Character does not exist') raise ArgumentError("Character does not exist")
if character.user.id != user_id: 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: 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 signature = character.signature
else: else:
signature = '~Ersatz Scrivener' signature = "~Ersatz Scrivener"
new_article = Article( new_article = Article(
lexicon_id=lexicon_id, lexicon_id=lexicon_id,
user_id=user_id, user_id=user_id,
character_id=character_id, character_id=character_id,
title='Article title', title="Article title",
body=f'\n\n{signature}', body=f"\n\n{signature}",
) )
db.session.add(new_article) db.session.add(new_article)
db.session.commit() db.session.commit()

View File

@ -9,31 +9,28 @@ from amanuensis.errors import ArgumentError
def create( def create(
db: DbContext, db: DbContext, lexicon_id: int, user_id: int, name: str, signature: str
lexicon_id: int, ) -> Character:
user_id: int,
name: str,
signature: str) -> Character:
""" """
Create a new character for a user. Create a new character for a user.
""" """
# Verify argument types are correct # Verify argument types are correct
if not isinstance(lexicon_id, int): if not isinstance(lexicon_id, int):
raise ArgumentError('lexicon_id') raise ArgumentError("lexicon_id")
if not isinstance(user_id, int): if not isinstance(user_id, int):
raise ArgumentError('user_id') raise ArgumentError("user_id")
if not isinstance(name, str): if not isinstance(name, str):
raise ArgumentError('name') raise ArgumentError("name")
if signature is not None and not isinstance(signature, str): if signature is not None and not isinstance(signature, str):
raise ArgumentError('signature') raise ArgumentError("signature")
# Verify character name is valid # Verify character name is valid
if not name.strip(): 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 no signature is provided, use a default signature
if not signature or not signature.strip(): if not signature or not signature.strip():
signature = f'~{name}' signature = f"~{name}"
# Check that the user is a member of this lexicon # Check that the user is a member of this lexicon
mem: Membership = db( mem: Membership = db(
@ -42,7 +39,7 @@ def create(
.where(Membership.lexicon_id == lexicon_id) .where(Membership.lexicon_id == lexicon_id)
).scalar_one_or_none() ).scalar_one_or_none()
if not mem: 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 # Check that this user is below the limit for creating characters
num_user_chars = db( num_user_chars = db(
@ -50,8 +47,11 @@ def create(
.where(Character.lexicon_id == lexicon_id) .where(Character.lexicon_id == lexicon_id)
.where(Character.user_id == user_id) .where(Character.user_id == user_id)
).scalar() ).scalar()
if mem.lexicon.character_limit is not None and num_user_chars >= mem.lexicon.character_limit: if (
raise ArgumentError('User is at character limit') 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( new_character = Character(
lexicon_id=lexicon_id, lexicon_id=lexicon_id,

View File

@ -10,39 +10,34 @@ from amanuensis.db import DbContext, Lexicon
from amanuensis.errors import ArgumentError 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( def create(db: DbContext, name: str, title: str, prompt: str) -> Lexicon:
db: DbContext,
name: str,
title: str,
prompt: str) -> Lexicon:
""" """
Create a new lexicon. Create a new lexicon.
""" """
# Verify name # Verify name
if not isinstance(name, str): 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(): 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): 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 # Verify title
if title is not None and not isinstance(name, str): 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 # Verify prompt
if not isinstance(prompt, str): 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 # Query the db to make sure the lexicon name isn't taken
if db( if db(select(func.count(Lexicon.id)).where(Lexicon.name == name)).scalar() > 0:
select(func.count(Lexicon.id)) raise ArgumentError("Lexicon name is already taken")
.where(Lexicon.name == name)
).scalar() > 0:
raise ArgumentError('Lexicon name is already taken')
new_lexicon = Lexicon( new_lexicon = Lexicon(
name=name, name=name,

View File

@ -8,29 +8,28 @@ from amanuensis.db import DbContext, Membership
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
def create( def create(db: DbContext, user_id: int, lexicon_id: int, is_editor: bool) -> Membership:
db: DbContext,
user_id: int,
lexicon_id: int,
is_editor: bool) -> Membership:
""" """
Create a new user membership in a lexicon. Create a new user membership in a lexicon.
""" """
# Verify argument types are correct # Verify argument types are correct
if not isinstance(user_id, int): if not isinstance(user_id, int):
raise ArgumentError('user_id') raise ArgumentError("user_id")
if not isinstance(lexicon_id, int): if not isinstance(lexicon_id, int):
raise ArgumentError('lexicon_id') raise ArgumentError("lexicon_id")
if not isinstance(is_editor, bool): if not isinstance(is_editor, bool):
raise ArgumentError('is_editor') raise ArgumentError("is_editor")
# Verify user has not already joined lexicon # Verify user has not already joined lexicon
if db( if (
select(func.count(Membership.id)) db(
.where(Membership.user_id == user_id) select(func.count(Membership.id))
.where(Membership.lexicon_id == lexicon_id) .where(Membership.user_id == user_id)
).scalar() > 0: .where(Membership.lexicon_id == lexicon_id)
raise ArgumentError('User is already a member of lexicon') ).scalar()
> 0
):
raise ArgumentError("User is already a member of lexicon")
new_membership = Membership( new_membership = Membership(
user_id=user_id, user_id=user_id,

View File

@ -9,34 +9,27 @@ from sqlalchemy import select, func
from amanuensis.db import DbContext, Post from amanuensis.db import DbContext, Post
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
def create(
db: DbContext, def create(db: DbContext, lexicon_id: int, user_id: int, body: str) -> Post:
lexicon_id: int,
user_id: int,
body: str) -> Post:
""" """
Create a new post Create a new post
""" """
# Verify lexicon id # Verify lexicon id
if not isinstance(lexicon_id, int): 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 # Verify user_id
if not (isinstance(user_id, int) or user_id is None): 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 # Verify body
if not isinstance(body, str): 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(): if not body.strip():
raise ArgumentError('Post body cannot be empty.') raise ArgumentError("Post body cannot be empty.")
new_post = Post( new_post = Post(lexicon_id=lexicon_id, user_id=user_id, body=body)
lexicon_id=lexicon_id,
user_id=user_id,
body=body
)
db.session.add(new_post) db.session.add(new_post)
db.session.commit() db.session.commit()
return new_post return new_post

View File

@ -11,8 +11,8 @@ from amanuensis.db import DbContext, User
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
RE_NO_LETTERS = re.compile(r'^[0-9-_]*$') RE_NO_LETTERS = re.compile(r"^[0-9-_]*$")
RE_ALPHANUM_DASH_UNDER = re.compile(r'^[A-Za-z0-9-_]*$') RE_ALPHANUM_DASH_UNDER = re.compile(r"^[A-Za-z0-9-_]*$")
def create( def create(
@ -21,41 +21,41 @@ def create(
password: str, password: str,
display_name: str, display_name: str,
email: str, email: str,
is_site_admin: bool) -> User: is_site_admin: bool,
) -> User:
""" """
Create a new user. Create a new user.
""" """
# Verify username # Verify username
if not isinstance(username, str): 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: 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): 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): 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 # Verify password
if not isinstance(password, str): if not isinstance(password, str):
raise ArgumentError('Password must be a string') raise ArgumentError("Password must be a string")
# Verify display name # Verify display name
if display_name is not None and not isinstance(display_name, str): 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 display name is not provided, use the username
if not display_name or not display_name.strip(): if not display_name or not display_name.strip():
display_name = username display_name = username
# Verify email # Verify email
if not isinstance(email, str): 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 # Query the db to make sure the username isn't taken
if db( if db(select(func.count(User.id)).where(User.username == username)).scalar() > 0:
select(func.count(User.id)) raise ArgumentError("Username is already taken")
.where(User.username == username)
).scalar() > 0:
raise ArgumentError('Username is already taken')
new_user = User( new_user = User(
username=username, username=username,

View File

@ -15,17 +15,17 @@ from .models import (
) )
__all__ = [ __all__ = [
'DbContext', "DbContext",
'User', "User",
'Lexicon', "Lexicon",
'Membership', "Membership",
'Character', "Character",
'ArticleState', "ArticleState",
'Article', "Article",
'IndexType', "IndexType",
'ArticleIndex', "ArticleIndex",
'ArticleIndexRule', "ArticleIndexRule",
'ArticleContentRuleType', "ArticleContentRuleType",
'ArticleContentRule', "ArticleContentRule",
'Post', "Post",
] ]

View File

@ -8,22 +8,25 @@ from sqlalchemy.orm import sessionmaker
# Define naming conventions for generated constraints # Define naming conventions for generated constraints
metadata = MetaData(naming_convention={ metadata = MetaData(
"ix": "ix_%(column_0_label)s", naming_convention={
"uq": "uq_%(table_name)s_%(column_0_name)s", "ix": "ix_%(column_0_label)s",
"ck": "ck_%(table_name)s_%(constraint_name)s", "uq": "uq_%(table_name)s_%(column_0_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", "ck": "ck_%(table_name)s_%(constraint_name)s",
"pk": "pk_%(table_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 # Base class for ORM models
ModelBase = declarative_base(metadata=metadata) ModelBase = declarative_base(metadata=metadata)
class DbContext(): class DbContext:
def __init__(self, db_uri, debug=False): def __init__(self, db_uri, debug=False):
# Create an engine and enable foreign key constraints in sqlite # Create an engine and enable foreign key constraints in sqlite
self.engine = create_engine(db_uri, echo=debug) self.engine = create_engine(db_uri, echo=debug)
@event.listens_for(self.engine, "connect") @event.listens_for(self.engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record): def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor() cursor = dbapi_connection.cursor()

View File

@ -28,15 +28,16 @@ class Uuid(TypeDecorator):
""" """
A uuid backed by a char(32) field in sqlite. A uuid backed by a char(32) field in sqlite.
""" """
impl = CHAR(32) impl = CHAR(32)
def process_bind_param(self, value, dialect): def process_bind_param(self, value, dialect):
if value is None: if value is None:
return value return value
elif not isinstance(value, uuid.UUID): elif not isinstance(value, uuid.UUID):
return f'{uuid.UUID(value).int:32x}' return f"{uuid.UUID(value).int:32x}"
else: else:
return f'{value.int:32x}' return f"{value.int:32x}"
def process_result_value(self, value, dialect): def process_result_value(self, value, dialect):
if value is None: if value is None:
@ -51,7 +52,8 @@ class User(ModelBase):
""" """
Represents a single user of Amanuensis. Represents a single user of Amanuensis.
""" """
__tablename__ = 'user'
__tablename__ = "user"
############# #############
# User info # # User info #
@ -73,14 +75,14 @@ class User(ModelBase):
email = Column(String, nullable=False) email = Column(String, nullable=False)
# Whether the user can access site admin functions # 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 # # History tracking #
#################### ####################
# The timestamp the user was created # 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 # The timestamp the user last logged in
# This is NULL if the user has never logged in # This is NULL if the user has never logged in
@ -94,17 +96,18 @@ class User(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
memberships = relationship('Membership', back_populates='user') memberships = relationship("Membership", back_populates="user")
characters = relationship('Character', back_populates='user') characters = relationship("Character", back_populates="user")
articles = relationship('Article', back_populates='user') articles = relationship("Article", back_populates="user")
posts = relationship('Post', back_populates='user') posts = relationship("Post", back_populates="user")
class Lexicon(ModelBase): class Lexicon(ModelBase):
""" """
Represents a single game of Lexicon. Represents a single game of Lexicon.
""" """
__tablename__ = 'lexicon'
__tablename__ = "lexicon"
############# #############
# Game info # # Game info #
@ -128,10 +131,12 @@ class Lexicon(ModelBase):
#################### ####################
# The timestamp the lexicon was created # 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 # 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 # The timestamp the first turn was started
# This is NULL until the game starts # This is NULL until the game starts
@ -221,23 +226,22 @@ class Lexicon(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
memberships = relationship('Membership', back_populates='lexicon') memberships = relationship("Membership", back_populates="lexicon")
characters = relationship('Character', back_populates='lexicon') characters = relationship("Character", back_populates="lexicon")
articles = relationship('Article', back_populates='lexicon') articles = relationship("Article", back_populates="lexicon")
indexes = relationship('ArticleIndex', back_populates='lexicon') indexes = relationship("ArticleIndex", back_populates="lexicon")
index_rules = relationship('ArticleIndexRule', back_populates='lexicon') index_rules = relationship("ArticleIndexRule", back_populates="lexicon")
content_rules = relationship('ArticleContentRule', back_populates='lexicon') content_rules = relationship("ArticleContentRule", back_populates="lexicon")
posts = relationship('Post', back_populates='lexicon') posts = relationship("Post", back_populates="lexicon")
class Membership(ModelBase): class Membership(ModelBase):
""" """
Represents a user's participation in a Lexicon game. Represents a user's participation in a Lexicon game.
""" """
__tablename__ = 'membership'
__table_args__ = ( __tablename__ = "membership"
UniqueConstraint('user_id', 'lexicon_id'), __table_args__ = (UniqueConstraint("user_id", "lexicon_id"),)
)
################### ###################
# Membership keys # # Membership keys #
@ -247,17 +251,17 @@ class Membership(ModelBase):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
# The user who is a member of a lexicon # 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 # 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 # # History tracking #
#################### ####################
# Timestamp the user joined the game # 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 # Timestamp of the last time the user viewed the post feed
# This is NULL if the player has never viewed posts # This is NULL if the player has never viewed posts
@ -268,7 +272,7 @@ class Membership(ModelBase):
################### ###################
# Whether the user can access editor functions # 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 # # Notification settings #
@ -287,15 +291,16 @@ class Membership(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
user = relationship('User', back_populates='memberships') user = relationship("User", back_populates="memberships")
lexicon = relationship('Lexicon', back_populates='memberships') lexicon = relationship("Lexicon", back_populates="memberships")
class Character(ModelBase): class Character(ModelBase):
""" """
Represents a character played by a uaser in a Lexicon game. Represents a character played by a uaser in a Lexicon game.
""" """
__tablename__ = 'character'
__tablename__ = "character"
################## ##################
# Character info # # Character info #
@ -308,10 +313,10 @@ class Character(ModelBase):
public_id = Column(Uuid, nullable=False, unique=True, default=uuid.uuid4) public_id = Column(Uuid, nullable=False, unique=True, default=uuid.uuid4)
# The lexicon to which this character belongs # 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 # 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 # The character's name
name = Column(String, nullable=False) name = Column(String, nullable=False)
@ -323,16 +328,17 @@ class Character(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
user = relationship('User', back_populates='characters') user = relationship("User", back_populates="characters")
lexicon = relationship('Lexicon', back_populates='characters') lexicon = relationship("Lexicon", back_populates="characters")
articles = relationship('Article', back_populates='character') articles = relationship("Article", back_populates="character")
index_rules = relationship('ArticleIndexRule', back_populates='character') index_rules = relationship("ArticleIndexRule", back_populates="character")
class ArticleState(enum.Enum): class ArticleState(enum.Enum):
""" """
The step of the editorial process an article is in. The step of the editorial process an article is in.
""" """
DRAFT = 0 DRAFT = 0
SUBMITTED = 1 SUBMITTED = 1
APPROVED = 2 APPROVED = 2
@ -342,7 +348,8 @@ class Article(ModelBase):
""" """
Represents a single article in a lexicon. Represents a single article in a lexicon.
""" """
__tablename__ = 'article'
__tablename__ = "article"
################ ################
# Article info # # Article info #
@ -355,17 +362,17 @@ class Article(ModelBase):
public_id = Column(Uuid, nullable=False, unique=True, default=uuid.uuid4) public_id = Column(Uuid, nullable=False, unique=True, default=uuid.uuid4)
# The lexicon to which this article belongs # 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 # The character who is the author of this article
# If this is NULL, the article is written by Ersatz Scrivener # 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 # 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 # 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 # # Article state #
@ -386,7 +393,9 @@ class Article(ModelBase):
#################### ####################
# Timestamp the content of the article was last updated # 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 # Timestamp the article was last submitted
# This is NULL until the article is submitted # This is NULL until the article is submitted
@ -410,16 +419,17 @@ class Article(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
lexicon = relationship('Lexicon', back_populates='articles') lexicon = relationship("Lexicon", back_populates="articles")
character = relationship('Character', back_populates='articles') character = relationship("Character", back_populates="articles")
user = relationship('User', back_populates='articles') user = relationship("User", back_populates="articles")
addenda = relationship('Article', backref=backref('parent', remote_side=[id])) addenda = relationship("Article", backref=backref("parent", remote_side=[id]))
class IndexType(enum.Enum): class IndexType(enum.Enum):
""" """
The title-matching behavior of an article index. The title-matching behavior of an article index.
""" """
CHAR = 0 CHAR = 0
RANGE = 1 RANGE = 1
PREFIX = 2 PREFIX = 2
@ -430,7 +440,8 @@ class ArticleIndex(ModelBase):
""" """
Represents an index definition. Represents an index definition.
""" """
__tablename__ = 'article_index'
__tablename__ = "article_index"
############## ##############
# Index info # # Index info #
@ -440,7 +451,7 @@ class ArticleIndex(ModelBase):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
# The lexicon this index is in # 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 # The index type
index_type = Column(Enum(IndexType), nullable=False) index_type = Column(Enum(IndexType), nullable=False)
@ -462,8 +473,8 @@ class ArticleIndex(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
lexicon = relationship('Lexicon', back_populates='indexes') lexicon = relationship("Lexicon", back_populates="indexes")
index_rules = relationship('ArticleIndexRule', back_populates='index') index_rules = relationship("ArticleIndexRule", back_populates="index")
class ArticleIndexRule(ModelBase): class ArticleIndexRule(ModelBase):
@ -472,7 +483,8 @@ class ArticleIndexRule(ModelBase):
A character with multiple index rules may write in any index that satisfies 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. a rule. A character with no index rules may write in any index.
""" """
__tablename__ = 'article_index_rule'
__tablename__ = "article_index_rule"
################### ###################
# Index rule info # # Index rule info #
@ -482,17 +494,17 @@ class ArticleIndexRule(ModelBase):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
# The lexicon of this index rule # 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 # # Index rule scope #
#################### ####################
# The character to whom this rule applies # 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 # 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 # The turn in which this rule applies
turn = Column(Integer, nullable=False) turn = Column(Integer, nullable=False)
@ -501,15 +513,16 @@ class ArticleIndexRule(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
lexicon = relationship('Lexicon', back_populates='index_rules') lexicon = relationship("Lexicon", back_populates="index_rules")
index = relationship('ArticleIndex', back_populates='index_rules') index = relationship("ArticleIndex", back_populates="index_rules")
character = relationship('Character', back_populates='index_rules') character = relationship("Character", back_populates="index_rules")
class ArticleContentRuleType(enum.Enum): class ArticleContentRuleType(enum.Enum):
""" """
The possible article content rules. The possible article content rules.
""" """
# Whether characters can cite themselves # Whether characters can cite themselves
ALLOW_SELF_CITE = 0 ALLOW_SELF_CITE = 0
# Whether characters can write new articles instead of phantoms # 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. Represents a restriction on the content of an article for a turn.
""" """
__tablename__ = 'article_content_rule'
__tablename__ = "article_content_rule"
##################### #####################
# Content rule info # # Content rule info #
@ -553,7 +567,7 @@ class ArticleContentRule(ModelBase):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
# The lexicon of this content rule # 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 # # Content rule scope #
@ -577,14 +591,15 @@ class ArticleContentRule(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
lexicon = relationship('Lexicon', back_populates='content_rules') lexicon = relationship("Lexicon", back_populates="content_rules")
class Post(ModelBase): class Post(ModelBase):
""" """
Represents a post in the game feed. Represents a post in the game feed.
""" """
__tablename__ = 'post'
__tablename__ = "post"
############# #############
# Post info # # Post info #
@ -594,18 +609,18 @@ class Post(ModelBase):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
# The lexicon in which the post was made # 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 # The user who made the post
# This may be NULL if the post was made by Amanuensis # 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 # # Post content #
################ ################
# The timestamp the post was created # 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 # The body of the post
body = Column(Text, nullable=False) body = Column(Text, nullable=False)
@ -614,5 +629,5 @@ class Post(ModelBase):
# Foreign key relationships # # Foreign key relationships #
############################# #############################
user = relationship('User', back_populates='posts') user = relationship("User", back_populates="posts")
lexicon = relationship('Lexicon', back_populates='posts') lexicon = relationship("Lexicon", back_populates="posts")

View File

@ -2,6 +2,7 @@
Submodule of custom exception types Submodule of custom exception types
""" """
class AmanuensisError(Exception): class AmanuensisError(Exception):
"""Base class for exceptions in amanuensis""" """Base class for exceptions in amanuensis"""

394
poetry.lock generated
View File

@ -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]] [[package]]
name = "atomicwrites" name = "atomicwrites"
version = "1.4.0" version = "1.4.0"
@ -8,17 +16,39 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "attrs" name = "attrs"
version = "20.3.0" version = "21.2.0"
description = "Classes Without Boilerplate" description = "Classes Without Boilerplate"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras] [package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 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"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 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"] 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]] [[package]]
name = "click" name = "click"
@ -38,17 +68,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "flask" name = "flask"
version = "1.1.2" version = "1.1.4"
description = "A simple framework for building complex web applications." description = "A simple framework for building complex web applications."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies] [package.dependencies]
click = ">=5.1" click = ">=5.1,<8.0"
itsdangerous = ">=0.24" itsdangerous = ">=0.24,<2.0"
Jinja2 = ">=2.10.1" Jinja2 = ">=2.10.1,<3.0"
Werkzeug = ">=0.15" Werkzeug = ">=0.15,<2.0"
[package.extras] [package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"] dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
@ -81,7 +111,7 @@ WTForms = "*"
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "1.0.0" version = "1.1.0"
description = "Lightweight in-process concurrent programming" description = "Lightweight in-process concurrent programming"
category = "main" category = "main"
optional = false optional = false
@ -114,20 +144,28 @@ i18n = ["Babel (>=0.8)"]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "1.1.1" version = "2.0.1"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" python-versions = ">=3.6"
[[package]] [[package]]
name = "more-itertools" name = "more-itertools"
version = "8.7.0" version = "8.8.0"
description = "More routines for operating on iterables, beyond itertools" description = "More routines for operating on iterables, beyond itertools"
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.5" 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]] [[package]]
name = "packaging" name = "packaging"
version = "20.9" version = "20.9"
@ -139,6 +177,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" 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]] [[package]]
name = "pluggy" name = "pluggy"
version = "0.13.1" version = "0.13.1"
@ -188,9 +234,17 @@ wcwidth = "*"
checkqa-mypy = ["mypy (==v0.761)"] checkqa-mypy = ["mypy (==v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 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]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "1.4.12" version = "1.4.17"
description = "Database Abstraction Library" description = "Database Abstraction Library"
category = "main" category = "main"
optional = false optional = false
@ -219,6 +273,14 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1)", "pymysql"] pymysql = ["pymysql (<1)", "pymysql"]
sqlcipher = ["sqlcipher3-binary"] 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]] [[package]]
name = "wcwidth" name = "wcwidth"
version = "0.2.5" version = "0.2.5"
@ -258,16 +320,24 @@ locale = ["Babel (>=1.3)"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "48928fcd093c025ed3b9ed6153fd54310cbdefdaba133a57b4a2bfb6c9f5941f" content-hash = "e81ba4ffeb172410ef80bd6500c0af4e3be6b800d9cfda85df2f6a5c74319d87"
[metadata.files] [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 = [ atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
] ]
attrs = [ attrs = [
{file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
{file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, {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 = [ click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {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"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
] ]
flask = [ flask = [
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"}, {file = "Flask-1.1.4-py2.py3-none-any.whl", hash = "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22"},
{file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"}, {file = "Flask-1.1.4.tar.gz", hash = "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196"},
] ]
flask-login = [ flask-login = [
{file = "Flask-Login-0.5.0.tar.gz", hash = "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b"}, {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"}, {file = "Flask_WTF-0.14.3-py2.py3-none-any.whl", hash = "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2"},
] ]
greenlet = [ greenlet = [
{file = "greenlet-1.0.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:1d1d4473ecb1c1d31ce8fd8d91e4da1b1f64d425c1dc965edc4ed2a63cfa67b2"}, {file = "greenlet-1.1.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:60848099b76467ef09b62b0f4512e7e6f0a2c977357a036de602b653667f5f4c"},
{file = "greenlet-1.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:cfd06e0f0cc8db2a854137bd79154b61ecd940dce96fad0cba23fe31de0b793c"}, {file = "greenlet-1.1.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f42ad188466d946f1b3afc0a9e1a266ac8926461ee0786c06baac6bd71f8a6f3"},
{file = "greenlet-1.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb333b90036358a0e2c57373f72e7648d7207b76ef0bd00a4f7daad1f79f5203"}, {file = "greenlet-1.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:76ed710b4e953fc31c663b079d317c18f40235ba2e3d55f70ff80794f7b57922"},
{file = "greenlet-1.0.0-cp27-cp27m-win32.whl", hash = "sha256:1a1ada42a1fd2607d232ae11a7b3195735edaa49ea787a6d9e6a53afaf6f3476"}, {file = "greenlet-1.1.0-cp27-cp27m-win32.whl", hash = "sha256:b33b51ab057f8a20b497ffafdb1e79256db0c03ef4f5e3d52e7497200e11f821"},
{file = "greenlet-1.0.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f6f65bf54215e4ebf6b01e4bb94c49180a589573df643735107056f7a910275b"}, {file = "greenlet-1.1.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed1377feed808c9c1139bdb6a61bcbf030c236dd288d6fca71ac26906ab03ba6"},
{file = "greenlet-1.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f59eded163d9752fd49978e0bab7a1ff21b1b8d25c05f0995d140cc08ac83379"}, {file = "greenlet-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:da862b8f7de577bc421323714f63276acb2f759ab8c5e33335509f0b89e06b8f"},
{file = "greenlet-1.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:875d4c60a6299f55df1c3bb870ebe6dcb7db28c165ab9ea6cdc5d5af36bb33ce"}, {file = "greenlet-1.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5f75e7f237428755d00e7460239a2482fa7e3970db56c8935bd60da3f0733e56"},
{file = "greenlet-1.0.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:1bb80c71de788b36cefb0c3bb6bfab306ba75073dbde2829c858dc3ad70f867c"}, {file = "greenlet-1.1.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:258f9612aba0d06785143ee1cbf2d7361801c95489c0bd10c69d163ec5254a16"},
{file = "greenlet-1.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b5f1b333015d53d4b381745f5de842f19fe59728b65f0fbb662dafbe2018c3a5"}, {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d928e2e3c3906e0a29b43dc26d9b3d6e36921eee276786c4e7ad9ff5665c78a"},
{file = "greenlet-1.0.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:5352c15c1d91d22902582e891f27728d8dac3bd5e0ee565b6a9f575355e6d92f"}, {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cc407b68e0a874e7ece60f6639df46309376882152345508be94da608cc0b831"},
{file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:2c65320774a8cd5fdb6e117c13afa91c4707548282464a18cf80243cf976b3e6"}, {file = "greenlet-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c557c809eeee215b87e8a7cbfb2d783fb5598a78342c29ade561440abae7d22"},
{file = "greenlet-1.0.0-cp35-cp35m-manylinux2014_ppc64le.whl", hash = "sha256:111cfd92d78f2af0bc7317452bd93a477128af6327332ebf3c2be7df99566683"}, {file = "greenlet-1.1.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:3d13da093d44dee7535b91049e44dd2b5540c2a0e15df168404d3dd2626e0ec5"},
{file = "greenlet-1.0.0-cp35-cp35m-win32.whl", hash = "sha256:cdb90267650c1edb54459cdb51dab865f6c6594c3a47ebd441bc493360c7af70"}, {file = "greenlet-1.1.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b3090631fecdf7e983d183d0fad7ea72cfb12fa9212461a9b708ff7907ffff47"},
{file = "greenlet-1.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:eac8803c9ad1817ce3d8d15d1bb82c2da3feda6bee1153eec5c58fa6e5d3f770"}, {file = "greenlet-1.1.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:06ecb43b04480e6bafc45cb1b4b67c785e183ce12c079473359e04a709333b08"},
{file = "greenlet-1.0.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c93d1a71c3fe222308939b2e516c07f35a849c5047f0197442a4d6fbcb4128ee"}, {file = "greenlet-1.1.0-cp35-cp35m-win32.whl", hash = "sha256:944fbdd540712d5377a8795c840a97ff71e7f3221d3fddc98769a15a87b36131"},
{file = "greenlet-1.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:122c63ba795fdba4fc19c744df6277d9cfd913ed53d1a286f12189a0265316dd"}, {file = "greenlet-1.1.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c767458511a59f6f597bfb0032a1c82a52c29ae228c2c0a6865cfeaeaac4c5f5"},
{file = "greenlet-1.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c5b22b31c947ad8b6964d4ed66776bcae986f73669ba50620162ba7c832a6b6a"}, {file = "greenlet-1.1.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2325123ff3a8ecc10ca76f062445efef13b6cf5a23389e2df3c02a4a527b89bc"},
{file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:4365eccd68e72564c776418c53ce3c5af402bc526fe0653722bc89efd85bf12d"}, {file = "greenlet-1.1.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:598bcfd841e0b1d88e32e6a5ea48348a2c726461b05ff057c1b8692be9443c6e"},
{file = "greenlet-1.0.0-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:da7d09ad0f24270b20f77d56934e196e982af0d0a2446120cb772be4e060e1a2"}, {file = "greenlet-1.1.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:be9768e56f92d1d7cd94185bab5856f3c5589a50d221c166cc2ad5eb134bd1dc"},
{file = "greenlet-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:647ba1df86d025f5a34043451d7c4a9f05f240bee06277a524daad11f997d1e7"}, {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe7eac0d253915116ed0cd160a15a88981a1d194c1ef151e862a5c7d2f853d3"},
{file = "greenlet-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6e9fdaf6c90d02b95e6b0709aeb1aba5affbbb9ccaea5502f8638e4323206be"}, {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a6b035aa2c5fcf3dbbf0e3a8a5bc75286fc2d4e6f9cfa738788b433ec894919"},
{file = "greenlet-1.0.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:62afad6e5fd70f34d773ffcbb7c22657e1d46d7fd7c95a43361de979f0a45aef"}, {file = "greenlet-1.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1c4a569232c063615f9e70ff9a1e2fee8c66a6fb5caf0f5e8b21a396deec3e"},
{file = "greenlet-1.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d3789c1c394944084b5e57c192889985a9f23bd985f6d15728c745d380318128"}, {file = "greenlet-1.1.0-cp36-cp36m-win32.whl", hash = "sha256:3096286a6072553b5dbd5efbefc22297e9d06a05ac14ba017233fedaed7584a8"},
{file = "greenlet-1.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f5e2d36c86c7b03c94b8459c3bd2c9fe2c7dab4b258b8885617d44a22e453fb7"}, {file = "greenlet-1.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c35872b2916ab5a240d52a94314c963476c989814ba9b519bc842e5b61b464bb"},
{file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:292e801fcb3a0b3a12d8c603c7cf340659ea27fd73c98683e75800d9fd8f704c"}, {file = "greenlet-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b97c9a144bbeec7039cca44df117efcbeed7209543f5695201cacf05ba3b5857"},
{file = "greenlet-1.0.0-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:f3dc68272990849132d6698f7dc6df2ab62a88b0d36e54702a8fd16c0490e44f"}, {file = "greenlet-1.1.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:16183fa53bc1a037c38d75fdc59d6208181fa28024a12a7f64bb0884434c91ea"},
{file = "greenlet-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:7cd5a237f241f2764324396e06298b5dee0df580cf06ef4ada0ff9bff851286c"}, {file = "greenlet-1.1.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6b1d08f2e7f2048d77343279c4d4faa7aef168b3e36039cba1917fffb781a8ed"},
{file = "greenlet-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0ddd77586553e3daf439aa88b6642c5f252f7ef79a39271c25b1d4bf1b7cbb85"}, {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14927b15c953f8f2d2a8dffa224aa78d7759ef95284d4c39e1745cf36e8cdd2c"},
{file = "greenlet-1.0.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90b6a25841488cf2cb1c8623a53e6879573010a669455046df5f029d93db51b7"}, {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bdcff4b9051fb1aa4bba4fceff6a5f770c6be436408efd99b76fc827f2a9319"},
{file = "greenlet-1.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ed1d1351f05e795a527abc04a0d82e9aecd3bdf9f46662c36ff47b0b00ecaf06"}, {file = "greenlet-1.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70c7dd733a4c56838d1f1781e769081a25fade879510c5b5f0df76956abfa05"},
{file = "greenlet-1.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:94620ed996a7632723a424bccb84b07e7b861ab7bb06a5aeb041c111dd723d36"}, {file = "greenlet-1.1.0-cp37-cp37m-win32.whl", hash = "sha256:0de64d419b1cb1bfd4ea544bedea4b535ef3ae1e150b0f2609da14bbf48a4a5f"},
{file = "greenlet-1.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:f97d83049715fd9dec7911860ecf0e17b48d8725de01e45de07d8ac0bd5bc378"}, {file = "greenlet-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8833e27949ea32d27f7e96930fa29404dd4f2feb13cce483daf52e8842ec246a"},
{file = "greenlet-1.0.0-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:0a77691f0080c9da8dfc81e23f4e3cffa5accf0f5b56478951016d7cfead9196"}, {file = "greenlet-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c1580087ab493c6b43e66f2bdd165d9e3c1e86ef83f6c2c44a29f2869d2c5bd5"},
{file = "greenlet-1.0.0-cp38-cp38-win32.whl", hash = "sha256:e1128e022d8dce375362e063754e129750323b67454cac5600008aad9f54139e"}, {file = "greenlet-1.1.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ad80bb338cf9f8129c049837a42a43451fc7c8b57ad56f8e6d32e7697b115505"},
{file = "greenlet-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d4030b04061fdf4cbc446008e238e44936d77a04b2b32f804688ad64197953c"}, {file = "greenlet-1.1.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:a9017ff5fc2522e45562882ff481128631bf35da444775bc2776ac5c61d8bcae"},
{file = "greenlet-1.0.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:f8450d5ef759dbe59f84f2c9f77491bb3d3c44bc1a573746daf086e70b14c243"}, {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7920e3eccd26b7f4c661b746002f5ec5f0928076bd738d38d894bb359ce51927"},
{file = "greenlet-1.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df8053867c831b2643b2c489fe1d62049a98566b1646b194cc815f13e27b90df"}, {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:408071b64e52192869129a205e5b463abda36eff0cebb19d6e63369440e4dc99"},
{file = "greenlet-1.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:df3e83323268594fa9755480a442cabfe8d82b21aba815a71acf1bb6c1776218"}, {file = "greenlet-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be13a18cec649ebaab835dff269e914679ef329204704869f2f167b2c163a9da"},
{file = "greenlet-1.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:181300f826625b7fd1182205b830642926f52bd8cdb08b34574c9d5b2b1813f7"}, {file = "greenlet-1.1.0-cp38-cp38-win32.whl", hash = "sha256:22002259e5b7828b05600a762579fa2f8b33373ad95a0ee57b4d6109d0e589ad"},
{file = "greenlet-1.0.0-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:58ca0f078d1c135ecf1879d50711f925ee238fe773dfe44e206d7d126f5bc664"}, {file = "greenlet-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:206295d270f702bc27dbdbd7651e8ebe42d319139e0d90217b2074309a200da8"},
{file = "greenlet-1.0.0-cp39-cp39-win32.whl", hash = "sha256:5f297cb343114b33a13755032ecf7109b07b9a0020e841d1c3cedff6602cc139"}, {file = "greenlet-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:096cb0217d1505826ba3d723e8981096f2622cde1eb91af9ed89a17c10aa1f3e"},
{file = "greenlet-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:5d69bbd9547d3bc49f8a545db7a0bd69f407badd2ff0f6e1a163680b5841d2b0"}, {file = "greenlet-1.1.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:03f28a5ea20201e70ab70518d151116ce939b412961c33827519ce620957d44c"},
{file = "greenlet-1.0.0.tar.gz", hash = "sha256:719e169c79255816cdcf6dccd9ed2d089a72a9f6c42273aae12d55e8d35bdcf8"}, {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 = [ itsdangerous = [
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, {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"}, {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
] ]
markupsafe = [ markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, {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 = [ more-itertools = [
{file = "more-itertools-8.7.0.tar.gz", hash = "sha256:c5d6da9ca3ff65220c3bfd2a8db06d698f05d4d2b9be57e1deb2be5a45019713"}, {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"},
{file = "more_itertools-8.7.0-py3-none-any.whl", hash = "sha256:5652a9ac72209ed7df8d9c15daf4e1aa0e3d2ccd3c87f8265a0673cd9cbc9ced"}, {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 = [ packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, {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 = [ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {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-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, {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 = [ sqlalchemy = [
{file = "SQLAlchemy-1.4.12-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:8c71a80a5474e6e9c9bbf1957ab1c73cdece9d33cfb26d9ea6e7aed41f535cd6"}, {file = "SQLAlchemy-1.4.17-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c367ed95d41df584f412a9419b5ece85b0d6c2a08a51ae13ae47ef74ff9a9349"},
{file = "SQLAlchemy-1.4.12-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b1d513ebb16a204c87296d774c2317950191583b34032540948f20096b63efe4"}, {file = "SQLAlchemy-1.4.17-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fdad4a33140b77df61d456922b7974c1f1bb2c35238f6809f078003a620c4734"},
{file = "SQLAlchemy-1.4.12-cp27-cp27m-win32.whl", hash = "sha256:4b749cdedf1afb613c3d31235258110e1f36231c15df9b8b63b3f13c712e4790"}, {file = "SQLAlchemy-1.4.17-cp27-cp27m-win32.whl", hash = "sha256:f1c68f7bd4a57ffdb85eab489362828dddf6cd565a4c18eda4c446c1d5d3059d"},
{file = "SQLAlchemy-1.4.12-cp27-cp27m-win_amd64.whl", hash = "sha256:b58f09f4ea42a92e0a8923f4598001f8935bd2ed0c4c6abb9903c5b4cd0d4015"}, {file = "SQLAlchemy-1.4.17-cp27-cp27m-win_amd64.whl", hash = "sha256:ee6e7ca09ff274c55d19a1e15ee6f884fa0230c0d9b8d22a456e249d08dee5bf"},
{file = "SQLAlchemy-1.4.12-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:b4bf83b05056349265b40de37c836517649ea9edd174301072f5a58c7b374f94"}, {file = "SQLAlchemy-1.4.17-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5f00a2be7d777119e15ccfb5ba0b2a92e8a193959281089d79821a001095f80"},
{file = "SQLAlchemy-1.4.12-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:c94fe5ec27dec6a994293d1f194a97fcb904252526bbe72698229ec62c0f7281"}, {file = "SQLAlchemy-1.4.17-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:1dd77acbc19bee9c0ba858ff5e4e5d5c60895495c83b4df9bcdf4ad5e9b74f21"},
{file = "SQLAlchemy-1.4.12-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ac4a48e49e863a4d00d8a5ec94ff5540de1f5bcf96d8d54273a75c3278d8b4af"}, {file = "SQLAlchemy-1.4.17-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5732858e56d32fa7e02468f4fd2d8f01ddf709e5b93d035c637762890f8ed8b6"},
{file = "SQLAlchemy-1.4.12-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e815a729b427bd997d681711dc0b22330e445a0a0c47e16b05d2038e814bd29f"}, {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.12-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:aeb389136f3a39399ebb8e8ee17beba18d361cde9638059cfbf7e896354412b7"}, {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.12-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0c839000817201310a51af390545d7b316fafd6969ef250dad0a6d28c025214d"}, {file = "SQLAlchemy-1.4.17-cp36-cp36m-win32.whl", hash = "sha256:bde055c019e6e449ebc4ec61abd3e08690abeb028c7ada2a3b95d8e352b7b514"},
{file = "SQLAlchemy-1.4.12-cp36-cp36m-win32.whl", hash = "sha256:1e8a884d766fcc918199576bf37f1870327582640fa3302489d7415d815be8a9"}, {file = "SQLAlchemy-1.4.17-cp36-cp36m-win_amd64.whl", hash = "sha256:b0ad951a6e590bbcfbfeadc5748ef5ec8ede505a8119a71b235f7481cc08371c"},
{file = "SQLAlchemy-1.4.12-cp36-cp36m-win_amd64.whl", hash = "sha256:e11ccaa08975e414df6a16466377bb11af692b2a62255c3a70c0993cb2d7f2d7"}, {file = "SQLAlchemy-1.4.17-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:82922a320d38d7d6aa3a8130523ec7e8c70fa95f7ca7d0fd6ec114b626e4b10b"},
{file = "SQLAlchemy-1.4.12-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:deef50c730ddfb4169417a3a3b6393f1e90b0d5c1e62e1d090c1eb1132529f3f"}, {file = "SQLAlchemy-1.4.17-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e133e2551fa99c75849848a4ac08efb79930561eb629dd7d2dc9b7ee05256e6"},
{file = "SQLAlchemy-1.4.12-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a21f41c4cdb76d7f68a6986b9f5c56bdc8eafbc366893d1031df0c367e832388"}, {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.12-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:aec20f0ec5788bee91ecf667e9e30e5ed0add9233b63b0e34e916b21eb5bc850"}, {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.12-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:d5da8fff36593ac96dd3d60a4eb9495a142fb6d3f0ed23baf5567c0ef7aa9b47"}, {file = "SQLAlchemy-1.4.17-cp37-cp37m-win32.whl", hash = "sha256:4d93b62e98248e3e1ac1e91c2e6ee1e7316f704be1f734338b350b6951e6c175"},
{file = "SQLAlchemy-1.4.12-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:a4c9c947fc08d2ac48116c64b7dfbac22b9896619cb74923ba59876504ff6256"}, {file = "SQLAlchemy-1.4.17-cp37-cp37m-win_amd64.whl", hash = "sha256:a2d225c8863a76d15468896dc5af36f1e196b403eb9c7e0151e77ffab9e7df57"},
{file = "SQLAlchemy-1.4.12-cp37-cp37m-win32.whl", hash = "sha256:4c8c335b072967da27fef54fb53e74fadadd7d2167c5eb98f0bfb4bfeb3a6948"}, {file = "SQLAlchemy-1.4.17-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:b59b2c0a3b1d93027f6b6b8379a50c354483fe1ebe796c6740e157bb2e06d39a"},
{file = "SQLAlchemy-1.4.12-cp37-cp37m-win_amd64.whl", hash = "sha256:01b610951c83452ee5e7d912c4ed9db4538b15d66e96ca6696ec38f0c5ce2908"}, {file = "SQLAlchemy-1.4.17-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7222f3236c280fab3a2d76f903b493171f0ffc29667538cc388a5d5dd0216a88"},
{file = "SQLAlchemy-1.4.12-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:6b77880e23d3758db7ad65732304ab1c3a42f0cd20505f4a211750862563a161"}, {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.12-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:f04acd3840bcf33f941b049e24aeef0be5145b2cd5489a89559c11be2d25e262"}, {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.12-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:691568d8238c756011d97a655a76820715cbc0295b7d294aa2f1d62fb0be4361"}, {file = "SQLAlchemy-1.4.17-cp38-cp38-win32.whl", hash = "sha256:dde05ae0987e43ec84e64d6722ce66305eda2a5e2b7d6fda004b37aabdfbb909"},
{file = "SQLAlchemy-1.4.12-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:0646a4caab207279532ffd3f173b4756ae3863f3a94e369b7d1b82831a7ad433"}, {file = "SQLAlchemy-1.4.17-cp38-cp38-win_amd64.whl", hash = "sha256:bc89e37c359dcd4d75b744e5e81af128ba678aa2ecea4be957e80e6e958a1612"},
{file = "SQLAlchemy-1.4.12-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:2b35206c11c415448caf5b7abddbfac6acbe37f79832ae2d1be013f0dfe252ea"}, {file = "SQLAlchemy-1.4.17-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4c5e20666b33b03bf7f14953f0deb93007bf8c1342e985bd7c7cf25f46fac579"},
{file = "SQLAlchemy-1.4.12-cp38-cp38-win32.whl", hash = "sha256:89e755688476b7a925554a1e8a756e0dd6124dfb8fac80470a90cd8424326bee"}, {file = "SQLAlchemy-1.4.17-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f63e1f531a8bf52184e2afb53648511f3f8534decb7575b483a583d3cd8d13ed"},
{file = "SQLAlchemy-1.4.12-cp38-cp38-win_amd64.whl", hash = "sha256:1bc9ea9e54bbaf65fece8b719f56472748f75777806f4f5fadd8112a165eab19"}, {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.12-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:1bdf65dc5263be4651aa34ebe07aa035c61421f145b0d43f4c0b1f3c33bec673"}, {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.12-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f90a42db44427bf98128d823502e0af3f4b83f208e09a3d51df5c2cd7f2a76cf"}, {file = "SQLAlchemy-1.4.17-cp39-cp39-win32.whl", hash = "sha256:6fe1c8dc26bc0005439cb78ebc78772a22cccc773f5a0e67cb3002d791f53f0f"},
{file = "SQLAlchemy-1.4.12-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9047989b8645d8830067dddb2bda544c625419b22b0f546660fd0bfe73341f6"}, {file = "SQLAlchemy-1.4.17-cp39-cp39-win_amd64.whl", hash = "sha256:7eb55d5583076c03aaf1510473fad2a61288490809049cb31028af56af7068ee"},
{file = "SQLAlchemy-1.4.12-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b7ed6ce2e32a68a3b417a848a409ed5b7e4c8e5fa8911b06c77a6be1cc767658"}, {file = "SQLAlchemy-1.4.17.tar.gz", hash = "sha256:651cdb3adcee13624ba22d5ff3e96f91e16a115d2ca489ddc16a8e4c217e8509"},
{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"}, toml = [
{file = "SQLAlchemy-1.4.12-cp39-cp39-win_amd64.whl", hash = "sha256:ce5fc1099d194fbecc8d7c038c927d9daf75cbb83b3b314df3e43e308d67c33e"}, {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "SQLAlchemy-1.4.12.tar.gz", hash = "sha256:968e8cf7f269eaeed1b753cb5df4112be998c933df39421229fc7726c413672c"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
wcwidth = [ wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},

View File

@ -13,6 +13,21 @@ SQLAlchemy = "^1.4.12"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^5.2" 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] [tool.pytest.ini_options]
addopts = "--show-capture=log" addopts = "--show-capture=log"

View File

@ -13,7 +13,7 @@ import amanuensis.backend.user as userq
@pytest.fixture @pytest.fixture
def db(): def db():
"""Provides an initialized database in memory.""" """Provides an initialized database in memory."""
db = DbContext('sqlite:///:memory:', debug=False) db = DbContext("sqlite:///:memory:", debug=False)
db.create_all() db.create_all()
return db return db
@ -21,58 +21,66 @@ def db():
@pytest.fixture @pytest.fixture
def make_user(db: DbContext): def make_user(db: DbContext):
"""Provides a factory function for creating users, with valid default values.""" """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 = { default_kwargs = {
'username': f'test_user_{state["nonce"]}', "username": f'test_user_{state["nonce"]}',
'password': 'password', "password": "password",
'display_name': None, "display_name": None,
'email': 'user@example.com', "email": "user@example.com",
'is_site_admin': False, "is_site_admin": False,
} }
state['nonce'] += 1 state["nonce"] += 1
updated_kwargs = {**default_kwargs, **kwargs} updated_kwargs = {**default_kwargs, **kwargs}
return userq.create(db, **updated_kwargs) return userq.create(db, **updated_kwargs)
return user_factory return user_factory
@pytest.fixture @pytest.fixture
def make_lexicon(db: DbContext): def make_lexicon(db: DbContext):
"""Provides a factory function for creating lexicons, with valid default values.""" """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 = { default_kwargs = {
'name': f'Test_{state["nonce"]}', "name": f'Test_{state["nonce"]}',
'title': None, "title": None,
'prompt': f'Test Lexicon game {state["nonce"]}' "prompt": f'Test Lexicon game {state["nonce"]}',
} }
state['nonce'] += 1 state["nonce"] += 1
updated_kwargs = {**default_kwargs, **kwargs} updated_kwargs = {**default_kwargs, **kwargs}
return lexiq.create(db, **updated_kwargs) return lexiq.create(db, **updated_kwargs)
return lexicon_factory return lexicon_factory
@pytest.fixture @pytest.fixture
def make_membership(db: DbContext): def make_membership(db: DbContext):
"""Provides a factory function for creating memberships, with valid default values.""" """Provides a factory function for creating memberships, with valid default values."""
def membership_factory(**kwargs): def membership_factory(**kwargs):
default_kwargs = { default_kwargs = {
'is_editor': False, "is_editor": False,
} }
updated_kwargs = {**default_kwargs, **kwargs} updated_kwargs = {**default_kwargs, **kwargs}
return memq.create(db, **updated_kwargs) return memq.create(db, **updated_kwargs)
return membership_factory return membership_factory
@pytest.fixture @pytest.fixture
def make_character(db: DbContext): def make_character(db: DbContext):
"""Provides a factory function for creating characters, with valid default values.""" """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 = { default_kwargs = {
'name': f'Character {state["nonce"]}', "name": f'Character {state["nonce"]}',
'signature': None, "signature": None,
} }
state['nonce'] += 1 state["nonce"] += 1
updated_kwargs = {**default_kwargs, **kwargs} updated_kwargs = {**default_kwargs, **kwargs}
return charq.create(db, **updated_kwargs) return charq.create(db, **updated_kwargs)
return character_factory return character_factory
@ -87,11 +95,8 @@ class TestFactory:
@pytest.fixture @pytest.fixture
def make( def make(
db: DbContext, db: DbContext, make_user, make_lexicon, make_membership, make_character
make_user, ) -> TestFactory:
make_lexicon,
make_membership,
make_character) -> TestFactory:
"""Fixture that groups all factory fixtures together.""" """Fixture that groups all factory fixtures together."""
return TestFactory( return TestFactory(
db, db,
@ -109,6 +114,8 @@ def lexicon_with_editor(make):
assert editor assert editor
lexicon = make.lexicon() lexicon = make.lexicon()
assert 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 assert membership
return (lexicon, editor) return (lexicon, editor)

View File

@ -9,33 +9,33 @@ def test_create_character(db: DbContext, lexicon_with_editor, make):
"""Test creating a character.""" """Test creating a character."""
lexicon, user = lexicon_with_editor lexicon, user = lexicon_with_editor
kwargs = { kwargs = {
'db': db, "db": db,
'user_id': user.id, "user_id": user.id,
'lexicon_id': lexicon.id, "lexicon_id": lexicon.id,
'name': 'Character Name', "name": "Character Name",
'signature': 'Signature', "signature": "Signature",
} }
# Bad argument types # Bad argument types
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, 'name': b'bytestring'}) charq.create(**{**kwargs, "name": b"bytestring"})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, 'name': None}) charq.create(**{**kwargs, "name": None})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, 'signature': b'bytestring'}) charq.create(**{**kwargs, "signature": b"bytestring"})
# Bad character name # Bad character name
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, 'name': ' '}) charq.create(**{**kwargs, "name": " "})
# Signature is auto-populated # Signature is auto-populated
char = charq.create(**{**kwargs, 'signature': None}) char = charq.create(**{**kwargs, "signature": None})
assert char.signature is not None assert char.signature is not None
# User must be in lexicon # User must be in lexicon
new_user = make.user() new_user = make.user()
with pytest.raises(ArgumentError): 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): 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 # Set character limit to one and create a character
lexicon.character_limit = 1 lexicon.character_limit = 1
db.session.commit() db.session.commit()
char1 = charq.create(db, lexicon.id, user.id, 'Test Character 1', signature=None) char1 = charq.create(db, lexicon.id, user.id, "Test Character 1", signature=None)
assert char1.id, 'Failed to create character 1' assert char1.id, "Failed to create character 1"
# Creating a second character should fail # Creating a second character should fail
with pytest.raises(ArgumentError): 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 assert char2
# Raising the limit to 2 should allow a second character # Raising the limit to 2 should allow a second character
lexicon.character_limit = 2 lexicon.character_limit = 2
db.session.commit() db.session.commit()
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.id, 'Failed to create character 2' assert char2.id, "Failed to create character 2"
# Creating a third character should fail # Creating a third character should fail
with pytest.raises(ArgumentError): 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 assert char3
# Setting the limit to null should allow a third character # Setting the limit to null should allow a third character
lexicon.character_limit = None lexicon.character_limit = None
db.session.commit() db.session.commit()
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.id, 'Failed to create character 3' assert char3.id, "Failed to create character 3"

View File

@ -9,24 +9,20 @@ from amanuensis.errors import ArgumentError
def test_create_lexicon(db: DbContext): def test_create_lexicon(db: DbContext):
"""Test new game creation.""" """Test new game creation."""
kwargs = { kwargs = {"name": "Test", "title": None, "prompt": "A test Lexicon game"}
'name': 'Test',
'title': None,
'prompt': 'A test Lexicon game'
}
# Test name constraints # Test name constraints
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, 'name': None}) lexiq.create(db, **{**kwargs, "name": None})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, 'name': ''}) lexiq.create(db, **{**kwargs, "name": ""})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, 'name': ' '}) lexiq.create(db, **{**kwargs, "name": " "})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, 'name': '..'}) lexiq.create(db, **{**kwargs, "name": ".."})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, 'name': '\x00'}) lexiq.create(db, **{**kwargs, "name": "\x00"})
with pytest.raises(ArgumentError): 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 # Validate that creation populates fields, including timestamps
before = datetime.datetime.utcnow() - datetime.timedelta(seconds=1) before = datetime.datetime.utcnow() - datetime.timedelta(seconds=1)

View File

@ -11,13 +11,13 @@ def test_create_membership(db: DbContext, make):
"""Test joining a game.""" """Test joining a game."""
# Set up a user and a lexicon # Set up a user and a lexicon
new_user = make.user() new_user = make.user()
assert new_user.id, 'Failed to create user' assert new_user.id, "Failed to create user"
new_lexicon = make.lexicon() 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 # Add the user to the lexicon as an editor
mem = memq.create(db, new_user.id, new_lexicon.id, True) 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 # 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)) assert any(map(lambda mem: mem.lexicon == new_lexicon, new_user.memberships))

View File

@ -11,29 +11,25 @@ def test_create_post(db: DbContext, lexicon_with_editor):
lexicon, editor = lexicon_with_editor lexicon, editor = lexicon_with_editor
# argument dictionary for post object # argument dictionary for post object
kwargs = { kwargs = {"lexicon_id": lexicon.id, "user_id": editor.id, "body": "body"}
'lexicon_id': lexicon.id,
'user_id': editor.id,
'body': 'body'
}
# ids are integers # ids are integers
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
postq.create(db, **{**kwargs, 'user_id': 'zero'}) postq.create(db, **{**kwargs, "user_id": "zero"})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
postq.create(db, **{**kwargs, 'lexicon_id': 'zero'}) postq.create(db, **{**kwargs, "lexicon_id": "zero"})
# empty arguments don't work # empty arguments don't work
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
postq.create(db, **{**kwargs, 'lexicon_id': ''}) postq.create(db, **{**kwargs, "lexicon_id": ""})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
postq.create(db, **{**kwargs, 'user_id': ''}) postq.create(db, **{**kwargs, "user_id": ""})
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
postq.create(db, **{**kwargs, 'body': ''}) postq.create(db, **{**kwargs, "body": ""})
# post with only whitespace doesn't work # post with only whitespace doesn't work
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
postq.create(db, **{**kwargs, 'body': ' '}) postq.create(db, **{**kwargs, "body": " "})
# post creation works and populates fields # post creation works and populates fields
new_post = postq.create(db, **kwargs) 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 assert new_post.body is not None
# post creation works when user is 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
assert new_post.user_id is None assert new_post.user_id is None

View File

@ -8,24 +8,26 @@ from amanuensis.errors import ArgumentError
def test_create_user(db: DbContext): def test_create_user(db: DbContext):
"""Test new user creation.""" """Test new user creation."""
kwargs = { kwargs = {
'username': 'username', "username": "username",
'password': 'password', "password": "password",
'display_name': 'User Name', "display_name": "User Name",
'email': 'user@example.com', "email": "user@example.com",
'is_site_admin': False "is_site_admin": False,
} }
# Test length constraints # Test length constraints
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
userq.create(db, **{**kwargs, 'username': 'me'}) userq.create(db, **{**kwargs, "username": "me"})
with pytest.raises(ArgumentError): 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 # Test allowed characters
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
userq.create(db, **{**kwargs, 'username': 'user name'}) userq.create(db, **{**kwargs, "username": "user name"})
# No password # No password
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
userq.create(db, **{**kwargs, 'password': None}) userq.create(db, **{**kwargs, "password": None})
# Valid creation works and populates fields # Valid creation works and populates fields
new_user = userq.create(db, **kwargs) new_user = userq.create(db, **kwargs)
@ -38,6 +40,6 @@ def test_create_user(db: DbContext):
duplicate = userq.create(db, **kwargs) duplicate = userq.create(db, **kwargs)
# Missing display name populates with username # 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) user2 = userq.create(db, **user2_kw)
assert user2.display_name is not None assert user2.display_name is not None