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 Article query interface
""" """
from typing import Optional
from sqlalchemy import select from sqlalchemy import select
from amanuensis.db import * from amanuensis.db import *
from amanuensis.errors import ArgumentError 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. Create a new article in a lexicon.
""" """

View File

@ -2,6 +2,8 @@
Character query interface Character query interface
""" """
from typing import Optional
from sqlalchemy import select, func from sqlalchemy import select, func
from amanuensis.db import * from amanuensis.db import *
@ -9,7 +11,11 @@ from amanuensis.errors import ArgumentError
def create( 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: ) -> Character:
""" """
Create a new character for a user. 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-_]*$") 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. Create a new lexicon.
""" """

View File

@ -8,7 +8,12 @@ from amanuensis.db import DbContext, Membership
from amanuensis.errors import ArgumentError 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. 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 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 Create a new post
""" """

View File

@ -11,7 +11,7 @@ import amanuensis.backend.user as userq
@pytest.fixture @pytest.fixture
def db(): def db() -> DbContext:
"""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()

View File

@ -1,6 +1,7 @@
import pytest import pytest
from amanuensis.db import DbContext from amanuensis.db import DbContext
from amanuensis.db.models import Character, Lexicon, User
import amanuensis.backend.article as artiq import amanuensis.backend.article as artiq
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError
@ -9,18 +10,18 @@ from amanuensis.errors import ArgumentError
def test_create_article(db: DbContext, make): def test_create_article(db: DbContext, make):
"""Test new article creation""" """Test new article creation"""
# Create two users in a shared lexicon # Create two users in a shared lexicon
user1 = make.user() user1: User = make.user()
user2 = make.user() user2: User = make.user()
lexicon1 = make.lexicon() lexicon1: Lexicon = make.lexicon()
make.membership(user_id=user1.id, lexicon_id=lexicon1.id) make.membership(user_id=user1.id, lexicon_id=lexicon1.id)
make.membership(user_id=user2.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_1: Character = make.character(lexicon_id=lexicon1.id, user_id=user1.id)
char1_2 = make.character(lexicon_id=lexicon1.id, user_id=user2.id) char1_2: Character = make.character(lexicon_id=lexicon1.id, user_id=user2.id)
# Create a lexicon that only one user is in # 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) 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 # User cannot create article for another user's character
with pytest.raises(ArgumentError): 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): def test_create_character(db: DbContext, lexicon_with_editor, make):
"""Test creating a character.""" """Test creating a character."""
lexicon: Lexicon
user: User
lexicon, user = lexicon_with_editor lexicon, user = lexicon_with_editor
kwargs = { defaults: dict = {
"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",
} }
kwargs: dict
# Bad argument types # Bad argument types
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "name": b"bytestring"}) kwargs = {**defaults, "name": b"bytestring"}
charq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "name": None}) kwargs = {**defaults, "name": None}
charq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "signature": b"bytestring"}) kwargs = {**defaults, "signature": b"bytestring"}
charq.create(**kwargs)
# Bad character name # Bad character name
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
charq.create(**{**kwargs, "name": " "}) kwargs = {**defaults, "name": " "}
charq.create(**kwargs)
# Signature is auto-populated # Signature is auto-populated
char = charq.create(**{**kwargs, "signature": None}) kwargs = {**defaults, "signature": None}
char = charq.create(**kwargs)
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: User = make.user()
with pytest.raises(ArgumentError): 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): 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 # 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: Character = 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( char2: Character = charq.create(
db, lexicon.id, user.id, "Test Character 2", signature=None db, lexicon.id, user.id, "Test Character 2", signature=None
) )
assert char2 assert char2
@ -65,7 +76,7 @@ def test_character_limits(db: DbContext, lexicon_with_editor):
# Creating a third character should fail # Creating a third character should fail
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
char3 = charq.create( char3: Character = charq.create(
db, lexicon.id, user.id, "Test Character 3", signature=None db, lexicon.id, user.id, "Test Character 3", signature=None
) )
assert char3 assert char3

View File

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

View File

@ -10,13 +10,13 @@ import amanuensis.backend.membership as memq
def test_create_membership(db: DbContext, make): 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: 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: 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: Membership = 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
@ -24,7 +24,7 @@ def test_create_membership(db: DbContext, make):
assert any(map(lambda mem: mem.user == new_user, new_lexicon.memberships)) assert any(map(lambda mem: mem.user == new_user, new_lexicon.memberships))
# Check that the editor flag was set properly # Check that the editor flag was set properly
editor = db( editor: User = db(
select(User) select(User)
.join(User.memberships) .join(User.memberships)
.join(Membership.lexicon) .join(Membership.lexicon)
@ -37,5 +37,4 @@ def test_create_membership(db: DbContext, make):
# Check that joining twice is not allowed # Check that joining twice is not allowed
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
mem2 = memq.create(db, new_user.id, new_lexicon.id, False) memq.create(db, new_user.id, new_lexicon.id, False)
assert mem2

View File

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

View File

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