Adopt black as a code formatter #6
|
@ -8,11 +8,7 @@ from amanuensis.db import *
|
|||
from amanuensis.errors import ArgumentError
|
||||
|
||||
|
||||
def create(
|
||||
db: DbContext,
|
||||
lexicon_id: int,
|
||||
user_id: int,
|
||||
character_id: int) -> Article:
|
||||
def create(db: DbContext, lexicon_id: int, user_id: int, character_id: int) -> Article:
|
||||
"""
|
||||
Create a new article in a lexicon.
|
||||
"""
|
||||
|
@ -37,8 +33,7 @@ def create(
|
|||
# and the character belongs to the lexicon
|
||||
if character_id is not None:
|
||||
character: Character = db(
|
||||
select(Character)
|
||||
.where(Character.id == character_id)
|
||||
select(Character).where(Character.id == character_id)
|
||||
).scalar_one_or_none()
|
||||
if not character:
|
||||
raise ArgumentError('Character does not exist')
|
||||
|
|
|
@ -9,11 +9,8 @@ from amanuensis.errors import ArgumentError
|
|||
|
||||
|
||||
def create(
|
||||
db: DbContext,
|
||||
lexicon_id: int,
|
||||
user_id: int,
|
||||
name: str,
|
||||
signature: str) -> Character:
|
||||
db: DbContext, lexicon_id: int, user_id: int, name: str, signature: str
|
||||
) -> Character:
|
||||
"""
|
||||
Create a new character for a user.
|
||||
"""
|
||||
|
@ -50,7 +47,10 @@ def create(
|
|||
.where(Character.lexicon_id == lexicon_id)
|
||||
.where(Character.user_id == user_id)
|
||||
).scalar()
|
||||
if mem.lexicon.character_limit is not None and num_user_chars >= mem.lexicon.character_limit:
|
||||
if (
|
||||
mem.lexicon.character_limit is not None
|
||||
and num_user_chars >= mem.lexicon.character_limit
|
||||
):
|
||||
raise ArgumentError('User is at character limit')
|
||||
|
||||
new_character = Character(
|
||||
|
|
|
@ -13,11 +13,7 @@ from amanuensis.errors import ArgumentError
|
|||
RE_ALPHANUM_DASH_UNDER = re.compile(r'^[A-Za-z0-9-_]*$')
|
||||
|
||||
|
||||
def create(
|
||||
db: DbContext,
|
||||
name: str,
|
||||
title: str,
|
||||
prompt: str) -> Lexicon:
|
||||
def create(db: DbContext, name: str, title: str, prompt: str) -> Lexicon:
|
||||
"""
|
||||
Create a new lexicon.
|
||||
"""
|
||||
|
@ -27,7 +23,9 @@ def create(
|
|||
if not name.strip():
|
||||
raise ArgumentError('Lexicon name must not be blank')
|
||||
if not RE_ALPHANUM_DASH_UNDER.match(name):
|
||||
raise ArgumentError('Lexicon name may only contain alphanumerics, dash, and underscore')
|
||||
raise ArgumentError(
|
||||
'Lexicon name may only contain alphanumerics, dash, and underscore'
|
||||
)
|
||||
|
||||
# Verify title
|
||||
if title is not None and not isinstance(name, str):
|
||||
|
@ -38,10 +36,7 @@ def create(
|
|||
raise ArgumentError('Lexicon prompt must be a string')
|
||||
|
||||
# Query the db to make sure the lexicon name isn't taken
|
||||
if db(
|
||||
select(func.count(Lexicon.id))
|
||||
.where(Lexicon.name == name)
|
||||
).scalar() > 0:
|
||||
if db(select(func.count(Lexicon.id)).where(Lexicon.name == name)).scalar() > 0:
|
||||
raise ArgumentError('Lexicon name is already taken')
|
||||
|
||||
new_lexicon = Lexicon(
|
||||
|
|
|
@ -8,11 +8,7 @@ from amanuensis.db import DbContext, Membership
|
|||
from amanuensis.errors import ArgumentError
|
||||
|
||||
|
||||
def create(
|
||||
db: DbContext,
|
||||
user_id: int,
|
||||
lexicon_id: int,
|
||||
is_editor: bool) -> Membership:
|
||||
def create(db: DbContext, user_id: int, lexicon_id: int, is_editor: bool) -> Membership:
|
||||
"""
|
||||
Create a new user membership in a lexicon.
|
||||
"""
|
||||
|
@ -25,11 +21,14 @@ def create(
|
|||
raise ArgumentError('is_editor')
|
||||
|
||||
# Verify user has not already joined lexicon
|
||||
if db(
|
||||
select(func.count(Membership.id))
|
||||
.where(Membership.user_id == user_id)
|
||||
.where(Membership.lexicon_id == lexicon_id)
|
||||
).scalar() > 0:
|
||||
if (
|
||||
db(
|
||||
select(func.count(Membership.id))
|
||||
.where(Membership.user_id == user_id)
|
||||
.where(Membership.lexicon_id == lexicon_id)
|
||||
).scalar()
|
||||
> 0
|
||||
):
|
||||
raise ArgumentError('User is already a member of lexicon')
|
||||
|
||||
new_membership = Membership(
|
||||
|
|
|
@ -9,11 +9,8 @@ from sqlalchemy import select, func
|
|||
from amanuensis.db import DbContext, Post
|
||||
from amanuensis.errors import ArgumentError
|
||||
|
||||
def create(
|
||||
db: DbContext,
|
||||
lexicon_id: int,
|
||||
user_id: int,
|
||||
body: str) -> Post:
|
||||
|
||||
def create(db: DbContext, lexicon_id: int, user_id: int, body: str) -> Post:
|
||||
"""
|
||||
Create a new post
|
||||
"""
|
||||
|
@ -32,11 +29,7 @@ def create(
|
|||
if not body.strip():
|
||||
raise ArgumentError('Post body cannot be empty.')
|
||||
|
||||
new_post = Post(
|
||||
lexicon_id=lexicon_id,
|
||||
user_id=user_id,
|
||||
body=body
|
||||
)
|
||||
new_post = Post(lexicon_id=lexicon_id, user_id=user_id, body=body)
|
||||
db.session.add(new_post)
|
||||
db.session.commit()
|
||||
return new_post
|
||||
|
|
|
@ -11,7 +11,7 @@ from amanuensis.db import DbContext, User
|
|||
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-_]*$')
|
||||
|
||||
|
||||
|
@ -21,7 +21,8 @@ def create(
|
|||
password: str,
|
||||
display_name: str,
|
||||
email: str,
|
||||
is_site_admin: bool) -> User:
|
||||
is_site_admin: bool,
|
||||
) -> User:
|
||||
"""
|
||||
Create a new user.
|
||||
"""
|
||||
|
@ -33,7 +34,9 @@ def create(
|
|||
if RE_NO_LETTERS.match(username):
|
||||
raise ArgumentError('Username must contain a letter')
|
||||
if not RE_ALPHANUM_DASH_UNDER.match(username):
|
||||
raise ArgumentError('Username may only contain alphanumerics, dash, and underscore')
|
||||
raise ArgumentError(
|
||||
'Username may only contain alphanumerics, dash, and underscore'
|
||||
)
|
||||
|
||||
# Verify password
|
||||
if not isinstance(password, str):
|
||||
|
@ -51,10 +54,7 @@ def create(
|
|||
raise ArgumentError('Email must be a string')
|
||||
|
||||
# Query the db to make sure the username isn't taken
|
||||
if db(
|
||||
select(func.count(User.id))
|
||||
.where(User.username == username)
|
||||
).scalar() > 0:
|
||||
if db(select(func.count(User.id)).where(User.username == username)).scalar() > 0:
|
||||
raise ArgumentError('Username is already taken')
|
||||
|
||||
new_user = User(
|
||||
|
|
|
@ -28,4 +28,4 @@ __all__ = [
|
|||
'ArticleContentRuleType',
|
||||
'ArticleContentRule',
|
||||
'Post',
|
||||
]
|
||||
]
|
||||
|
|
|
@ -8,22 +8,25 @@ from sqlalchemy.orm import sessionmaker
|
|||
|
||||
|
||||
# Define naming conventions for generated constraints
|
||||
metadata = MetaData(naming_convention={
|
||||
"ix": "ix_%(column_0_label)s",
|
||||
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
||||
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
||||
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
||||
"pk": "pk_%(table_name)s"
|
||||
})
|
||||
metadata = MetaData(
|
||||
naming_convention={
|
||||
"ix": "ix_%(column_0_label)s",
|
||||
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
||||
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
||||
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
||||
"pk": "pk_%(table_name)s",
|
||||
}
|
||||
)
|
||||
|
||||
# Base class for ORM models
|
||||
ModelBase = declarative_base(metadata=metadata)
|
||||
|
||||
|
||||
class DbContext():
|
||||
class DbContext:
|
||||
def __init__(self, db_uri, debug=False):
|
||||
# Create an engine and enable foreign key constraints in sqlite
|
||||
self.engine = create_engine(db_uri, echo=debug)
|
||||
|
||||
@event.listens_for(self.engine, "connect")
|
||||
def set_sqlite_pragma(dbapi_connection, connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
|
|
|
@ -28,6 +28,7 @@ class Uuid(TypeDecorator):
|
|||
"""
|
||||
A uuid backed by a char(32) field in sqlite.
|
||||
"""
|
||||
|
||||
impl = CHAR(32)
|
||||
|
||||
def process_bind_param(self, value, dialect):
|
||||
|
@ -51,6 +52,7 @@ class User(ModelBase):
|
|||
"""
|
||||
Represents a single user of Amanuensis.
|
||||
"""
|
||||
|
||||
__tablename__ = 'user'
|
||||
|
||||
#############
|
||||
|
@ -104,6 +106,7 @@ class Lexicon(ModelBase):
|
|||
"""
|
||||
Represents a single game of Lexicon.
|
||||
"""
|
||||
|
||||
__tablename__ = 'lexicon'
|
||||
|
||||
#############
|
||||
|
@ -131,7 +134,9 @@ class Lexicon(ModelBase):
|
|||
created = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP'))
|
||||
|
||||
# The timestamp of the last change in game state
|
||||
last_updated = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP'))
|
||||
last_updated = Column(
|
||||
DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')
|
||||
)
|
||||
|
||||
# The timestamp the first turn was started
|
||||
# This is NULL until the game starts
|
||||
|
@ -234,10 +239,9 @@ class Membership(ModelBase):
|
|||
"""
|
||||
Represents a user's participation in a Lexicon game.
|
||||
"""
|
||||
|
||||
__tablename__ = 'membership'
|
||||
__table_args__ = (
|
||||
UniqueConstraint('user_id', 'lexicon_id'),
|
||||
)
|
||||
__table_args__ = (UniqueConstraint('user_id', 'lexicon_id'),)
|
||||
|
||||
###################
|
||||
# Membership keys #
|
||||
|
@ -295,6 +299,7 @@ class Character(ModelBase):
|
|||
"""
|
||||
Represents a character played by a uaser in a Lexicon game.
|
||||
"""
|
||||
|
||||
__tablename__ = 'character'
|
||||
|
||||
##################
|
||||
|
@ -333,6 +338,7 @@ class ArticleState(enum.Enum):
|
|||
"""
|
||||
The step of the editorial process an article is in.
|
||||
"""
|
||||
|
||||
DRAFT = 0
|
||||
SUBMITTED = 1
|
||||
APPROVED = 2
|
||||
|
@ -342,6 +348,7 @@ class Article(ModelBase):
|
|||
"""
|
||||
Represents a single article in a lexicon.
|
||||
"""
|
||||
|
||||
__tablename__ = 'article'
|
||||
|
||||
################
|
||||
|
@ -386,7 +393,9 @@ class Article(ModelBase):
|
|||
####################
|
||||
|
||||
# Timestamp the content of the article was last updated
|
||||
last_updated = Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP'))
|
||||
last_updated = Column(
|
||||
DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP')
|
||||
)
|
||||
|
||||
# Timestamp the article was last submitted
|
||||
# This is NULL until the article is submitted
|
||||
|
@ -420,6 +429,7 @@ class IndexType(enum.Enum):
|
|||
"""
|
||||
The title-matching behavior of an article index.
|
||||
"""
|
||||
|
||||
CHAR = 0
|
||||
RANGE = 1
|
||||
PREFIX = 2
|
||||
|
@ -430,6 +440,7 @@ class ArticleIndex(ModelBase):
|
|||
"""
|
||||
Represents an index definition.
|
||||
"""
|
||||
|
||||
__tablename__ = 'article_index'
|
||||
|
||||
##############
|
||||
|
@ -472,6 +483,7 @@ class ArticleIndexRule(ModelBase):
|
|||
A character with multiple index rules may write in any index that satisfies
|
||||
a rule. A character with no index rules may write in any index.
|
||||
"""
|
||||
|
||||
__tablename__ = 'article_index_rule'
|
||||
|
||||
###################
|
||||
|
@ -510,6 +522,7 @@ class ArticleContentRuleType(enum.Enum):
|
|||
"""
|
||||
The possible article content rules.
|
||||
"""
|
||||
|
||||
# Whether characters can cite themselves
|
||||
ALLOW_SELF_CITE = 0
|
||||
# Whether characters can write new articles instead of phantoms
|
||||
|
@ -543,6 +556,7 @@ class ArticleContentRule(ModelBase):
|
|||
"""
|
||||
Represents a restriction on the content of an article for a turn.
|
||||
"""
|
||||
|
||||
__tablename__ = 'article_content_rule'
|
||||
|
||||
#####################
|
||||
|
@ -584,6 +598,7 @@ class Post(ModelBase):
|
|||
"""
|
||||
Represents a post in the game feed.
|
||||
"""
|
||||
|
||||
__tablename__ = 'post'
|
||||
|
||||
#############
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Submodule of custom exception types
|
||||
"""
|
||||
|
||||
|
||||
class AmanuensisError(Exception):
|
||||
"""Base class for exceptions in amanuensis"""
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ def db():
|
|||
@pytest.fixture
|
||||
def make_user(db: DbContext):
|
||||
"""Provides a factory function for creating users, with valid default values."""
|
||||
|
||||
def user_factory(state={'nonce': 1}, **kwargs):
|
||||
default_kwargs = {
|
||||
'username': f'test_user_{state["nonce"]}',
|
||||
|
@ -32,39 +33,45 @@ def make_user(db: DbContext):
|
|||
state['nonce'] += 1
|
||||
updated_kwargs = {**default_kwargs, **kwargs}
|
||||
return userq.create(db, **updated_kwargs)
|
||||
|
||||
return user_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_lexicon(db: DbContext):
|
||||
"""Provides a factory function for creating lexicons, with valid default values."""
|
||||
|
||||
def lexicon_factory(state={'nonce': 1}, **kwargs):
|
||||
default_kwargs = {
|
||||
'name': f'Test_{state["nonce"]}',
|
||||
'title': None,
|
||||
'prompt': f'Test Lexicon game {state["nonce"]}'
|
||||
'prompt': f'Test Lexicon game {state["nonce"]}',
|
||||
}
|
||||
state['nonce'] += 1
|
||||
updated_kwargs = {**default_kwargs, **kwargs}
|
||||
return lexiq.create(db, **updated_kwargs)
|
||||
|
||||
return lexicon_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_membership(db: DbContext):
|
||||
"""Provides a factory function for creating memberships, with valid default values."""
|
||||
|
||||
def membership_factory(**kwargs):
|
||||
default_kwargs = {
|
||||
'is_editor': False,
|
||||
}
|
||||
updated_kwargs = {**default_kwargs, **kwargs}
|
||||
return memq.create(db, **updated_kwargs)
|
||||
|
||||
return membership_factory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_character(db: DbContext):
|
||||
"""Provides a factory function for creating characters, with valid default values."""
|
||||
|
||||
def character_factory(state={'nonce': 1}, **kwargs):
|
||||
default_kwargs = {
|
||||
'name': f'Character {state["nonce"]}',
|
||||
|
@ -73,6 +80,7 @@ def make_character(db: DbContext):
|
|||
state['nonce'] += 1
|
||||
updated_kwargs = {**default_kwargs, **kwargs}
|
||||
return charq.create(db, **updated_kwargs)
|
||||
|
||||
return character_factory
|
||||
|
||||
|
||||
|
@ -87,11 +95,8 @@ class TestFactory:
|
|||
|
||||
@pytest.fixture
|
||||
def make(
|
||||
db: DbContext,
|
||||
make_user,
|
||||
make_lexicon,
|
||||
make_membership,
|
||||
make_character) -> TestFactory:
|
||||
db: DbContext, make_user, make_lexicon, make_membership, make_character
|
||||
) -> TestFactory:
|
||||
"""Fixture that groups all factory fixtures together."""
|
||||
return TestFactory(
|
||||
db,
|
||||
|
@ -109,6 +114,8 @@ def lexicon_with_editor(make):
|
|||
assert editor
|
||||
lexicon = make.lexicon()
|
||||
assert lexicon
|
||||
membership = make.membership(user_id=editor.id, lexicon_id=lexicon.id, is_editor=True)
|
||||
membership = make.membership(
|
||||
user_id=editor.id, lexicon_id=lexicon.id, is_editor=True
|
||||
)
|
||||
assert membership
|
||||
return (lexicon, editor)
|
||||
|
|
|
@ -52,7 +52,9 @@ def test_character_limits(db: DbContext, lexicon_with_editor):
|
|||
|
||||
# Creating a second character should fail
|
||||
with pytest.raises(ArgumentError):
|
||||
char2 = charq.create(db, lexicon.id, user.id, 'Test Character 2', signature=None)
|
||||
char2 = charq.create(
|
||||
db, lexicon.id, user.id, 'Test Character 2', signature=None
|
||||
)
|
||||
assert char2
|
||||
|
||||
# Raising the limit to 2 should allow a second character
|
||||
|
@ -63,7 +65,9 @@ def test_character_limits(db: DbContext, lexicon_with_editor):
|
|||
|
||||
# Creating a third character should fail
|
||||
with pytest.raises(ArgumentError):
|
||||
char3 = charq.create(db, lexicon.id, user.id, 'Test Character 3', signature=None)
|
||||
char3 = charq.create(
|
||||
db, lexicon.id, user.id, 'Test Character 3', signature=None
|
||||
)
|
||||
assert char3
|
||||
|
||||
# Setting the limit to null should allow a third character
|
||||
|
|
|
@ -9,11 +9,7 @@ from amanuensis.errors import ArgumentError
|
|||
|
||||
def test_create_lexicon(db: DbContext):
|
||||
"""Test new game creation."""
|
||||
kwargs = {
|
||||
'name': 'Test',
|
||||
'title': None,
|
||||
'prompt': 'A test Lexicon game'
|
||||
}
|
||||
kwargs = {'name': 'Test', 'title': None, 'prompt': 'A test Lexicon game'}
|
||||
# Test name constraints
|
||||
with pytest.raises(ArgumentError):
|
||||
lexiq.create(db, **{**kwargs, 'name': None})
|
||||
|
|
|
@ -11,11 +11,7 @@ def test_create_post(db: DbContext, lexicon_with_editor):
|
|||
lexicon, editor = lexicon_with_editor
|
||||
|
||||
# argument dictionary for post object
|
||||
kwargs = {
|
||||
'lexicon_id': lexicon.id,
|
||||
'user_id': editor.id,
|
||||
'body': 'body'
|
||||
}
|
||||
kwargs = {'lexicon_id': lexicon.id, 'user_id': editor.id, 'body': 'body'}
|
||||
|
||||
# ids are integers
|
||||
with pytest.raises(ArgumentError):
|
||||
|
|
|
@ -12,14 +12,16 @@ def test_create_user(db: DbContext):
|
|||
'password': 'password',
|
||||
'display_name': 'User Name',
|
||||
'email': 'user@example.com',
|
||||
'is_site_admin': False
|
||||
'is_site_admin': False,
|
||||
}
|
||||
|
||||
# Test length constraints
|
||||
with pytest.raises(ArgumentError):
|
||||
userq.create(db, **{**kwargs, 'username': 'me'})
|
||||
with pytest.raises(ArgumentError):
|
||||
userq.create(db, **{**kwargs, 'username': 'the right honorable user-name, esquire'})
|
||||
userq.create(
|
||||
db, **{**kwargs, 'username': 'the right honorable user-name, esquire'}
|
||||
)
|
||||
# Test allowed characters
|
||||
with pytest.raises(ArgumentError):
|
||||
userq.create(db, **{**kwargs, 'username': 'user name'})
|
||||
|
|
Loading…
Reference in New Issue