Make backend argument type errors more specific

This commit is contained in:
Tim Van Baak 2021-08-13 16:38:47 -07:00
parent d6f558a92b
commit 7645c85c9d
12 changed files with 63 additions and 53 deletions

View File

@ -7,7 +7,7 @@ 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, BackendArgumentTypeError
def create( def create(
@ -21,11 +21,11 @@ def create(
""" """
# 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 BackendArgumentTypeError(int, lexicon_id=lexicon_id)
if not isinstance(user_id, int): if not isinstance(user_id, int):
raise ArgumentError("user_id") raise BackendArgumentTypeError(int, user_id=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 BackendArgumentTypeError(int, character_id=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(

View File

@ -7,7 +7,7 @@ from typing import Optional
from sqlalchemy import select, func from sqlalchemy import select, func
from amanuensis.db import * from amanuensis.db import *
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def create( def create(
@ -22,13 +22,13 @@ def create(
""" """
# 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 BackendArgumentTypeError(int, lexicon_id=lexicon_id)
if not isinstance(user_id, int): if not isinstance(user_id, int):
raise ArgumentError("user_id") raise BackendArgumentTypeError(int, user_id=user_id)
if not isinstance(name, str): if not isinstance(name, str):
raise ArgumentError("name") raise BackendArgumentTypeError(str, name=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 BackendArgumentTypeError(str, signature=signature)
# Verify character name is valid # Verify character name is valid
if not name.strip(): if not name.strip():

View File

@ -6,7 +6,7 @@ import re
from typing import Optional from typing import Optional
from amanuensis.db import DbContext, ArticleIndex, IndexType from amanuensis.db import DbContext, ArticleIndex, IndexType
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def create( def create(
@ -23,17 +23,17 @@ def create(
""" """
# 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 BackendArgumentTypeError(int, lexicon_id=lexicon_id)
if not isinstance(index_type, IndexType): if not isinstance(index_type, IndexType):
raise ArgumentError("index_type") raise BackendArgumentTypeError(IndexType, index_type=index_type)
if not isinstance(pattern, str): if not isinstance(pattern, str):
raise ArgumentError("pattern") raise BackendArgumentTypeError(str, pattern=pattern)
if not isinstance(logical_order, int): if not isinstance(logical_order, int):
raise ArgumentError("logical_order") raise BackendArgumentTypeError(int, logical_order=logical_order)
if not isinstance(display_order, int): if not isinstance(display_order, int):
raise ArgumentError("display_order") raise BackendArgumentTypeError(int, display_order=display_order)
if capacity is not None and not isinstance(capacity, int): if capacity is not None and not isinstance(capacity, int):
raise ArgumentError("capacity") raise BackendArgumentTypeError(int, capacity=capacity)
# Verify the pattern is valid for the index type: # Verify the pattern is valid for the index type:
if index_type == IndexType.CHAR: if index_type == IndexType.CHAR:

View File

@ -8,7 +8,7 @@ from typing import Sequence, Optional
from sqlalchemy import select, func from sqlalchemy import select, func
from amanuensis.db import DbContext, Lexicon, Membership from amanuensis.db import DbContext, Lexicon, Membership
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
RE_ALPHANUM_DASH_UNDER = re.compile(r"^[A-Za-z0-9-_]*$") RE_ALPHANUM_DASH_UNDER = re.compile(r"^[A-Za-z0-9-_]*$")
@ -25,7 +25,7 @@ def create(
""" """
# Verify name # Verify name
if not isinstance(name, str): if not isinstance(name, str):
raise ArgumentError("Lexicon name must be a string") raise BackendArgumentTypeError(str, name=name)
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):
@ -34,12 +34,12 @@ def create(
) )
# Verify title # Verify title
if title is not None and not isinstance(name, str): if title is not None and not isinstance(title, str):
raise ArgumentError("Lexicon name must be a string") raise BackendArgumentTypeError(str, title=title)
# Verify prompt # Verify prompt
if not isinstance(prompt, str): if not isinstance(prompt, str):
raise ArgumentError("Lexicon prompt must be a string") raise BackendArgumentTypeError(str, prompt=prompt)
# 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(select(func.count(Lexicon.id)).where(Lexicon.name == name)).scalar() > 0: if db(select(func.count(Lexicon.id)).where(Lexicon.name == name)).scalar() > 0:

View File

@ -6,7 +6,7 @@ from sqlalchemy import select, func
from amanuensis.db import DbContext, Membership from amanuensis.db import DbContext, Membership
from amanuensis.db.models import Lexicon from amanuensis.db.models import Lexicon
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def create( def create(
@ -20,11 +20,11 @@ def create(
""" """
# 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 BackendArgumentTypeError(int, user_id=user_id)
if not isinstance(lexicon_id, int): if not isinstance(lexicon_id, int):
raise ArgumentError("lexicon_id") raise BackendArgumentTypeError(int, lexicon_id=lexicon_id)
if not isinstance(is_editor, bool): if not isinstance(is_editor, bool):
raise ArgumentError("is_editor") raise BackendArgumentTypeError(bool, is_editor=is_editor)
# Verify user has not already joined lexicon # Verify user has not already joined lexicon
if ( if (

View File

@ -8,7 +8,7 @@ from sqlalchemy import select
from amanuensis.db import DbContext, Post from amanuensis.db import DbContext, Post
from amanuensis.db.models import Lexicon from amanuensis.db.models import Lexicon
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def create( def create(
@ -23,15 +23,15 @@ def create(
# 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 BackendArgumentTypeError(int, lexicon_id=lexicon_id)
# Verify user_id # Verify user_id
if not (isinstance(user_id, int) or user_id is None): if user_id is not None and not isinstance(user_id, int):
raise ArgumentError("User id must be an integer.") raise BackendArgumentTypeError(int, user_id=user_id)
# Verify body # Verify body
if not isinstance(body, str): if not isinstance(body, str):
raise ArgumentError("Post body must be a string.") raise BackendArgumentTypeError(str, body=body)
if not body.strip(): if not body.strip():
raise ArgumentError("Post body cannot be empty.") raise ArgumentError("Post body cannot be empty.")

View File

@ -10,7 +10,7 @@ from sqlalchemy import select, func, update
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from amanuensis.db import DbContext, User from amanuensis.db import DbContext, User
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
RE_NO_LETTERS = re.compile(r"^[0-9-_]*$") RE_NO_LETTERS = re.compile(r"^[0-9-_]*$")
@ -30,7 +30,7 @@ def create(
""" """
# Verify username # Verify username
if not isinstance(username, str): if not isinstance(username, str):
raise ArgumentError("Username must be a string") raise BackendArgumentTypeError(str, username=username)
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):
@ -42,18 +42,18 @@ def create(
# Verify password # Verify password
if not isinstance(password, str): if not isinstance(password, str):
raise ArgumentError("Password must be a string") raise BackendArgumentTypeError(str, password=password)
# 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 BackendArgumentTypeError(str, display_name=display_name)
# 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 BackendArgumentTypeError(str, email=email)
# Query the db to make sure the username isn't taken # 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:

View File

@ -4,8 +4,21 @@ Submodule of custom exception types
class AmanuensisError(Exception): class AmanuensisError(Exception):
"""Base class for exceptions in amanuensis""" """Base class for exceptions in Amanuensis"""
class ArgumentError(AmanuensisError): class ArgumentError(AmanuensisError):
"""An internal call was made with invalid arguments""" """An internal call was made with invalid arguments."""
class BackendArgumentTypeError(ArgumentError):
"""
A call to a backend function was made with a value of an invalid type for the parameter.
Specify the invalid parameter and value as a kwarg.
"""
def __init__(self, obj_type, **kwarg):
if not kwarg:
raise ValueError("Missing kwarg")
param, value = next(iter(kwarg.items()))
msg = f"Expected {param} of type {obj_type}, got {type(value)}"
super().__init__(msg)

View File

@ -2,7 +2,7 @@ import pytest
from amanuensis.backend import charq from amanuensis.backend import charq
from amanuensis.db import * from amanuensis.db import *
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def test_create_character(db: DbContext, lexicon_with_editor, make): def test_create_character(db: DbContext, lexicon_with_editor, make):
@ -20,13 +20,13 @@ def test_create_character(db: DbContext, lexicon_with_editor, make):
kwargs: dict kwargs: dict
# Bad argument types # Bad argument types
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "name": b"bytestring"} kwargs = {**defaults, "name": b"bytestring"}
charq.create(**kwargs) charq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "name": None} kwargs = {**defaults, "name": None}
charq.create(**kwargs) charq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "signature": b"bytestring"} kwargs = {**defaults, "signature": b"bytestring"}
charq.create(**kwargs) charq.create(**kwargs)

View File

@ -5,7 +5,7 @@ import pytest
from amanuensis.backend import lexiq from amanuensis.backend import lexiq
from amanuensis.db import DbContext, Lexicon, User from amanuensis.db import DbContext, Lexicon, User
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
from tests.conftest import ObjectFactory from tests.conftest import ObjectFactory
@ -20,7 +20,7 @@ def test_create_lexicon(db: DbContext):
kwargs: dict kwargs: dict
# Test name constraints # Test name constraints
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "name": None} kwargs = {**defaults, "name": None}
lexiq.create(**kwargs) lexiq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):

View File

@ -3,7 +3,7 @@ import pytest
from amanuensis.backend import postq from amanuensis.backend import postq
from amanuensis.db import DbContext from amanuensis.db import DbContext
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def test_create_post(db: DbContext, lexicon_with_editor): def test_create_post(db: DbContext, lexicon_with_editor):
@ -20,19 +20,16 @@ def test_create_post(db: DbContext, lexicon_with_editor):
kwargs: dict kwargs: dict
# ids are integers # ids are integers
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "user_id": "zero"} kwargs = {**defaults, "user_id": "zero"}
postq.create(**kwargs) postq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "lexicon_id": "zero"} kwargs = {**defaults, "lexicon_id": "zero"}
postq.create(**kwargs) postq.create(**kwargs)
# empty arguments don't work # empty arguments don't work
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "lexicon_id": ""} kwargs = {**defaults, "lexicon_id": None}
postq.create(**kwargs)
with pytest.raises(ArgumentError):
kwargs = {**defaults, "user_id": ""}
postq.create(**kwargs) postq.create(**kwargs)
with pytest.raises(ArgumentError): with pytest.raises(ArgumentError):
kwargs = {**defaults, "body": ""} kwargs = {**defaults, "body": ""}

View File

@ -4,7 +4,7 @@ import pytest
from amanuensis.backend import userq from amanuensis.backend import userq
from amanuensis.db import DbContext, User from amanuensis.db import DbContext, User
from amanuensis.errors import ArgumentError from amanuensis.errors import ArgumentError, BackendArgumentTypeError
def test_create_user(db: DbContext): def test_create_user(db: DbContext):
@ -33,7 +33,7 @@ def test_create_user(db: DbContext):
userq.create(**kwargs) userq.create(**kwargs)
# No password # No password
with pytest.raises(ArgumentError): with pytest.raises(BackendArgumentTypeError):
kwargs = {**defaults, "password": None} kwargs = {**defaults, "password": None}
userq.create(**kwargs) userq.create(**kwargs)