From 7645c85c9d975858b2976ab4775f88b7857d5268 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Fri, 13 Aug 2021 16:38:47 -0700 Subject: [PATCH] Make backend argument type errors more specific --- amanuensis/backend/article.py | 8 ++++---- amanuensis/backend/character.py | 10 +++++----- amanuensis/backend/index.py | 14 +++++++------- amanuensis/backend/lexicon.py | 10 +++++----- amanuensis/backend/membership.py | 8 ++++---- amanuensis/backend/post.py | 10 +++++----- amanuensis/backend/user.py | 10 +++++----- amanuensis/errors.py | 17 +++++++++++++++-- tests/backend/test_character.py | 8 ++++---- tests/backend/test_lexicon.py | 4 ++-- tests/backend/test_post.py | 13 +++++-------- tests/backend/test_user.py | 4 ++-- 12 files changed, 63 insertions(+), 53 deletions(-) diff --git a/amanuensis/backend/article.py b/amanuensis/backend/article.py index 973c123..187e9d0 100644 --- a/amanuensis/backend/article.py +++ b/amanuensis/backend/article.py @@ -7,7 +7,7 @@ from typing import Optional from sqlalchemy import select from amanuensis.db import * -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError def create( @@ -21,11 +21,11 @@ def create( """ # Verify argument types are correct if not isinstance(lexicon_id, int): - raise ArgumentError("lexicon_id") + raise BackendArgumentTypeError(int, lexicon_id=lexicon_id) 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): - raise ArgumentError("character_id") + raise BackendArgumentTypeError(int, character_id=character_id) # Check that the user is a member of this lexicon mem: Membership = db( diff --git a/amanuensis/backend/character.py b/amanuensis/backend/character.py index 1b87b3b..01f5804 100644 --- a/amanuensis/backend/character.py +++ b/amanuensis/backend/character.py @@ -7,7 +7,7 @@ from typing import Optional from sqlalchemy import select, func from amanuensis.db import * -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError def create( @@ -22,13 +22,13 @@ def create( """ # Verify argument types are correct if not isinstance(lexicon_id, int): - raise ArgumentError("lexicon_id") + raise BackendArgumentTypeError(int, lexicon_id=lexicon_id) if not isinstance(user_id, int): - raise ArgumentError("user_id") + raise BackendArgumentTypeError(int, user_id=user_id) if not isinstance(name, str): - raise ArgumentError("name") + raise BackendArgumentTypeError(str, name=name) if signature is not None and not isinstance(signature, str): - raise ArgumentError("signature") + raise BackendArgumentTypeError(str, signature=signature) # Verify character name is valid if not name.strip(): diff --git a/amanuensis/backend/index.py b/amanuensis/backend/index.py index 7f108ec..bfd259c 100644 --- a/amanuensis/backend/index.py +++ b/amanuensis/backend/index.py @@ -6,7 +6,7 @@ import re from typing import Optional from amanuensis.db import DbContext, ArticleIndex, IndexType -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError def create( @@ -23,17 +23,17 @@ def create( """ # Verify argument types are correct if not isinstance(lexicon_id, int): - raise ArgumentError("lexicon_id") + raise BackendArgumentTypeError(int, lexicon_id=lexicon_id) if not isinstance(index_type, IndexType): - raise ArgumentError("index_type") + raise BackendArgumentTypeError(IndexType, index_type=index_type) if not isinstance(pattern, str): - raise ArgumentError("pattern") + raise BackendArgumentTypeError(str, pattern=pattern) if not isinstance(logical_order, int): - raise ArgumentError("logical_order") + raise BackendArgumentTypeError(int, logical_order=logical_order) 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): - raise ArgumentError("capacity") + raise BackendArgumentTypeError(int, capacity=capacity) # Verify the pattern is valid for the index type: if index_type == IndexType.CHAR: diff --git a/amanuensis/backend/lexicon.py b/amanuensis/backend/lexicon.py index 073a4cf..ff83b34 100644 --- a/amanuensis/backend/lexicon.py +++ b/amanuensis/backend/lexicon.py @@ -8,7 +8,7 @@ from typing import Sequence, Optional from sqlalchemy import select, func 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-_]*$") @@ -25,7 +25,7 @@ def create( """ # Verify name if not isinstance(name, str): - raise ArgumentError("Lexicon name must be a string") + raise BackendArgumentTypeError(str, name=name) if not name.strip(): raise ArgumentError("Lexicon name must not be blank") if not RE_ALPHANUM_DASH_UNDER.match(name): @@ -34,12 +34,12 @@ def create( ) # Verify title - if title is not None and not isinstance(name, str): - raise ArgumentError("Lexicon name must be a string") + if title is not None and not isinstance(title, str): + raise BackendArgumentTypeError(str, title=title) # Verify prompt 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 if db(select(func.count(Lexicon.id)).where(Lexicon.name == name)).scalar() > 0: diff --git a/amanuensis/backend/membership.py b/amanuensis/backend/membership.py index 5e9af13..baddcdd 100644 --- a/amanuensis/backend/membership.py +++ b/amanuensis/backend/membership.py @@ -6,7 +6,7 @@ from sqlalchemy import select, func from amanuensis.db import DbContext, Membership from amanuensis.db.models import Lexicon -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError def create( @@ -20,11 +20,11 @@ def create( """ # Verify argument types are correct if not isinstance(user_id, int): - raise ArgumentError("user_id") + raise BackendArgumentTypeError(int, user_id=user_id) if not isinstance(lexicon_id, int): - raise ArgumentError("lexicon_id") + raise BackendArgumentTypeError(int, lexicon_id=lexicon_id) if not isinstance(is_editor, bool): - raise ArgumentError("is_editor") + raise BackendArgumentTypeError(bool, is_editor=is_editor) # Verify user has not already joined lexicon if ( diff --git a/amanuensis/backend/post.py b/amanuensis/backend/post.py index 0a6373f..8a4d59d 100644 --- a/amanuensis/backend/post.py +++ b/amanuensis/backend/post.py @@ -8,7 +8,7 @@ from sqlalchemy import select from amanuensis.db import DbContext, Post from amanuensis.db.models import Lexicon -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError def create( @@ -23,15 +23,15 @@ def create( # Verify lexicon id if not isinstance(lexicon_id, int): - raise ArgumentError("Lexicon id must be an integer.") + raise BackendArgumentTypeError(int, lexicon_id=lexicon_id) # Verify user_id - if not (isinstance(user_id, int) or user_id is None): - raise ArgumentError("User id must be an integer.") + if user_id is not None and not isinstance(user_id, int): + raise BackendArgumentTypeError(int, user_id=user_id) # Verify body if not isinstance(body, str): - raise ArgumentError("Post body must be a string.") + raise BackendArgumentTypeError(str, body=body) if not body.strip(): raise ArgumentError("Post body cannot be empty.") diff --git a/amanuensis/backend/user.py b/amanuensis/backend/user.py index 5542c8c..e07e315 100644 --- a/amanuensis/backend/user.py +++ b/amanuensis/backend/user.py @@ -10,7 +10,7 @@ from sqlalchemy import select, func, update from werkzeug.security import generate_password_hash, check_password_hash 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-_]*$") @@ -30,7 +30,7 @@ def create( """ # Verify username 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: raise ArgumentError("Username must be between 3 and 32 characters") if RE_NO_LETTERS.match(username): @@ -42,18 +42,18 @@ def create( # Verify password if not isinstance(password, str): - raise ArgumentError("Password must be a string") + raise BackendArgumentTypeError(str, password=password) # Verify display name 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 not display_name or not display_name.strip(): display_name = username # Verify email 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 if db(select(func.count(User.id)).where(User.username == username)).scalar() > 0: diff --git a/amanuensis/errors.py b/amanuensis/errors.py index b6a9145..7c35ee1 100644 --- a/amanuensis/errors.py +++ b/amanuensis/errors.py @@ -4,8 +4,21 @@ Submodule of custom exception types class AmanuensisError(Exception): - """Base class for exceptions in amanuensis""" + """Base class for exceptions in Amanuensis""" 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) diff --git a/tests/backend/test_character.py b/tests/backend/test_character.py index d6808f4..bf0bb95 100644 --- a/tests/backend/test_character.py +++ b/tests/backend/test_character.py @@ -2,7 +2,7 @@ import pytest from amanuensis.backend import charq 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): @@ -20,13 +20,13 @@ def test_create_character(db: DbContext, lexicon_with_editor, make): kwargs: dict # Bad argument types - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "name": b"bytestring"} charq.create(**kwargs) - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "name": None} charq.create(**kwargs) - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "signature": b"bytestring"} charq.create(**kwargs) diff --git a/tests/backend/test_lexicon.py b/tests/backend/test_lexicon.py index 8cc4f28..dbbb904 100644 --- a/tests/backend/test_lexicon.py +++ b/tests/backend/test_lexicon.py @@ -5,7 +5,7 @@ import pytest from amanuensis.backend import lexiq from amanuensis.db import DbContext, Lexicon, User -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError from tests.conftest import ObjectFactory @@ -20,7 +20,7 @@ def test_create_lexicon(db: DbContext): kwargs: dict # Test name constraints - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "name": None} lexiq.create(**kwargs) with pytest.raises(ArgumentError): diff --git a/tests/backend/test_post.py b/tests/backend/test_post.py index d66426a..289e989 100644 --- a/tests/backend/test_post.py +++ b/tests/backend/test_post.py @@ -3,7 +3,7 @@ import pytest from amanuensis.backend import postq 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): @@ -20,19 +20,16 @@ def test_create_post(db: DbContext, lexicon_with_editor): kwargs: dict # ids are integers - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "user_id": "zero"} postq.create(**kwargs) - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "lexicon_id": "zero"} postq.create(**kwargs) # empty arguments don't work - with pytest.raises(ArgumentError): - kwargs = {**defaults, "lexicon_id": ""} - postq.create(**kwargs) - with pytest.raises(ArgumentError): - kwargs = {**defaults, "user_id": ""} + with pytest.raises(BackendArgumentTypeError): + kwargs = {**defaults, "lexicon_id": None} postq.create(**kwargs) with pytest.raises(ArgumentError): kwargs = {**defaults, "body": ""} diff --git a/tests/backend/test_user.py b/tests/backend/test_user.py index 43af33f..6cf8f88 100644 --- a/tests/backend/test_user.py +++ b/tests/backend/test_user.py @@ -4,7 +4,7 @@ import pytest from amanuensis.backend import userq from amanuensis.db import DbContext, User -from amanuensis.errors import ArgumentError +from amanuensis.errors import ArgumentError, BackendArgumentTypeError def test_create_user(db: DbContext): @@ -33,7 +33,7 @@ def test_create_user(db: DbContext): userq.create(**kwargs) # No password - with pytest.raises(ArgumentError): + with pytest.raises(BackendArgumentTypeError): kwargs = {**defaults, "password": None} userq.create(**kwargs)