Skip to content

Commit

Permalink
Fix error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
horia141 committed Mar 2, 2024
1 parent 8c85dc0 commit f867292
Show file tree
Hide file tree
Showing 20 changed files with 519 additions and 288 deletions.
2 changes: 1 addition & 1 deletion src/cli/jupiter/cli/command/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -1413,7 +1413,7 @@ async def run(self, argv: list[str]) -> None:
):
try:
await command.run(self._console, args)
except Exception as e:
except Exception as e: # noqa: BLE001
if type(e) not in self._exception_handlers:
raise

Expand Down
18 changes: 17 additions & 1 deletion src/cli/jupiter/cli/command/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
from jupiter.core.domain.projects.errors import ProjectInSignificantUseError
from jupiter.core.domain.user.user import UserAlreadyExistsError, UserNotFoundError
from jupiter.core.domain.workspaces.workspace import WorkspaceNotFoundError
from jupiter.core.framework.errors import InputValidationError
from jupiter.core.framework.errors import (
InputValidationError,
MultiInputValidationError,
)
from jupiter.core.framework.repository import EntityNotFoundError
from jupiter.core.framework.storage import ConnectionPrepareError
from jupiter.core.use_cases.login import InvalidLoginCredentialsError
Expand Down Expand Up @@ -42,6 +45,19 @@ def handle(
sys.exit(1)


class MultiInputValidationHandler(CliExceptionHandler[MultiInputValidationError]):
"""Handle input validation errors."""

def handle(
self, app: CliApp, console: Console, exception: MultiInputValidationError
) -> None:
"""Handle input validation errors."""
print("Looks like there's something wrong with the command's arguments:")
for k, v in exception.errors.items():
print(f" {k}: {v}")
sys.exit(1)


class FeatureUnavailableHandler(CliExceptionHandler[FeatureUnavailableError]):
"""Handle feature unavailable errors."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async def do_it(
inbox_tasks_to_archive = await uow.get_for(InboxTask).find_all_generic(
parent_ref_id=inbox_task_collection.ref_id,
allow_archived=False,
big_plan_ref_ids=[big_plan.ref_id],
big_plan_ref_id=[big_plan.ref_id],
)

archived_inbox_tasks = []
Expand Down
55 changes: 44 additions & 11 deletions src/core/jupiter/core/domain/core/adate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import pendulum.parsing
import pendulum.tz
from jupiter.core.framework.base.timestamp import Timestamp
from jupiter.core.framework.errors import InputValidationError
from jupiter.core.framework.realm import (
CliRealm,
DatabaseRealm,
RealmDecoder,
RealmDecodingError,
RealmEncoder,
RealmThing,
WebRealm,
)
from jupiter.core.framework.value import AtomicValue, hashable_value
from pendulum.date import Date
Expand All @@ -31,21 +34,27 @@ class ADate(AtomicValue[Date]):
@staticmethod
def from_date(date: Date | date) -> "ADate":
"""Construct an ADate from a date."""
return ADate(Date(date.year, date.month, date.day))
try:
return ADate(Date(date.year, date.month, date.day))
except ValueError as err:
raise InputValidationError(f"Invalid date because: {err}") from None

@staticmethod
def from_str(date_raw: str) -> "ADate":
"""Parse a date from string."""
return ADate(
cast(
Date,
pendulum.parser.parse(
date_raw.replace(" 00:00:00", ""),
tz=UTC,
exact=True,
try:
return ADate(
cast(
Date,
pendulum.parser.parse(
date_raw.replace(" 00:00:00", ""),
tz=UTC,
exact=True,
),
),
),
)
)
except ValueError as err:
raise InputValidationError(f"Invalid date because: {err}") from None

def to_timestamp_at_end_of_day(self) -> Timestamp:
"""Transform to a timestamp at the end of the day."""
Expand Down Expand Up @@ -112,7 +121,31 @@ class ADateDatabaseDecoder(RealmDecoder[ADate, DatabaseRealm]):
def decode(self, value: RealmThing) -> ADate:
if not isinstance(value, (date, Date)):
raise RealmDecodingError(
f"Expected value for {self.__class__} to be datetime or DateTime"
f"Expected value for {self.__class__} to be date or Date but was {value.__class__}"
)

return ADate.from_date(value)


class ADateCliDecoder(RealmDecoder[ADate, CliRealm]):
"""A decoder for adates in databases."""

def decode(self, value: RealmThing) -> ADate:
if not isinstance(value, str):
raise RealmDecodingError(
f"Expected value for str to be date or Date but was {value.__class__}"
)

return ADate.from_str(value)


class ADateWebDecoder(RealmDecoder[ADate, WebRealm]):
"""A decoder for adates in databases."""

def decode(self, value: RealmThing) -> ADate:
if not isinstance(value, str):
raise RealmDecodingError(
f"Expected value for str to be date or Date but was {value.__class__}"
)

return ADate.from_str(value)
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,5 @@ async def archive_for_source(
if note.archived:
return

if not note.can_be_removed_independently:
raise Exception(f"Note {note.ref_id} cannot be removed dependently")

note = note.mark_archived(ctx)
await uow.get_for(Note).save(note)
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,9 @@ class SearchRepository(Repository, abc.ABC):
"""A search index repository for free form searching across all entities."""

@abc.abstractmethod
async def create(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
async def upsert(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
"""Add an entity and make it available for searching."""

@abc.abstractmethod
async def update(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
"""Update an entity for searching."""

@abc.abstractmethod
async def remove(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
"""Remove an entity from the search index."""
Expand Down
16 changes: 16 additions & 0 deletions src/core/jupiter/core/framework/errors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
"""Common errors."""
from typing import Mapping


class InputValidationError(ValueError):
"""An exception raised when validating some model type."""


class MultiInputValidationError(ValueError):
"""An exception raised when validating multiple model types."""

_errors: dict[str, InputValidationError]

def __init__(self, errors: dict[str, InputValidationError]):
"""Constructor."""
self._errors = errors

@property
def errors(self) -> Mapping[str, InputValidationError]:
"""The errors."""
return self._errors
15 changes: 9 additions & 6 deletions src/core/jupiter/core/framework/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ def find_all_modules(
all_modules = []

def explore_module_tree(the_module: ModuleType) -> None:
for _, name, is_pkg in pkgutil.iter_modules(the_module.__path__):
full_name = the_module.__name__ + "." + name
all_modules.append(importlib.import_module(full_name))
if is_pkg:
submodule = getattr(the_module, name)
explore_module_tree(submodule)
if not hasattr(the_module, "__path__"):
all_modules.append(the_module)
else:
for _, name, is_pkg in pkgutil.iter_modules(the_module.__path__):
full_name = the_module.__name__ + "." + name
all_modules.append(importlib.import_module(full_name))
if is_pkg:
submodule = getattr(the_module, name)
explore_module_tree(submodule)

for mr in module_roots:
explore_module_tree(mr)
Expand Down
63 changes: 33 additions & 30 deletions src/core/jupiter/core/repository/sqlite/domain/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,39 +69,42 @@ def __init__(
keep_existing=True,
)

async def create(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
async def upsert(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
"""Create an entity in the index."""
await self._connection.execute(
insert(self._search_index_table).values(
workspace_ref_id=workspace_ref_id.as_int(),
entity_tag=str(NamedEntityTag.from_entity(entity).value),
parent_ref_id=self._realm_codec_registry.get_encoder(
EntityId, DatabaseRealm
).encode(entity.parent_ref_id),
ref_id=self._realm_codec_registry.get_encoder(
EntityId, DatabaseRealm
).encode(entity.ref_id),
name=self._realm_codec_registry.get_encoder(
EntityName, DatabaseRealm
).encode(entity.name),
archived=self._realm_codec_registry.get_encoder(
bool, DatabaseRealm
).encode(entity.archived),
created_time=self._realm_codec_registry.get_encoder(
Timestamp, DatabaseRealm
).encode(entity.created_time),
last_modified_time=self._realm_codec_registry.get_encoder(
Timestamp, DatabaseRealm
).encode(entity.last_modified_time),
archived_time=self._realm_codec_registry.get_encoder(
Timestamp, DatabaseRealm
).encode(entity.archived_time)
if entity.archived_time
else None,
try:
await self._update(workspace_ref_id, entity)
except EntityNotFoundError:
await self._connection.execute(
insert(self._search_index_table).values(
workspace_ref_id=workspace_ref_id.as_int(),
entity_tag=str(NamedEntityTag.from_entity(entity).value),
parent_ref_id=self._realm_codec_registry.get_encoder(
EntityId, DatabaseRealm
).encode(entity.parent_ref_id),
ref_id=self._realm_codec_registry.get_encoder(
EntityId, DatabaseRealm
).encode(entity.ref_id),
name=self._realm_codec_registry.get_encoder(
EntityName, DatabaseRealm
).encode(entity.name),
archived=self._realm_codec_registry.get_encoder(
bool, DatabaseRealm
).encode(entity.archived),
created_time=self._realm_codec_registry.get_encoder(
Timestamp, DatabaseRealm
).encode(entity.created_time),
last_modified_time=self._realm_codec_registry.get_encoder(
Timestamp, DatabaseRealm
).encode(entity.last_modified_time),
archived_time=self._realm_codec_registry.get_encoder(
Timestamp, DatabaseRealm
).encode(entity.archived_time)
if entity.archived_time
else None,
)
)
)

async def update(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
async def _update(self, workspace_ref_id: EntityId, entity: CrownEntity) -> None:
"""Update an entity in the index."""
query = (
update(self._search_index_table)
Expand Down
2 changes: 1 addition & 1 deletion src/core/jupiter/core/use_cases/docs/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def _perform_transactional_read(
parent_ref_id=doc_collection.ref_id,
allow_archived=args.allow_archived,
ref_id=args.filter_ref_ids or NoFilter(),
parent_doc_ref_id=[None],
parent_doc_ref_id=NoFilter(),
)

notes_by_doc_ref_id: defaultdict[EntityId, Note] = defaultdict(None)
Expand Down
4 changes: 2 additions & 2 deletions src/core/jupiter/core/use_cases/gc/do_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ async def _execute(

async with self._search_storage_engine.get_unit_of_work() as search_uow:
for created_entity in progress_reporter.created_entities:
await search_uow.search_repository.create(
await search_uow.search_repository.upsert(
workspace.ref_id, created_entity
)

for updated_entity in progress_reporter.updated_entities:
await search_uow.search_repository.update(
await search_uow.search_repository.upsert(
workspace.ref_id, updated_entity
)

Expand Down
4 changes: 2 additions & 2 deletions src/core/jupiter/core/use_cases/gen/do_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ async def _execute(

async with self._search_storage_engine.get_unit_of_work() as search_uow:
for created_entity in progress_reporter.created_entities:
await search_uow.search_repository.create(
await search_uow.search_repository.upsert(
workspace.ref_id, created_entity
)

for updated_entity in progress_reporter.updated_entities:
await search_uow.search_repository.update(
await search_uow.search_repository.upsert(
workspace.ref_id, updated_entity
)

Expand Down
Loading

0 comments on commit f867292

Please sign in to comment.