Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
27eed84
add app logic for meaningful error when mismatched app type is used
kossman Jan 25, 2025
c216d32
fix patch call for read_manifest func
kossman Jan 25, 2025
fa5e7a5
fix lint
kossman Jan 25, 2025
4404c62
fix type checkers
kossman Jan 25, 2025
21de5cb
fix tests for test_apps_event_types.py
kossman Jan 25, 2025
7ee1ddb
revert some changes; optimize usage for BASE_EVENT_CLS_TO_APP_TYPE_MA…
kossman Jan 26, 2025
b7bb304
add more precise tests for new functionality
kossman Jan 27, 2025
84a68a4
add matrix testing on different py versions; fix error with context m…
kossman Jan 27, 2025
473a152
fix workflow
kossman Jan 27, 2025
837556d
fix workflow
kossman Jan 27, 2025
c099de7
trying to fix pytest errors on newer version 6.2.5 for tests on py 3.…
kossman Jan 27, 2025
c13c6a9
fix linting
kossman Jan 27, 2025
5f6b248
small test improvement
kossman Jan 27, 2025
463950c
ignore errors at matrix job running
kossman Jan 27, 2025
49837f7
some tests using continue-on-error=true
kossman Jan 27, 2025
12f174e
revert temp changes
kossman Jan 27, 2025
c2c25da
bump new minor version for corva python-sdk: update CHANGELOG.md & an…
kossman Jan 27, 2025
c6c541f
feat: update SDK logic to be more transparent and optimal
kossman Jan 28, 2025
306b081
feat: fix linting
kossman Jan 28, 2025
2209c20
fix: linter
kossman Jan 28, 2025
a5c970a
add some fixes; code improvements
kossman Jan 30, 2025
f442cd0
fix format
kossman Jan 30, 2025
21fce8c
fix typo for manifest.json scheduled app type
kossman Jan 31, 2025
b221f8b
fix: update code according to last PR comments requested
kossman Feb 7, 2025
2dea522
fix: linting
kossman Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]


## [1.12.1] - 2025-01-27
### Fixed
- Add app logic for meaningful error when mismatched app type is used either declared at `manifest.json` or according
to used event payload passed to app


## [1.12.0] - 2024-10-06
### Fixed
- Unset the `CORVA_LOGGER.propagate = False`, so the OTel handler will be able to collect and send those logs as well
Expand Down
2 changes: 1 addition & 1 deletion docs/antora-playbook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ content:
start_path: docs
branches: []
# branches: HEAD # Use this for local development
tags: [v1.12.0]
tags: [v1.12.1]
asciidoc:
attributes:
page-toclevels: 5
Expand Down
48 changes: 32 additions & 16 deletions src/corva/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,16 @@
from corva.service import service
from corva.service.api_sdk import CachingApiSdk, CorvaApiSdk
from corva.service.cache_sdk import FakeInternalCacheSdk, InternalRedisSdk, UserRedisSdk
from corva.validate_app_init import validate_app_type_context

StreamEventT = TypeVar("StreamEventT", bound=StreamEvent)
ScheduledEventT = TypeVar("ScheduledEventT", bound=ScheduledEvent)
HANDLERS: Dict[Type[RawBaseEvent], Callable] = {}
GENERIC_APP_EVENT_TYPES = [RawStreamEvent, RawScheduledEvent, RawTaskEvent]
GENERIC_APP_EVENT_TYPES = (
RawStreamEvent,
RawScheduledEvent,
RawTaskEvent,
)


def get_cache_key(
Expand Down Expand Up @@ -72,11 +77,30 @@ def wrapper(aws_event: Any, aws_context: Any) -> List[Any]:
user_handler=handler,
logger=CORVA_LOGGER,
) as logging_ctx:
# Verify either current call from app_decorator or not
# for instance from partial rerun merge
(
raw_custom_event_type,
custom_handler,
) = _get_custom_event_type_by_raw_aws_event(aws_event)
specific_callable = custom_handler or func
is_direct_app_call: bool = not custom_handler
data_transformation_type = raw_custom_event_type or raw_event_type
if merge_events:
aws_event = _merge_events(aws_event, data_transformation_type)

if (
is_direct_app_call
and data_transformation_type not in GENERIC_APP_EVENT_TYPES
):
CORVA_LOGGER.warning(
f"Handler for {data_transformation_type.__name__!r} "
f"event not found. Skipping..."
)
return []

if is_direct_app_call:
# Means current app call is not RawPartialRerunMergeEvent or similar
validate_app_type_context(aws_event, raw_event_type)

try:
context = CorvaContext.from_aws(
Expand All @@ -86,20 +110,8 @@ def wrapper(aws_event: Any, aws_context: Any) -> List[Any]:
redis_client = redis.Redis.from_url(
url=SETTINGS.CACHE_URL, decode_responses=True, max_connections=1
)
data_transformation_type = raw_custom_event_type or raw_event_type
if merge_events:
aws_event = _merge_events(aws_event, data_transformation_type)
raw_events = data_transformation_type.from_raw_event(event=aws_event)

if (
custom_handler is None
and data_transformation_type not in GENERIC_APP_EVENT_TYPES
):
CORVA_LOGGER.warning(
f"Handler for {data_transformation_type.__name__!r} "
f"event not found. Skipping..."
)
return []
specific_callable = custom_handler or func

results = [
specific_callable(
Expand Down Expand Up @@ -354,7 +366,11 @@ def task(
return functools.partial(task, handler=handler)

@functools.wraps(func)
@functools.partial(base_handler, raw_event_type=RawTaskEvent, handler=handler)
@functools.partial(
base_handler,
raw_event_type=RawTaskEvent,
handler=handler,
)
def wrapper(
event: RawTaskEvent,
api_key: str,
Expand Down
7 changes: 7 additions & 0 deletions src/corva/models/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
from __future__ import annotations

import abc
from enum import Enum
from typing import Any, Sequence

import pydantic


class AppType(str, Enum):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I like that we do not have mapping dict now. Also, is there a way to use already existing models for event to identify the app type?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that I've got your point, could you please explain a bit wider 🙏

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neatpick: Seems like AppType is type of app defined in manifest.json.

STREAM = "stream"
TASK = "task"
SCHEDULER = "scheduler"


class CorvaBaseEvent(pydantic.BaseModel):
class Config:
extra = pydantic.Extra.allow
Expand Down
83 changes: 83 additions & 0 deletions src/corva/validate_app_init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import contextlib
import functools
import json
import os
from typing import Any, Dict, Optional, Type

import pydantic

from corva.models.base import AppType, RawBaseEvent
from corva.models.scheduled.raw import RawScheduledEvent
from corva.models.stream.raw import RawStreamEvent
from corva.models.task import RawTaskEvent

MANIFESTED_APP_TYPE_TO_RAW_BASE_EVENT = {
AppType.TASK: RawTaskEvent,
AppType.STREAM: RawStreamEvent,
AppType.SCHEDULER: RawScheduledEvent,
}


def find_leaf_subclasses(base_class):
leaf_classes = []
for subclass in base_class.__subclasses__():
if not subclass.__subclasses__():
leaf_classes.append(subclass)
else:
leaf_classes.extend(find_leaf_subclasses(subclass))
return leaf_classes


def get_event_type(aws_event: Any) -> Optional[Type[RawBaseEvent]]:
all_children = find_leaf_subclasses(RawBaseEvent)
for child_cls in all_children:
with contextlib.suppress(pydantic.ValidationError):
child_cls.from_raw_event(aws_event)
return child_cls
return None


def validate_manifested_type(
manifest_app_type: 'AppType', raw_event_type: Type[RawBaseEvent]
) -> None:
expected_base_event_cls = MANIFESTED_APP_TYPE_TO_RAW_BASE_EVENT[manifest_app_type]
if expected_base_event_cls != raw_event_type:
raise RuntimeError(
f'Application with type "{manifest_app_type.value}" '
f'can\'t invoke a "{raw_event_type}" handler'
)


def validate_event_payload(aws_event: Any, raw_event_type: Type[RawBaseEvent]) -> None:

if event_cls := get_event_type(aws_event):
if not issubclass(event_cls, raw_event_type):
raise RuntimeError(
f'Application with type "{raw_event_type}" '
f'was invoked with "{event_cls}" event type'
)


@functools.lru_cache(maxsize=1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

though(non-blocking): Please be advised that this approach can not work between lamba calls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But anyway, I think we can keep such logic here to be prepared for provisioned_concurrency in the future after changes on packager side are rolled out 🙏

def read_manifest() -> Optional[Dict[str, Any]]:
manifest_json_path = os.path.join(os.getcwd(), "manifest.json")
if os.path.exists(manifest_json_path):
with open(manifest_json_path, "r") as manifest_json_file:
return json.load(manifest_json_file)
return None


def get_manifested_type() -> Optional['AppType']:
if manifest := read_manifest():
application_type = manifest.get("application", {}).get("type")
return AppType(application_type) if application_type else None
return None


def validate_app_type_context(
aws_event: Any, raw_event_type: Type[RawBaseEvent]
) -> None:
if manifested_type := get_manifested_type():
validate_manifested_type(manifested_type, raw_event_type)
else:
validate_event_payload(aws_event, raw_event_type)
2 changes: 1 addition & 1 deletion src/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "1.12.0"
VERSION = "1.12.1"
8 changes: 8 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from corva.configuration import SETTINGS
from corva.testing import TestClient
from corva.validate_app_init import read_manifest


@pytest.fixture(scope='function', autouse=True)
Expand All @@ -16,6 +17,13 @@ def clean_redis():
redis_client.flushall()


@pytest.fixture(scope='function', autouse=True)
def clean_read_manifest_lru_cache():
read_manifest.cache_clear()
yield
read_manifest.cache_clear()


@pytest.fixture(scope='function')
def context():
return TestClient._context
Loading