Adopt mypy as a static type checker #7

Merged
Jaculabilis merged 2 commits from tvb/typecheck into develop 2021-06-03 04:40:51 +00:00
12 changed files with 132 additions and 60 deletions
Showing only changes of commit 705df21c17 - Show all commits

View File

@ -2,13 +2,20 @@
Article query interface
"""
from typing import Optional
from sqlalchemy import select
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: Optional[int],
) -> Article:
"""
Create a new article in a lexicon.
"""

View File

@ -2,6 +2,8 @@
Character query interface
"""
from typing import Optional
from sqlalchemy import select, func
from amanuensis.db import *
@ -9,7 +11,11 @@ from amanuensis.errors import ArgumentError
def create(
db: DbContext, lexicon_id: int, user_id: int, name: str, signature: str
db: DbContext,
lexicon_id: int,
user_id: int,
name: str,
signature: Optional[str],
) -> Character:
"""
Create a new character for a user.

View File

@ -13,7 +13,12 @@ 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.
"""

View File

@ -8,7 +8,12 @@ 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.
"""

View File

@ -10,7 +10,12 @@ 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
"""

View File

@ -11,7 +11,7 @@ import amanuensis.backend.user as userq
@pytest.fixture
def db():
def db() -> DbContext:
"""Provides an initialized database in memory."""
db = DbContext("sqlite:///:memory:", debug=False)
db.create_all()

View File

@ -1,6 +1,7 @@
import pytest
from amanuensis.db import DbContext
from amanuensis.db.models import Character, Lexicon, User
import amanuensis.backend.article as artiq
from amanuensis.errors import ArgumentError
@ -9,18 +10,18 @@ from amanuensis.errors import ArgumentError
def test_create_article(db: DbContext, make):
"""Test new article creation"""
# Create two users in a shared lexicon
user1 = make.user()
user2 = make.user()
lexicon1 = make.lexicon()
user1: User = make.user()
user2: User = make.user()
lexicon1: Lexicon = make.lexicon()
make.membership(user_id=user1.id, lexicon_id=lexicon1.id)
make.membership(user_id=user2.id, lexicon_id=lexicon1.id)
char1_1 = make.character(lexicon_id=lexicon1.id, user_id=user1.id)
char1_2 = make.character(lexicon_id=lexicon1.id, user_id=user2.id)
char1_1: Character = make.character(lexicon_id=lexicon1.id, user_id=user1.id)
char1_2: Character = make.character(lexicon_id=lexicon1.id, user_id=user2.id)
# Create a lexicon that only one user is in
lexicon2 = make.lexicon()
lexicon2: Lexicon = make.lexicon()
make.membership(user_id=user2.id, lexicon_id=lexicon2.id)
char2_2 = make.character(lexicon_id=lexicon2.id, user_id=user2.id)
char2_2: Character = make.character(lexicon_id=lexicon2.id, user_id=user2.id)
# User cannot create article for another user's character
with pytest.raises(ArgumentError):

View File

@ -7,35 +7,44 @@ from amanuensis.errors import ArgumentError
def test_create_character(db: DbContext, lexicon_with_editor, make):
"""Test creating a character."""
lexicon: Lexicon
user: User
lexicon, user = lexicon_with_editor
kwargs = {
defaults: dict = {
"db": db,
"user_id": user.id,
"lexicon_id": lexicon.id,
"name": "Character Name",
"signature": "Signature",
}
kwargs: dict
# Bad argument types
with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "name": b"bytestring"})
kwargs = {**defaults, "name": b"bytestring"}
charq.create(**kwargs)
with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "name": None})
kwargs = {**defaults, "name": None}
charq.create(**kwargs)
with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "signature": b"bytestring"})
kwargs = {**defaults, "signature": b"bytestring"}
charq.create(**kwargs)
# Bad character name
with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "name": " "})
kwargs = {**defaults, "name": " "}
charq.create(**kwargs)
# Signature is auto-populated
char = charq.create(**{**kwargs, "signature": None})
kwargs = {**defaults, "signature": None}
char = charq.create(**kwargs)
assert char.signature is not None
# User must be in lexicon
new_user = make.user()
new_user: User = make.user()
with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "user_id": new_user.id})
kwargs = {**defaults, "user_id": new_user.id}
charq.create(**kwargs)
def test_character_limits(db: DbContext, lexicon_with_editor):
@ -47,12 +56,14 @@ def test_character_limits(db: DbContext, lexicon_with_editor):
# Set character limit to one and create a character
lexicon.character_limit = 1
db.session.commit()
char1 = charq.create(db, lexicon.id, user.id, "Test Character 1", signature=None)
char1: Character = charq.create(
db, lexicon.id, user.id, "Test Character 1", signature=None
)
assert char1.id, "Failed to create character 1"
# Creating a second character should fail
with pytest.raises(ArgumentError):
char2 = charq.create(
char2: Character = charq.create(
db, lexicon.id, user.id, "Test Character 2", signature=None
)
assert char2
@ -65,7 +76,7 @@ def test_character_limits(db: DbContext, lexicon_with_editor):
# Creating a third character should fail
with pytest.raises(ArgumentError):
char3 = charq.create(
char3: Character = charq.create(
db, lexicon.id, user.id, "Test Character 3", signature=None
)
assert char3

View File

@ -1,3 +1,4 @@
from amanuensis.db.models import Lexicon
import datetime
import pytest
@ -9,24 +10,37 @@ 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"}
defaults: dict = {
"db": db,
"name": "Test",
"title": None,
"prompt": "A test Lexicon game",
}
kwargs: dict
# Test name constraints
with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, "name": None})
kwargs = {**defaults, "name": None}
lexiq.create(**kwargs)
with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, "name": ""})
kwargs = {**defaults, "name": ""}
lexiq.create(**kwargs)
with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, "name": " "})
kwargs = {**defaults, "name": " "}
lexiq.create(**kwargs)
with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, "name": ".."})
kwargs = {**defaults, "name": ".."}
lexiq.create(**kwargs)
with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, "name": "\x00"})
kwargs = {**defaults, "name": "\x00"}
lexiq.create(**kwargs)
with pytest.raises(ArgumentError):
lexiq.create(db, **{**kwargs, "name": "space in name"})
kwargs = {**defaults, "name": "space in name"}
lexiq.create(**kwargs)
# Validate that creation populates fields, including timestamps
before = datetime.datetime.utcnow() - datetime.timedelta(seconds=1)
new_lexicon = lexiq.create(db, **kwargs)
new_lexicon: Lexicon = lexiq.create(**defaults)
after = datetime.datetime.utcnow() + datetime.timedelta(seconds=1)
assert new_lexicon
assert new_lexicon.id is not None
@ -36,5 +50,4 @@ def test_create_lexicon(db: DbContext):
# No duplicate lexicon names
with pytest.raises(ArgumentError):
duplicate = lexiq.create(db, **kwargs)
assert duplicate
lexiq.create(**defaults)

View File

@ -10,13 +10,13 @@ import amanuensis.backend.membership as memq
def test_create_membership(db: DbContext, make):
"""Test joining a game."""
# Set up a user and a lexicon
new_user = make.user()
new_user: User = make.user()
assert new_user.id, "Failed to create user"
new_lexicon = make.lexicon()
new_lexicon: Lexicon = make.lexicon()
assert new_lexicon.id, "Failed to create lexicon"
# Add the user to the lexicon as an editor
mem = memq.create(db, new_user.id, new_lexicon.id, True)
mem: Membership = memq.create(db, new_user.id, new_lexicon.id, True)
assert mem, "Failed to create membership"
# Check that the user and lexicon are mutually visible in the ORM relationships
@ -24,7 +24,7 @@ def test_create_membership(db: DbContext, make):
assert any(map(lambda mem: mem.user == new_user, new_lexicon.memberships))
# Check that the editor flag was set properly
editor = db(
editor: User = db(
select(User)
.join(User.memberships)
.join(Membership.lexicon)
@ -37,5 +37,4 @@ def test_create_membership(db: DbContext, make):
# Check that joining twice is not allowed
with pytest.raises(ArgumentError):
mem2 = memq.create(db, new_user.id, new_lexicon.id, False)
assert mem2
memq.create(db, new_user.id, new_lexicon.id, False)

View File

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

View File

@ -1,3 +1,4 @@
from amanuensis.db.models import User
import pytest
from amanuensis.db import DbContext
@ -7,39 +8,45 @@ from amanuensis.errors import ArgumentError
def test_create_user(db: DbContext):
"""Test new user creation."""
kwargs = {
defaults: dict = {
"db": db,
"username": "username",
"password": "password",
"display_name": "User Name",
"email": "user@example.com",
"is_site_admin": False,
}
kwargs: dict
# Test length constraints
with pytest.raises(ArgumentError):
userq.create(db, **{**kwargs, "username": "me"})
kwargs = {**defaults, "username": "me"}
userq.create(**kwargs)
with pytest.raises(ArgumentError):
userq.create(
db, **{**kwargs, "username": "the right honorable user-name, esquire"}
)
kwargs = {**defaults, "username": "the right honorable user-name, esquire"}
userq.create(**kwargs)
# Test allowed characters
with pytest.raises(ArgumentError):
userq.create(db, **{**kwargs, "username": "user name"})
kwargs = {**defaults, "username": "user name"}
userq.create(**kwargs)
# No password
with pytest.raises(ArgumentError):
userq.create(db, **{**kwargs, "password": None})
kwargs = {**defaults, "password": None}
userq.create(**kwargs)
# Valid creation works and populates fields
new_user = userq.create(db, **kwargs)
new_user = userq.create(**defaults)
assert new_user
assert new_user.id is not None
assert new_user.created is not None
# No duplicate usernames
with pytest.raises(ArgumentError):
duplicate = userq.create(db, **kwargs)
userq.create(**defaults)
# Missing display name populates with username
user2_kw = {**kwargs, "username": "user2", "display_name": None}
user2 = userq.create(db, **user2_kw)
user2_kw: dict = {**defaults, "username": "user2", "display_name": None}
user2: User = userq.create(**user2_kw)
assert user2.display_name is not None