Enhance API robustness: Add model validators, view-field filtering, and test suite.

- Added validators to Person_Base, Journal_Base, Journal_Entry_Base, Address_Base, and Contact_Base to handle null values and unsafe lookups.
- Implemented 'fields_to_exclude_from_db' ClassVar in Journal models to prevent view-only fields from causing DB errors.
- Updated Contact object map to align with DB schema.
- Added comprehensive test suite in 'tests/' directory (model validation, filtering logic).
- Updated GEMINI.md with progress.
This commit is contained in:
Scott Idem
2026-01-09 15:36:28 -05:00
parent 29b4d5ae4b
commit 1c0922ace2
11 changed files with 472 additions and 17 deletions

View File

@@ -3,7 +3,7 @@ import datetime, pytz
from typing import Dict, List, Optional, Set, Union
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator
from app.db_sql import redis_lookup_id_random
from app.db_sql import get_id_random, redis_lookup_id_random
from app.lib_general import log, logging
from app.models.common_field_schema import base_fields, default_num_bytes
@@ -123,7 +123,7 @@ class Address_Base(BaseModel):
log.setLevel(logging.WARNING)
log.debug(locals())
if values['for_id_random'] and values['for_type']:
if values.get('for_id_random') and values.get('for_type'):
return redis_lookup_id_random(record_id_random=values['for_id_random'], table_name=values['for_type'])
return None

View File

@@ -1,6 +1,6 @@
import datetime, pytz
from typing import Dict, List, Optional, Set, Union
from typing import Dict, List, Optional, Set, Union, ClassVar
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator
from app.db_sql import redis_lookup_id_random
@@ -127,6 +127,25 @@ class Journal_Entry_Base(BaseModel):
return redis_lookup_id_random(record_id_random=id_random, table_name='journal')
return None
@validator('parent_id', always=True)
def parent_id_lookup(cls, v, values, **kwargs):
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('parent_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='journal_entry')
return None
@validator('for_id', always=True)
def for_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if values.get('for_id_random') and values.get('for_type'):
return redis_lookup_id_random(record_id_random=values['for_id_random'], table_name=values['for_type'])
return None
# Fields that are part of the model (for reading) but should not be saved to the DB table
fields_to_exclude_from_db: ClassVar[list] = ['file_count']
class Config:
underscore_attrs_are_private = True
allow_population_by_field_name = True

View File

@@ -1,6 +1,6 @@
import datetime, pytz
from typing import Dict, List, Optional, Set, Union
from typing import Dict, List, Optional, Set, Union, ClassVar
from pydantic import BaseModel, EmailStr, Field, Json, PrivateAttr, ValidationError, validator
from app.db_sql import redis_lookup_id_random
@@ -144,13 +144,24 @@ class Journal_Base(BaseModel):
return redis_lookup_id_random(record_id_random=id_random, table_name='journal')
return None
@validator('account_id', always=True)
def account_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('account_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='account')
return None
@validator('person_id', always=True)
def person_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if values['person_id_random']:
return redis_lookup_id_random(record_id_random=values['person_id_random'], table_name='person')
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('person_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='person')
return None
@validator('user_id', always=True)
@@ -158,10 +169,27 @@ class Journal_Base(BaseModel):
log.setLevel(logging.WARNING)
log.debug(locals())
if values['user_id_random']:
return redis_lookup_id_random(record_id_random=values['user_id_random'], table_name='user')
if isinstance(v, int) and v > 0: return v
elif id_random := values.get('user_id_random'):
return redis_lookup_id_random(record_id_random=id_random, table_name='user')
return None
@validator('for_id', always=True)
def for_id_lookup(cls, v, values, **kwargs):
log.setLevel(logging.WARNING)
log.debug(locals())
if values.get('for_id_random') and values.get('for_type'):
return redis_lookup_id_random(record_id_random=values['for_id_random'], table_name=values['for_type'])
return None
# Fields that are part of the model (for reading) but should not be saved to the DB table
fields_to_exclude_from_db: ClassVar[list] = [
'person_external_id', 'person_given_name', 'person_family_name',
'person_full_name', 'person_primary_email', 'person_passcode',
'journal_entry_count', 'file_count', 'file_count_all'
]
class Config:
underscore_attrs_are_private = True
allow_population_by_field_name = True